From 67843a855be30dba0ef71c97ee827fb60fc9b77a Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 22 Jan 2025 13:06:12 -0800 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 01c9fbd76ca73d1012f30376cc6cfff8b4769b03 Author: Bryan Fullam Date: Wed Jan 22 19:37:11 2025 +0100 feat: solana swap and bridge navigation (#29705) ## **Description** Enables swap and bridge buttons for non-evm networks and makes both of those buttons navigate to the bridge interface. Also fences the feature behind a code fence at the build level. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29705?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Run repo with "yarn start:flask" command 2. Enable "Add a new Solana account (Beta)" toggle in experimental settings 3. Add a Solana account through the account dropdown 4. Go to home page while having Solana account selected 5. Click swap or bridge 6. See bridge interface ## **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/main/.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/main/.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. commit ed2fb49150e97c2db2582fe12fc1bc6f5cc1e3cc Author: Hassan Malik <41640681+hmalik88@users.noreply.github.com> Date: Wed Jan 22 12:04:22 2025 -0500 fix: centering on Snap radio buttons (#29850) ## **Description** Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? Radio buttons were not aligned with their labels in the snap component. 2. What is the improvement/solution? Strip radio button of margin. ## **Related issues** Fixes: #29725 ## **Manual testing steps** 1. Build this branch 2. Go to https://metamask.github.io/snaps/test-snaps/2.18.0/ 3. Trigger the custom ui interactive snap dialog 4. Observe the changes below. ## **Screenshots/Recordings** ### **Before** [See issue](https://github.com/MetaMask/metamask-extension/issues/29725) ### **After** Screenshot 2025-01-22 at 9 47 19 AM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 82db3eea1108ae0417ab2b6cfd5c6405d491a8b1 Author: Pedro Figueiredo Date: Wed Jan 22 16:59:21 2025 +0000 feat: Remove 'Improved signature requests' setting toggle (#29819) ## **Description** Removes the settings toggle for redesigned signatures. Removes e2e tests used for old flows no longer supported. For `test/e2e/tests/metrics/signature-approved.spec.js`, the tests were migrated to the redesigned confirmation screen. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29819?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3029 ## **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/main/.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/main/.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. commit 50246152bd4fc9d4a2dada96e9a89ee65a1d30e2 Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Wed Jan 22 17:01:26 2025 +0100 feat: build beta (#29712) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29712?quickstart=1) This PR migrates the beta build from CircleCI to GitHub Actions. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28572, https://github.com/MetaMask/metamask-extension/issues/29446 ## **Manual testing steps** For commit messages matching the pattern: `Version v0.0.0-beta.0` on any branch other than `master,` the following should happen: 1. Build should run on GH Actions 2. Build should be uploaded on S3 3. Build should appear in the metamaskbot comment ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 615c469c2e562faa15f3a98849432a8095ce2153 Author: Micaela Estabillo <100321200+micaelae@users.noreply.github.com> Date: Wed Jan 22 07:37:47 2025 -0800 fix: handle undefined token address in useBridging hook (#29832) ## **Description** Changes - gracefully handle tokens for which there is no `address` field in useBridging - fallback to src token in swaps fetchParams when reopening extension and linking to the Bridge page [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29832?quickstart=1) ## **Related issues** Fixes: https://metamask.sentry.io/issues/6223741673/?environment=production&project=273505&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20firstRelease%3Alatest&referrer=issue-stream&sort=date&statsPeriod=7d&stream_index=12 ## **Manual testing steps** 1. Request a swap quote 2. Close extension 3. Reopen extensionm, which should redirect to Swaps page 5. Click "Swap across networks with Bridge" link 6. Bridge page should load with native asset as src token ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/0251bf1b-4621-49fa-ab6a-e547f977df22 ### **After** https://github.com/user-attachments/assets/549de95c-d325-469a-944d-049455b9e25b ## **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/main/.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/main/.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. commit 2da82c9e2570d944fd8bdc8b4af357f61f979f8a Author: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Wed Jan 22 09:29:41 2025 -0300 chore: upgrade `@metamask/user-operation-controller` (#29839) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29839?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29681 ## **Manual testing steps** E2E is enough to test the changes. ## **Screenshots/Recordings** No UI/UX changes ## **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/main/.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/main/.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 commit feb89fad247b1cb45a05c498ed7f0691590ed8d3 Author: Hassan Malik <41640681+hmalik88@users.noreply.github.com> Date: Wed Jan 22 04:35:45 2025 -0500 fix: overflow of title/description in Snap `Card` component (#29838) ## **Description** Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? Text overflow was not properly working for the title & description section of the `Card` snap component. 2. What is the improvement/solution? Add a constraint to the parent container. ## **Related issues** Fixes: https://github.com/MetaMask/snaps/issues/2815 ## **Manual testing steps** 1. Build the extension 2. Trigger a snap dialog with the example code in the issue 3. Observe the below changes. ## **Screenshots/Recordings** ### **Before** [See issue link](https://github.com/MetaMask/snaps/issues/2815) ### **After** Screenshot 2025-01-21 at 6 49 00 PM Screenshot 2025-01-21 at 6 48 00 PM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 27c1231e11604603b3747388958dd7152953d3b3 Author: Priya Date: Wed Jan 22 10:35:25 2025 +0100 chore: update test dapp version to 9.0.0 (#29827) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29827?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/main/.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/main/.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. commit d097924275d44d4d10fa1689e193fd9ef02479a6 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed Jan 22 06:53:47 2025 +0900 fix: not routing to home page after bridge tx submitted (#29809) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29809?quickstart=1) This PR fixes an issue where you would not be routed back to the Home Activity tab after submitting a bridge transaction. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29793 ## **Manual testing steps** 1. Go to Bridge 2. Submit a bridge tx 3. Observe that you are sent to the Activity screen right after ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit f856a3b423a4316f14d2e5d7cd0297f76566f70a Author: Charly Chevalier Date: Tue Jan 21 18:18:52 2025 +0100 feat: add `scopes` field to `KeyringAccount` (#29195) ## **Description** Testing the new `scopes` added on the `KeyringAccount`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29195?quickstart=1) ## **Related issues** Requires this PR to be merged first: - [x] https://github.com/MetaMask/metamask-extension/pull/28861 Related to: - https://github.com/MetaMask/accounts/pull/101 - https://github.com/MetaMask/core/pull/5066 - https://github.com/MetaMask/snap-bitcoin-wallet/pull/364 ## **Manual testing steps** - Use a previous stable version ```console git checkout Version-v12.10.0 # Or use a release build ``` - Run it: ```console yarn yarn start:flask ``` - Create a bunch of accounts (EVM, non-EVM (Solana/Bitcoin), hardware-wallet, Snap EVM accounts like the SSK) ![Screenshot 2025-01-16 at 17 11 55](https://github.com/user-attachments/assets/815303e6-2682-4c6b-9969-8f4a8c11e0d7) - Save your extension logs (Settings > Advanced) - Now disable your extension (chrome://extensions) - Stops your `yarn start:flask` - Now, update your extension by going back to this PR (or by using the latest RC if you're validating an RC version) ```console git checkout feat/keyring-account-scopes ``` - Re-run it: ```console yarn yarn start:flask ``` - Re-enable your extension (chrome://extensions) - Open up your console logs (looking at the `service worker`) - You should now see some migrations running like: ![Screenshot 2025-01-16 at 17 14 03](https://github.com/user-attachments/assets/35c08ba9-83f4-4827-aac5-97b9ee055732) - You should also see some other migrations from the Snap keyring this like so: ![Screenshot 2025-01-16 at 18 52 48](https://github.com/user-attachments/assets/5c74700c-18a4-4452-8a47-77d1434c96ad) - Save your extension logs again (Settings > Advanced, and use a different filename) - Now, compare your 2 `.json` files ## **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/main/.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/main/.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: Howard Braham Co-authored-by: MetaMask Bot commit fcb30d7034f9fed385d874897a445330b5b47473 Author: Elliot Winkler Date: Tue Jan 21 08:50:04 2025 -0700 fix: Remove old properties from state (#29792) ## **Description** There are several kinds of errors in Sentry which indicate that there are properties in controller state we are attempting to persist that do not have corresponding metadata. This indicates that these properties were removed at some point from the controller's state but no migration was added that removed them from the persisted wallet state. In many cases, at the time of removal, such a migration was not needed because the controller in question inherited from BaseController v1. We have made a targeted effort over the past few years to migrate all controllers to BaseController v2, however, and so it matters now that every property have corresponding metadata or else are removed from state. We don't want these errors to show up in Sentry because they create noise, so this commit removes these properties from state. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29792?quickstart=1) ## **Related issues** Fixes #28289. Fixes #28290. Fixes #28300. Fixes #28302. Fixes #28344. Fixes #28608. Fixes #29746. ## **Manual testing steps** These changes should not affect users in any way since the errors we are trying to avoid occur out of band and should not crash anything. ## **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/main/.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/main/.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. commit 212b5c946a4a27d8a21c856f2b3e958a88f7ac7b Author: João Tavares Date: Tue Jan 21 15:35:01 2025 +0000 test: address integration tests warnings (#29007) ## **Description** General improvements/fixes for integration tests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29007?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29002 ## **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/main/.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/main/.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. commit 64e2c3fb041ae9457da1a0ab333613f95bd22eaf Author: Priya Date: Tue Jan 21 12:02:10 2025 +0100 fix: Remove scroll to bottom requirement for signatures (#29817) Reverts MetaMask/metamask-extension#29808 Removes scroll to bottom requirement on signatures Improves the clickElementSafe function commit bb9250d5b5f3a366e279b53cc45ddb100696ddeb Author: weizman Date: Tue Jan 21 09:18:24 2025 +0200 feat(lavamoat/lavadome): update integration to improve security (#25653) Address concerns under [Safe Usage](https://github.com/LavaMoat/LavaDome/blob/main/README.md#safe-usage): * [#csp](https://github.com/LavaMoat/LavaDome/blob/main/README.md#csp) - do not allow font to be fetched from just about anywhere * [#execution-order](https://github.com/LavaMoat/LavaDome/blob/main/README.md#execution-order) - make sure LavaDome is imported right away This should go with #27756 commit ecc29079d7e32882b06b805d35849447afc181d7 Author: Prithpal Sooriya Date: Mon Jan 20 20:24:07 2025 +0000 build: update the patches applied in the package.json (#29807) ## **Description** I think there was a small syntax error when adding multiple patches [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29807?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/main/.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/main/.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. commit c99eae23cf40507e5a6e1996b4af190efc205aef Author: Pedro Figueiredo Date: Mon Jan 20 21:27:31 2025 +0100 fix: Revert "fix: Remove scroll to bottom requirement for signatures" (#29808) Reverts MetaMask/metamask-extension#29784 to address CI failures on `main` caused by that PR commit 210c794a372e29bc953a5c28f824422fe4db6743 Author: Maarten Zuidhoorn Date: Mon Jan 20 18:08:24 2025 +0100 chore: Bump `@metamask/snaps-rpc-methods` from `^11.9.0` to `^11.9.1` (#29805) ## **Description** This bumps `@metamask/snaps-rpc-methods` from `^11.9.0` to `^11.9.1`, which fixes some bugs and improves error messages related to state management. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29805?quickstart=1) ## **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/main/.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/main/.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. commit 5e95217ded0c7ab19b0aa6a676b6acb2f924f942 Author: Jony Bursztyn Date: Mon Jan 20 15:00:56 2025 +0000 feat: add Portfolio button to BTC accounts (#28184) ## **Description** Readds the "Portfolio" button for non-EVM accounts [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28184?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/pull/26148 https://github.com/MetaMask/metamask-extension/issues/28185 ## **Manual testing steps** 1. Go to the Wallet page 2. Switch to a BTC account 3. The Portfolio button should be there ## **Screenshots/Recordings** ### **Before** before ### **After** Screenshot 2025-01-17 at 14 50 46 ## **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. commit d5cd7fd3ad3421952a1222d5115756fac134b95b Author: jiexi Date: Mon Jan 20 06:26:10 2025 -0800 feat: Migrate eth_accounts and permittedChains to CAIP-25 endowment (#27847) ## **Description** This PR replaces the replaces the internal `eth_accounts` and `endowment:permittedChains` permission structure with a [CAIP-25](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-25.md) endowment. It adds adapter logic to translate to and from the new internal CAIP-25 permissions. This change should be transparent to wallet users and to dapps except for ~one~ two cases, see below. This change is required in order to support CAIP-25 and CAIP-27 requests in a follow-up PR that enables the Multichain API. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27847?quickstart=1) ## **Related issues** Related: https://github.com/MetaMask/core/pull/4784 ## **Manual testing steps** There should be no user or dapp facing difference in behavior except: * When calling `wallet_revokePermissions` and specifying either `eth_accounts` or `endowment:permitted-chains`, the entire CAIP-25 permission will be revoked. It will appear to the dapp as if both `eth_accounts` and `endowment:permitted-chains` were revoked. * When calling `wallet_getPermissions` for a permitted dapp when the wallet is **locked**, `eth_accounts` should be returned in addition to `endowment:permitted-chains`. Currently there is a regression on `main` where only `endowment:permitted-chains` gets returned when the wallet is locked. ``` await window.ethereum.request({ "method": "wallet_revokePermissions", "params": [ { eth_accounts: {} } ], }); await window.ethereum.request({ "method": "wallet_revokePermissions", "params": [ { 'endowment:permitted-chains': {} } ], }); await window.ethereum.request({ "method": "wallet_getPermissions", "params": [], }); ``` ### Locked Wallet Behavior with dapp connected Other than the two noted items below, this behavior matches that in `main` - `eth_accounts` returns [] - `wallet_getPermissions` returns permissions incl eth_accounts - `wallet_revokePermissions` works as usual and revokes eth_accounts and revoke permitted-chains together - * Note this fixes a regression in `main` where eth_accounts and permitted-chains aren't revoked as a pair if either is revoked - `eth_requestAccounts` prompts for unlock, after unlock returns accounts if any are permitted, otherwise shows connection prompt - `wallet_requestPermissions` prompts for unlock - signature methods fails with method or accounts not authorized - non-signature methods work as usual - `accountsChanged` empty array on lock. no event after revokePermissions which makes sense since the dapp was told empty array on lock and now it's actually empty array so no changes have occurred as far as the dapp should be concerned. - **CHANGED**: for dapps that were granted chain permissions via the `wallet_addEthereum` or `wallet_switchEthereumChain` flows without account permissions, these permissions will be removed with this migration. We think this ok because: - This is a very uncommon scenario for dapps to request chain switches without account permissions. - These permissions can be regained very trivially with subsequent chain switch requests. ### Testing the migration * Create a dev build from `main` * Install the dev build from the `dist/chrome` directory and proceed through onboarding * Run this command in the background console: ``` chrome.storage.local.get( null, (state) => { state.data.PermissionController = {}; // Replace this line based on instructions below chrome.storage.local.set(state, () => chrome.runtime.reload()); } ); ``` * Disable the extension * Switch to `main` and create a dev build * Enable and reload the extension * You should see in the console that migration 139 has failed Repeat the above steps but with the line above replaced with the following for example: * `state.data.NetworkController = {}; ` * `state.data.NetworkController = 'foobar'; ` * `state.data.NetworkController.selectedNetworkClientId = null;` * `state.data.NetworkController.networkConfigurationsByChainId = 'foobar';` ## **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: MetaMask Bot Co-authored-by: Alex Co-authored-by: Elliot Winkler Co-authored-by: Mark Stacey Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com> Co-authored-by: Frederik Bolding commit 5ae45abf4d88d5643cf21d11b7d89c4e4607ffd9 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Mon Jan 20 14:53:03 2025 +0100 test: [POM] Migrate bitcoin send e2e tests to POM (#29515) ## **Description** - Migrate send transaction e2e tests with bitcoin account to POM - Create related Bitcoin transaction page class and methods. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** ## **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. commit 2d335c6509e27ac32731dcef0a3c338cf3584ca2 Author: Priya Date: Mon Jan 20 13:13:07 2025 +0100 fix: Remove scroll to bottom requirement for signatures (#29784) ## **Description** Removes scroll to bottom requirement on signatures Improves the clickElementSafe function [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29784?quickstart=1) ## **Related issues** Fixes: [#29779](https://github.com/MetaMask/metamask-extension/issues/29779) ## **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/main/.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/main/.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. commit 27d2706008652602d7d950e701460569a1390ed9 Author: sahar-fehri Date: Mon Jan 20 12:06:50 2025 +0100 fix: patch mantle price fix (#29790) ## **Description** PR to patch Mantle price core [fix](https://github.com/MetaMask/core/pull/5099) current Mantle price: https://www.cryptocompare.com/coins/mantle/overview/USDC [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29790?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Add Mantle network from chainList (chainId 5000) 2. Go to MM and you should see correct fiat balance ## **Screenshots/Recordings** ### **Before** Screenshot 2025-01-17 at 21 18 51 ### **After** Screenshot 2025-01-17 at 21 17 08 ## **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/main/.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/main/.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. commit 5887e05bdd1db65710d58dcac4512858b042ba06 Author: OGPoyraz Date: Mon Jan 20 12:01:12 2025 +0100 feat: Use `gasLimitNoBuffer` on network fee estimation (#29502) ## **Description** While estimating gas for the transaction we add `50%` gas limit buffer. With this PR we want to show network fee without gas limit buffer using `gasLimitNoBuffer` property on `transactionMeta`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29502?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3773 ## **Manual testing steps** N/A ## **Screenshots/Recordings** In the recording you will see that `gasLimitNoBuffer` used over `txParams.gas`. Please see that `txParams.gas` is greater than `gasLimitNoBuffer`. This calculation leads lower value in the UI. https://github.com/user-attachments/assets/b5726828-7c13-4eb4-83fa-eea21562e6a9 ### **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/main/.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/main/.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. commit 2751a0d0b6f723718296b1b3ea5f6aef2dc38955 Author: Micaela Estabillo <100321200+micaelae@users.noreply.github.com> Date: Fri Jan 17 11:28:25 2025 -0800 chore: retain src input amount after switching tokens (#29709) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29709?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1798 ## **Manual testing steps** 1. Load bridge page 2. Select amount, src token and dest token 3. Click token switch button to swap src and dest selections 4. Verify that src amount is preserved ## **Screenshots/Recordings** ### **Before** Amount is cleared ### **After** Amount is preserved ## **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/main/.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/main/.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. commit 582ec932f3c4b4a7002f439095c98a7e76b8ab20 Author: Mark Stacey Date: Fri Jan 17 13:02:15 2025 -0330 test: Fix invalid fixture builder (#29783) ## **Description** Two of the permission fixture builders were adding permissions for accounts that do not exist. They have been updated to only grant permissions for the selected account, which is the only account guaranteed to exist in the default fixture. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29783?quickstart=1) ## **Related issues** This was extracted from https://github.com/MetaMask/metamask-extension/pull/27847 ## **Manual testing steps** See that E2E tests still pass ## **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/main/.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/main/.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. commit 3b856331b6e26b8d879206850be2e327220de38e Author: Brian Bergeron Date: Fri Jan 17 07:56:53 2025 -0800 fix: stop polling on environment close (#29707) ## **Description** When the asset controllers start polling, they add their polling tokens to app state, keyed by the particular environment (popup vs fullscreen). But these polling tokens in app state were not being used during cleanup. This PR updates `onEnvironmentTypeClosed` to stop polling by those tokens. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29707?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Open MM in fullscreen 2. Open MM popup 3. Close the popup 4. `onEnvironmentTypeClosed` should fire for the popup environment 5. Each polling token should be found and removed by one of the controllers ## **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/main/.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/main/.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. commit 8038f4de18ada13fbd3f29f78a315a981839dad7 Author: Daniel <80175477+dan437@users.noreply.github.com> Date: Fri Jan 17 16:43:20 2025 +0100 feat: Enable BSC for smart transactions (#29747) ## **Description** Enables BSC for smart transactions and reduces status check time to 1s. We can turn off BSC support for smart transactions remotely if needed. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29747?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Make sure smart transactions are enabled in Advanced Settings 2. Be on the BNB Chain 3. Submit a tx. It will be submitted as a smart transaction Happy paths tested for Swaps, Send and dapp transactions, but this will require extensive testing before it goes to production. ## **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/main/.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/main/.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. commit 8f82ac4f13cb1a67463bec35ca3c977cce4e8a3d Author: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Fri Jan 17 13:34:02 2025 +0100 refactor: use `withKeyring` method (#25435) (#27025) ## **Description** This PR cherry-picks def6b15 into `develop`, after being reverted at 821c3bd. The reason the original commit was reverted is explained in #26840. _From the original PR (#25435):_ With `@metamask/keyring-controller` v16 a new method is available which simplify safe direct keyring interactions which are not available through KeyringController. Through `withKeyring` it's possible to lock KeyringController's main mutex and select a specific keyring to interact with while KeyringController will be unusable concurrently. `withKeyring` also takes care of persisting keyrings and updating the controller state at the end of the operation, which makes it possible to use it in substitution of `getKeyringsByType` and `persistAllKeyrings`. Currently, most of the direct keyring interactions in the extension are made for hardware devices and snaps. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27025?quickstart=1) ## **Related issues** Fixes: #26840 Fixes: #24276 ## **Manual testing steps** _As for the original PR (#25435):_ Affected workflows may include: - Lock - Unlock - Add account - Remove account - Connect hardware wallet - All hardware wallet interactions ## **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. commit e659f4f02f19dcb33c3cf27baf7700fb2b35ff19 Author: Pedro Figueiredo Date: Fri Jan 17 12:11:22 2025 +0000 fix: Missing "Unlimited" as value for the DAI permit (#29597) ## **Description** Introduces a new logical path for displaying "Unlimited" in the permit simulation, for when the `allowed` property in the signature message is set and the permit is for the token DAI. This is as is specified in [ERC-2612](https://eips.ethereum.org/EIPS/eip-2612)'s "Backwards Compatibility" section: > There are already a couple of permit functions in token contracts implemented in contracts in the wild, most notably the one introduced in the dai.sol. > Its implementation differs slightly from the presentation here in that: > instead of taking a value argument, it takes a bool allowed, setting approval to 0 or uint(-1). This PR also fixes a bug that prevents boolean values from being displayed in the key value display in the message section of signatures. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29597?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28964 ## **Manual testing steps** 1. Open the browser console and execute the following code: ```javascript async function connectMetaMask() { try { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts', }); console.log('Connected account:', accounts[0]); return accounts[0]; } catch (error) { console.error('User rejected the request:', error); throw error; } } async function signPermit() { try { const fromAddress = await connectMetaMask(); const msgParams = { types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], Permit: [ { name: 'holder', type: 'address' }, { name: 'spender', type: 'address' }, { name: 'nonce', type: 'uint256' }, { name: 'expiry', type: 'uint256' }, { name: 'allowed', type: 'bool' }, ], }, domain: { name: 'Dai Stablecoin', version: '1', verifyingContract: '0x6B175474E89094C44Da98b954EedeAC495271d0F', chainId: '0x1', }, primaryType: 'Permit', message: { holder: '0xD2C44F28eC4C7eF686f587FADdb204da3aEFa827', spender: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', allowed: true, nonce: 0, expiry: 1660916504, }, }; const signature = await window.ethereum.request({ method: 'eth_signTypedData_v4', params: [fromAddress, JSON.stringify(msgParams)], }); console.log('Signature:', signature); return signature; } catch (error) { console.error('Error signing permit:', error); } } signPermit(); ``` 2. Change `"allowed": true` to `"allowed": false` in the aforementioned code and execute it to see the revocation screen. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2025-01-08 at 18 30 35 Screenshot 2025-01-08 at 18 30 37 Screenshot 2025-01-09 at 10 22 09 Screenshot 2025-01-09 at 10 22 13 ## **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/main/.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/main/.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. commit 16ac0894b324d62ca501376e18508b06cb6b903f Author: António Regadas Date: Fri Jan 17 11:27:22 2025 +0000 chore: skips failing tests (#29778) ## **Description** This test suite is failing for Firefox, and causing `main` to be red in CI, therefore we are skipping it for now and will come back to them later to fix them. Reference [slack thread](https://consensys.slack.com/archives/CTQAGKY5V/p1737051408094609). ## **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/main/.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/main/.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. commit 2a78f64b1f24abfebb161099b1a2f8220463059f Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri Jan 17 08:31:53 2025 +0000 fix: capture only local ppom errors (#29702) ## **Description** This PR refines error handling by limiting the capture of errors to those occurring within the local PPOM, reducing unnecessary noise. Errors from the API are already tracked through observability tools, ensuring a more streamlined and focused error reporting process. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29702?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28767 ## **Manual testing steps** 1. Go to the test dapp 2. Click on one of the buttons in `PPOM - Malicious Transactions and Signatures` section 3. API should perform the validation as normal ## **Screenshots/Recordings** [Screencast from 2025-01-14 16-00-47.webm](https://github.com/user-attachments/assets/f1a3cf62-fbf9-4711-94bc-de37a7b0d67b) ### **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/main/.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/main/.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. commit 8ac211699a9f4c316d8a22f62acb7c38cf9901cd Author: jiexi Date: Thu Jan 16 15:24:13 2025 -0800 test: Remove unused `restrictReturnedAccounts` param option (#29767) ## **Description** * Removes the `restrictReturnedAccounts` param option in many of the fixtures for eth_accounts in the fixture builder since it is no longer used anywhere [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29767?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/main/.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/main/.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. commit a6a91d5a84d5b188579346c4880cdaea62d1ebb4 Author: jiexi Date: Thu Jan 16 14:10:47 2025 -0800 test: Cleanup snap-account-signature e2e tests. Add permittedChains scenario to wallet_revokePermissions e2e test (#29761) ## **Description** * Fix incorrect snap-account-signature e2e test fixtures / starting state (accounts permissioned before they exist in the wallet) * Add `endowment:permitted-chains` scenario to `wallet_revokePermissions` e2e test [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29761?quickstart=1) ## **Related issues** See: https://github.com/MetaMask/metamask-extension/pull/27847 ## **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/main/.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/main/.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: Alex Donesky Co-authored-by: Mark Stacey commit f503a480980ecc22e4b9044fbd2d812844f167ee Author: Micaela Estabillo <100321200+micaelae@users.noreply.github.com> Date: Thu Jan 16 13:42:23 2025 -0800 chore: remove bridge src token list from controller state (#29492) ## **Description** This reduces data stored in the BridgeController by removing stored bridge-specific token lists, and reusing token data from the assets controllers instead. Changes - **AssetList**: compare symbols in order to determine which token to highlight as "selected" - **AssetPickerModal**: fetch topAssets list on network change, yield tokens with balances first - **pages/bridge**: remove custom src token list (falls back to AssetPicker's default list) - **bridge-controller, ducks/bridge**: remove src token methods and states - **hooks/bridge**: remove allowlist check for tokens since these have moved to the bridge-api [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29492?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1825 Depends on https://github.com/consensys-vertical-apps/va-mmcx-bridge-api/pull/136 ## **Manual testing steps** 1. Verify that Swap+Send token allowlists have not changed 2. Verify that Send token allowlists have not changed 3. Bridging 137:BNB -> 10:USDC should result in no quotes 4. Bridging 10:USDC -> 137:USDC should result in no quotes 5. Bridge src token should be pre-filled when navigating from asset page 6. Bridge inputs should be restored after fetching quotes and reopening extension ## **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/main/.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/main/.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. commit cdd603c56211d1d483d87e5ee3fea27aadd66fda Author: jiexi Date: Thu Jan 16 13:09:09 2025 -0800 fix: Fix `EditAccountsModal` and `EditNetworkModal` checkboxes reseting back to default on rerender (#29755) ## **Description** Fixes a bug in the `EditAccountsModal` and `EditNetworkModal` that occasionally causes any changes to the default selected checkboxes to be reset on component rerender. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29755?quickstart=1) ## **Related issues** See: https://github.com/MetaMask/metamask-extension/pull/27847#discussion_r1917444716 ## **Manual testing steps** 1. For a dapp with permissions 2. In the wallet UI, edit the dapp permissions 3. Open the edit network modal 4. Change some checkboxes to be different from what was default selected 5. Wait 6. The checkboxes should not revert back to the default selected Do the same for the edit accounts modal ## **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/main/.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/main/.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. commit 08cc02fd63b04eb0fd861ced19701a1fec940e1f Author: Brian Bergeron Date: Thu Jan 16 11:52:11 2025 -0800 feat: add Lisk network logos (#29762) ## **Description** Adds logos for the list network. Taken from https://github.com/MetaMask/metamask-extension/pull/28763 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29762?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Add the lisk network (mainnet and testnet) from https://chainlist.org/?search=lisk&testnets=true 2. Verify they have icons in the network selector ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2025-01-16 at 10 56 14 AM ## **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/main/.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/main/.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. commit fd6b18086ec775bca0d0bc7b6d205da0a15cf25d Author: Priya Date: Thu Jan 16 20:26:36 2025 +0100 test: fix flaky erc1155 set approval for all tests (#29467) ## **Description** Fixes the flkay set approval for all tests. Also improved the click element retry logic to also include ElementClickInterceptedError and ElementNotInteractableError. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29467?quickstart=1) ## **Related issues** Fixes: [#28816](https://github.com/MetaMask/metamask-extension/issues/28816) ## **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/main/.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/main/.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. commit e825061d0eb7e79434b7b8db525dd6270320887b Author: sahar-fehri Date: Thu Jan 16 20:03:39 2025 +0100 fix: remove check for changeX token from test (#29752) ## **Description** PR to temporarily fix flakey test in [CI](https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/119494/workflows/3c063d9c-ca1f-49f2-82fd-f6a7ed3806d3/jobs/4457831/tests). The error suggests that its looking for token "Changex" in the [UI](https://circleci-tasks-prod.s3.us-east-1.amazonaws.com/storage/artifacts/fb281712-ea41-446f-b28f-c0bea18842cc/f44a10a1-324a-479a-a517-ac7449f04940/3/test-artifacts/undefined/Import%20flow%20allows%20importing%20multiple%20tokens%20from%20search/test-failure-screenshot-1.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQVFQINEOHHHIDJFC%2F20250116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250116T154238Z&X-Amz-Expires=60&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEFAaCXVzLWVhc3QtMSJIMEYCIQCs2Dbc66EZ3NDFwl31fBUwbUHDvig5d3rz7Gepa5PtKAIhAOFgadFo%2FY3lQAUmNRK7KNiJAxCUfG2i%2BCg25Xo2mYkcKqsCCEkQAxoMMDQ1NDY2ODA2NTU2Igy8sIzMomt82TDhPeMqiAKCPwJH6LSQMfhw3vc9jmERRbkspocJk0T4IiS8X%2FS2imb2LWD6242leJdrbg6V8V0WaR0s4V09Fc%2Fyncs6fp5fFGFp7VZ6FDY5ya1tRxkKjPhS1RlPwIHEWZz%2FOy%2BAJnkkKk4y6Zy%2F9%2B1sEYowXjUxkAfepTw%2BF62cXQakg3DpJKFTA0K01rEqg0bqGketptqF3xJwPiF%2BteDiDm1qZ3mWiPdgEzyGruxg9YIlXbdKoAQ9q3l%2Fwe%2Bl76RGVK84kyNZdCEim2NjdzRljDPM9im2rrvbgOKkrSjYt7U%2BEiHXV%2FH%2Fbz3sG%2BPbdF0eGa1B%2BaEAtdGrzr807A82qe4qY5v%2FEtxpoDGu3pMw%2F9CkvAY6nAFHOiiHsiyoFyynYILU5LwrACm1puVGJ6HdwXc5kTfP0tdKlVp03AfHIg2q%2BvYjYEQDE%2B%2FTlKRAvGWFlxwVmr45mc1R590X2wgazjuME9XeLbh6Xk2MCO0AowwPFcMTCYCObWY8LQb9%2FjeW9pNN83A80l8x1GyEyjnsEvLvyKF8C9mbs4euKXpWKBaIwUylIUzYjMzW3b3PPesUggk%3D&X-Amz-SignedHeaders=host&x-id=GetObject&X-Amz-Signature=ada9d7c63c7365144357b1e735a84dea6beb5f244aa0c1a03c57b8526b7b857d); while looking at the screenshot from CI the token imported is "changeX"; So the check in the test should be more like `await tokenList.check_tokenIsDisplayed('ChangeX');` and not `await tokenList.check_tokenIsDisplayed('Changex');` In this PR i have removed the check from the test to unblock CI. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29752?quickstart=1) ## **Related issues** Fixes: Related: https://github.com/MetaMask/metamask-extension/issues/29750 ## **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/main/.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/main/.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. commit 933bd1bdc172c686d5c834382c59f4b12132396a Author: Salim TOUBAL Date: Thu Jan 16 19:41:22 2025 +0100 fix: add soneium logo (#29713) ## **Description** Adding Soneium Mainnet and Soneium Testnet [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29686?quickstart=1) ## **Related issues** Fixes: N/A ## **Manual testing steps** 1. Adding Soneium network to the metamask image 2. Click the Metamask icon to add network 3. Open Metamask extension to check the network and token icons Open Metamask extension to check the network and token icons ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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: sahar-fehri commit 64aa03b8659f3cfe38a65e36265997fa88a710f9 Author: Priya Date: Thu Jan 16 19:03:02 2025 +0100 chore: de-deuplicates the erc20 revoke allowance tests (#29658) ## **Description** The ERC 20 revoke allowance tests were using some exported methods from other tests (increase allowance and approve component) causing also those tests to be run along with the revoke tests. This PR moves the reusable methods to the shared file so that the tests are not duplicated [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29658?quickstart=1) ## **Related issues** Fixes: [#28486](https://github.com/MetaMask/metamask-extension/issues/28486) ## **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/main/.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/main/.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. commit c4f0cd6b9e3c25616547b9b02609a1412e160cd8 Merge: 77b7ba4ae9 a0e2584304 Author: Dan J Miller Date: Thu Jan 16 11:45:52 2025 -0330 Merge pull request #29614 from MetaMask/master-sync chore: Master sync following v12.9.3, v12.10.0 and v12.10.1 commit 77b7ba4ae91bf6f1c95450d668e93ca3598987dd Author: Frederik Bolding Date: Thu Jan 16 14:30:49 2025 +0100 chore: Bump Snaps packages (#29693) ## **Description** Bump Snaps packages and handle any required changes. Summary of Snaps changes: - Add `snap_getState`, `snap_setState`, `snap_clearState` RPC methods - Add support for non-recurring cronjobs scheduled via `snap_scheduleBackgroundEvent` - Cache snap state in memory for improved performance - Unblock `eth_signTypedData` - Add `hideBalances` property to `snap_getPreferences` - Allow usage of `Text` in `Value` props - Add `backgroundColor` property to `Container` component - Skip unnecesary provider initialization [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29693?quickstart=1) ## **Related issues** Closes https://github.com/MetaMask/snaps/issues/2977 Closes https://github.com/MetaMask/snaps/issues/2906 Closes https://github.com/MetaMask/metamask-extension/issues/29498 Closes https://github.com/MetaMask/metamask-extension/issues/29469 --------- Co-authored-by: Guillaume Roux Co-authored-by: Hassan Malik <41640681+hmalik88@users.noreply.github.com> Co-authored-by: David Drazic Co-authored-by: Maarten Zuidhoorn commit 66c994ac4ffae3bbdde98482d0aa09f162355b9e Author: Pedro Figueiredo Date: Thu Jan 16 13:17:54 2025 +0000 feat: Remove 'Improved transactions requests' toggle (#29695) ## **Description** We [recently enabled the transaction redesign for all users by default](https://github.com/MetaMask/metamask-extension/pull/28321). We also [migrated some existing e2e tests to corresponding e2e tests for redesigned flows](https://github.com/MetaMask/metamask-extension/pull/28780). This PR removes the toggle that allowed users to opt out of the redesigned confirmations. It also removes the e2e tests for old confirmation flows that are no longer visible in any scenario. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29695?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3030 ## **Manual testing steps** 1. Open experimental settings. 2. The option toggle shouldn't be there. 3. Create a new transaction confirmation 4. It should be redesigned. ## **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/main/.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/main/.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. commit a0e258430436f6d02a5df998d1df5a2d60163126 Merge: 003045373c 5786e7a022 Author: Derek Brans Date: Thu Jan 16 08:05:57 2025 -0500 Merge origin/main into master-sync commit 5786e7a02217ce12ae9bc9c6681f330565f9d9d3 Author: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com> Date: Thu Jan 16 12:19:52 2025 +0100 feat: check balance switching networks e2e tests (#29345) ## **Description** This PR covers the Solana e2e coverage with the below scenarios: - Sending SOL flow (different scenarios) - Switching accounts between different EVM and Solana - Check solana balance (there is a bug open, hence only one scenario is running) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29345?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Test executed in chrome browser yarn build:test:flask yarn test:e2e:single test/e2e/flask/solana/check-balance.spec.ts --browser=chrome yarn test:e2e:single test/e2e/flask/solana/send-flow.spec.ts --browser=chrome yarn test:e2e:single test/e2e/flask/solana/switching-network-accounts.spec.ts --browser=chrome Test executed in firefox browser yarn build:test:flask:mv2 ENABLE_MV3=false yarn test:e2e:single test/e2e/flask/solana/check-balance.spec.ts --browser=firefox ENABLE_MV3=false yarn test:e2e:single test/e2e/flask/solana/send-flow.spec.ts --browser=firefox ENABLE_MV3=false yarn test:e2e:single test/e2e/flask/solana/switching-network-accounts.spec.ts --browser=firefox ## **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/main/.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/main/.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: Ulisses Ferreira Co-authored-by: Dan J Miller commit f6b163afcb80bfff6412ca412c1219ab0d49670c Author: Pedro Figueiredo Date: Thu Jan 16 10:47:20 2025 +0000 feat: Add new completion_time_onchain property to Transaction Finalized events (#29474) ## **Description** The new property `completion_time_onchain` has the number of seconds (rounded to the hundredths) between submitted time and the block timestamp. This number is necessarily lower than the previously existing `completion_time` property, which captures the time between block validation and UI finalization and depends on the polling interval. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29474?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3503 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2025-01-07 at 13 39 29 ## **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/main/.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/main/.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. commit 6b765baea8c3de40f4a8fbab202c2e8ce5a50bb3 Author: Daniel <80175477+dan437@users.noreply.github.com> Date: Thu Jan 16 11:16:14 2025 +0100 feat: Use a dynamic interval value for smart transaction status polling (#29703) ## **Description** We want to use a dynamic interval value for smart transaction status polling, which is returned by backend. That way we can easily change it if needed and use the most optimal value. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29703?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Make sure smart transactions are enabled in Advanced Settings 2. Be on Ethereum Mainnet 3. Submit a smart transaction 4. Check that a network request for checking smart transaction status happens every second ## **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/main/.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/main/.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. commit 77243cb0aecdfc408ecbcd3dabec61a97cd99d67 Author: Christian Montoya Date: Wed Jan 15 16:08:29 2025 -0500 docs: fix developer docs link in README (#29734) ## **Description** The URL in the main README was pointing to a dead site, this fixes the URL [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29734?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** 1. Go to https://github.com/MetaMask/metamask-extension/blob/main/README.md 2. Click Developer Docs ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 003045373c82b66b90f655fa8793256a0d31ba2c Merge: 317a69cc35 5c45c3c036 Author: Dan J Miller Date: Wed Jan 15 16:26:19 2025 -0330 Merge pull request #29731 from MetaMask/Version-v12.10.1 Version v12.10.1 RC commit 5c45c3c036e5be105aaa581c5725c735bd938955 Author: Derek Brans Date: Wed Jan 15 13:57:08 2025 -0500 chore: Update changelog for v12.10.1 (#29735) Update changelog for v12.10.1 commit 920373a9e02fe38484d1340a0910104e6ef70577 Author: Davide Brocchetto Date: Wed Jan 15 10:46:10 2025 -0800 test: Fix Playwright Swap e2e failures (#29729) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29729?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/main/.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/main/.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. commit e82092e6ec8c8ab143c71096a46545cd14408a74 Author: Xiaoming Wang <7315988+dawnseeker8@users.noreply.github.com> Date: Thu Jan 16 02:14:03 2025 +0800 fix: update eth-ledger-keyring-bridge lib to fix web pack issue. (#29722) This PR will update the ledger keyring bridge library again to solve the webpack build issue. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29722?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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: MetaMask Bot commit 6b433e64df209b21eeb21a876f3d76bb46eb1ebd Author: Derek Brans Date: Wed Jan 15 13:03:06 2025 -0500 cherry-pick: remove mmi tests into v12.10.1 (#29732) Cherry-pick 7a78bf271bb556a3528d88964354a418fc16dc0a, #29233 into v12.10.1 Conflicts: `.circleci/config.yml` Co-authored-by: Ramon AC <36987446+racitores@users.noreply.github.com> commit 5b5c04a16fb7937a6e9d59b1debe4713978ef39d Author: Danica Shen Date: Wed Jan 15 17:55:27 2025 +0000 feat(3744): Implement feature Flag Values with Scope Based on threshold (#29081) ## **Description** A more advanced version of remote rollout feature flags are object flags, described in ADR [here](https://github.com/MetaMask/decisions/blob/b3094d47a568ac1e076a44fa704c2d29d1b59f35/decisions/wallet-platform/0001-remote-rollout-feature-flags.md#use-case-2a-feature-flag-values-with-scope-based-on-threshold), we would like to address the use case allowing different values for random subsets of the user base. After the implementation of https://github.com/MetaMask/MetaMask-planning/issues/3742 in @metamask/remote-feature-flag-controller, we can now pass in metaMetricsId in controller when registering and further retrieve value based on threshold. In terms of metaMetricsId, it can be found in app state when onboarding a user, deleting metametrics data would not remove metaMetricsId [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29081?quickstart=1) ## **Related issues** Controller: https://github.com/MetaMask/MetaMask-planning/issues/3742 Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3744 ## **Manual testing steps** Load the extension Option 1: input chrome.storage.local.get(console.log) in dev tools console, and you'll find in data => RemoteFeatureFlagController => contains 2 dataset, cacheTimestamp and remoteFeatureFlags with value, where `remoteFeatureFlags` has 3 elements, the `testFlagForThreshold` is based on `metametricsId` Screenshot 2024-12-12 at 21 58 26 Option 2: go to settings => advanced, and click download stage logs button, you'll find testFlagForThreshold within the remoteFeatureFlags state. Option 3: Settings => about page, if you open console in dev tools, there's a log after Feature flag fetched successfully, which will be removed after we implement 1st feature flag in production, it looks like ``` Fetch remote feature flag success, eg: testFlagForThreshold has value {"name":"groupC","value":"valueC"} ``` ## **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/main/.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/main/.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 commit da25fe581c50076275ea134fabcbdf2d6fb5a84c Author: MetaMask Bot Date: Wed Jan 15 16:58:32 2025 +0000 Version v12.10.1 commit 7a78bf271bb556a3528d88964354a418fc16dc0a Author: Ramon AC <36987446+racitores@users.noreply.github.com> Date: Wed Jan 15 16:04:04 2025 +0100 test: MMQA-188: remove mmi e2e tests from ci (#29233) ## **Description** MMI e2e tests are not longer maintained so they need to be removed from ci: - Remove CI (only this in this PR due to the urgency of the change) Create a new PR to remove the next points: - Remove Docs - **node dependencies** - Remove e2e/tests - Remove e2e/tests/playwright - Remove references in scripts - Remove stuff not in the code base like https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/116332/workflows/e18a49f4-f111-4728-abc8-23d46b93feb6/jobs/4357634 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29233?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/main/.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/main/.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. commit 3944d7e00ed8bb8d3e156b03b6ecff66f8f670f0 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Wed Jan 15 13:49:21 2025 +0100 chore: remove `@metamask/eth-query` package (#29649) ## **Description** This PR removes `@metamask/eth-query` package by using directly the provider without using any wrapper package. The purpose of this PR is to simplify and break down the process of replacing web3-stream-provider ([issue](https://github.com/MetaMask/metamask-extension/issues/28774)), which uses the legacy sendAsync method and is not EIP-1193 compliant, with StreamProvider from @metamask/providers, which fully adheres to the EIP-1193 Specs. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29620?quickstart=1) ## **Related issues** Fixes: partially completes https://github.com/MetaMask/metamask-extension/issues/28774 ## **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/main/.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/main/.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 commit 0bcd3854c9a7250bd9c4b5a022f3aac4144829e2 Author: Pedro Figueiredo Date: Wed Jan 15 11:21:07 2025 +0000 feat: Capture block number within Transaction Finalized Anon events (#29638) ## **Description** Add a new property block_number within our Transaction Finalized Anon events to track the block number on which a transaction has been added. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29638?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3539 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2025-01-10 at 17 45 25 ## **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/main/.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/main/.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. commit 953eab49bad0e50d88e1adb1efc1be80a59fbe84 Author: Mathieu Artu Date: Wed Jan 15 11:45:40 2025 +0100 fix: add prefix to account sync error message (#29718) ## **Description** This PR add an "Account sync - " prefix for account syncing erroneous situations that are going to be sent to Sentry. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29718?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/main/.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/main/.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. commit d8ce2c7805cbcc37a22d81c49f9f90f8810dfaa5 Author: Mathieu Artu Date: Wed Jan 15 07:12:50 2025 +0100 fix: account syncing was not working after upgrading from a previous version (#29701) ## **Description** Account syncing was depending on `_addAccountsWithBalance` to be finished before being triggered. But `_addAccountsWithBalance` was only triggered after onboarding. So people upgrading from a previous version of the extension were not able to use account syncing since `_addAccountsWithBalance` was triggered during their original onboarding, at a time where account syncing was not available. Additionally, the state value `isAccountSyncingReadyToBeDispatched` from `UserStorageController` was not persisted. So people were not able to use account syncing after upgrading their extension version, even coming from a version that had account syncing enabled and working. This PR fixes that, by adding a migration that sets `isAccountSyncingReadyToBeDispatched` to true if `completedOnboarding` is true. Also, bumping `@metamask/profile-sync-controller` to version `v4.1.0` will ensure that `isAccountSyncingReadyToBeDispatched` is persisted. (https://github.com/MetaMask/core/pull/5147) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29701?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29679 ## **Manual testing steps** 1. Install MetaMask v12.9.3 2. Complete onboarding 3. Upgrade to this PR version 4. Verify that account syncing is working ## **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/main/.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/main/.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. commit 31915a6a7d5ec7dc7dffe004842491a616222e63 Author: Micaela Estabillo <100321200+micaelae@users.noreply.github.com> Date: Tue Jan 14 17:21:45 2025 -0800 fix: focused search field forces scroll (#29676) ## **Description** The bridge page's input fields are auto-focused by default, which forces the AssetPicker's search input field to keep re-focusing when it's open. As a result, the asset list keeps scrolling up to the top of the modal. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29676?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1856 ## **Manual testing steps** 1. Load bridge page 2. Click src token picker and verify search input component is focused 3. Scroll to bottom of asset list and wait a 5 or more seconds 4. Verify that modal does not scroll back up to search input component ## **Screenshots/Recordings** ### **Before** ![Screenshot 2025-01-13 at 2 36 23 PM](https://github.com/user-attachments/assets/8a5d3296-b7ff-4caf-89b3-5e35eb8f466e) ### **After** ![Screenshot 2025-01-13 at 2 35 33 PM](https://github.com/user-attachments/assets/f9c60d55-bb94-42dd-81f5-b042f0000f6e) ## **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/main/.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/main/.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. commit deb67c4962a44b454ac317322fb2c0c1b7f42687 Author: Mark Stacey Date: Tue Jan 14 20:32:37 2025 -0330 chore: Remove unused `nonce-tracker` patch (#29714) ## **Description** This patch was accidentally left behind when we updated from v5 to v6. The patch is no longer needed, the change was inclued as part of v6 in this comment: https://github.com/MetaMask/nonce-tracker/commit/729ddd9f6dd17c6ec19896bd8a2485360a96b675 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29714?quickstart=1) ## **Related issues** N/A ## **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/main/.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/main/.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. commit ac0133746d5518bfe8d56fecab38e37e3184a0d8 Author: Davide Brocchetto Date: Tue Jan 14 22:18:18 2025 +0100 test: Fixed Swap Playwright tests (#29710) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29710?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/main/.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/main/.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. commit 220435a734dd8ceeefd10d8cfbbcf20ae13a06a4 Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Wed Jan 15 00:49:00 2025 +0400 feat: run the "Main" workflow on release branches (#29704) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29704?quickstart=1) This PR adds the glob expression "Version-v*" to run the "Main" workflow on release branches, thus allowing us to upload builds to Runway. It also modifies the Runway action to only run on release branches. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3619 ## **Manual testing steps** 1. Runway build artifacts should be uploaded on release branches. ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 810b16523b7e8abc63b780a606781aeaceaca54c Author: jiexi Date: Tue Jan 14 11:51:46 2025 -0800 test: Fix flask user-operations e2e test setup (#29675) ## **Description** Fixes incorrect permission in the fixtures used in the Flask user-operations e2e tests. Specifically the snap added account was permissioned in eth_accounts before it actually existed in the wallet. This replaces the fixture with a connection step instead. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29675?quickstart=1) ## **Related issues** See: https://github.com/MetaMask/metamask-extension/pull/27847#discussion_r1913303504 ## **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/main/.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/main/.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. commit c5e39f4575efb1369e8158cffd193b490aae63ac Author: jiexi Date: Tue Jan 14 09:59:03 2025 -0800 test: add fixture permission controller test dapp two accounts (#29671) ## **Description** Cleanup the fixture for the `eth_accounts` and `Increase Token Allowance` e2e specs [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29671?quickstart=1) ## **Related issues** See: https://github.com/MetaMask/metamask-extension/pull/27847#discussion_r1913308807 ## **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/main/.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/main/.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. commit df4458a1c707382a92757f8b8f2ef91783d50aba Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Tue Jan 14 21:49:54 2025 +0400 feat: lint-workflows (#29643) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29643?quickstart=1) This PR integrates the newly introduced lint-workflows reusable workflow to lint GitHub actions, replacing the previous hard-coded version with a reusable one. ## **Related issues** Contributes to: https://github.com/MetaMask/metamask-extension/issues/28572 ## **Manual testing steps** 1. Workflows should still be linted ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit e8b52cd44180adcb903abc49f1b218a7159f56cb Author: OGPoyraz Date: Tue Jan 14 17:52:19 2025 +0100 fix: Fix `SignatureController` hub `cancelWithReason` event handler (#29673) ## **Description** This PR aims to fix `cancelWithReason` event handler which is causing `rejectPendingApprovals` to be hang and throw. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29673?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29663 ## **Manual testing steps** 1. Create multiple signature request without accept or reject 2. Close notification window 3. See that no more badge number in extension bar ## **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/main/.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/main/.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. commit b52121ea30aeeb73676391dbf1e4879d7417c5ae Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Tue Jan 14 17:50:35 2025 +0100 chore: remove `@metamask/ethjs` package (#29620) ## **Description** This PR introduces the following changes: • Removes the `@metamask/ethjs` package. • Replaces the global variable `global.eth` with the existing `global.ethereumProvider` (an instance of `StreamProvider` from `web3-stream-provider` package). • Updates all instances of `getCode` to use a manual `sendAsync` call for the `eth_getCode` method, as StreamProvider does not include getCode, unlike ethjs. • Substitutes the use of ethjs contracts with Contract instances from the @ethersproject/contracts library. • Removal of `@metamask/ethjs-contract` and `@metamask/ethjs-query` packages. The purpose of this PR is to simplify and break down the process of replacing web3-stream-provider ([issue](https://github.com/MetaMask/metamask-extension/issues/28774)), which uses the legacy sendAsync method and is not EIP-1193 compliant, with StreamProvider from @metamask/providers, which fully adheres to the EIP-1193 Specs. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29620?quickstart=1) ## **Related issues** Fixes: partially completes https://github.com/MetaMask/metamask-extension/issues/28774 ## **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/main/.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/main/.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 commit 140b5d80b012995148887d053c34bdee1ee7f463 Author: Mathieu Artu Date: Tue Jan 14 16:00:11 2025 +0100 feat: add auto sign-in for existing users (#29654) ## **Description** This PR adds an auto-sign-in mechanism. Currently, users that upgraded from a pre-auth era version might meet the conditions for being signed-in, but aren't because we attached the sign-in mechanism to manual actions like toggling MetaMetrics or Profile Syncing. This fixes that, by checking if conditions for signing-in are met every time the UI opens, and if so, dispatching the sign-in method from `AuthenticationController` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29654?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/main/.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/main/.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. commit 317a69cc35a6611840bd2fb833c0e4bbd29be195 Merge: 2e0fe07f0e 1e7fe77a46 Author: Dan J Miller Date: Tue Jan 14 11:05:02 2025 -0330 Merge pull request #29330 from MetaMask/Version-v12.10.0 Version v12.10.0 RC commit bbe8143a52f7e137d3917f2c5fe29fc597358601 Author: Mathieu Artu Date: Tue Jan 14 15:03:39 2025 +0100 feat: add sentry error logging to account syncing erroneous situations (#29692) ## **Description** This PR adds Sentry logging for all account syncing erroneous situations calls. It also attaches context so we know more about these situations. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29692?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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: MetaMask Bot commit 500e4a86df8f809dcb2830a99c2388681a44761a Author: Mathieu Artu Date: Tue Jan 14 14:06:01 2025 +0100 fix: condition for signing in a user after onboarding (#29659) ## **Description** This PR fixes a condition that was used to determine if a user should be signed-in at the end of the onboarding process. Users should be signed-in if they either opted in Profile Syncing or MetaMetrics. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29659?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/main/.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/main/.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. commit 08fb039dbc46b8cb038fc14dfd632e6264160d5d Author: Frederik Bolding Date: Tue Jan 14 12:37:22 2025 +0100 fix: Correct Snaps Home scroll behavior (#29660) ## **Description** Fixes the scroll behavior on Snaps home pages following changes to the scroll behavior on the Snap confirmation screens. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29660?quickstart=1) ## **Manual testing steps** 1. Install the home page example Snap 2. View the home page 3. See that the scroll behavior is as expected (no overlap with footer) ## **Screenshots/Recordings** ### **Before** ![image](https://github.com/user-attachments/assets/2cc80d61-f9f7-4408-be1d-8bb70d7098d3) ### **After** ![image](https://github.com/user-attachments/assets/ad65306e-215a-4808-8670-d3c4201fe91c) commit c340f074beac8ffcfaca6eea91124696250bb833 Author: Pedro Figueiredo Date: Tue Jan 14 11:14:46 2025 +0000 fix: oldestPendingApproval and transactionsMetadata PropTypes in the … (#29689) …routes file ## **Description** There are two relatively new proptypes warnings that I included below. This is because `transactionsMetadata` is in fact an object, not an array of objects, and oldestPendingApproval can be undefined. These PropTypes were introduced in https://github.com/MetaMask/metamask-extension/pull/28301. This PR updates both PropTypes definitions to avoid any more warnings. ``` Warning: Failed prop type: The prop `oldestPendingApproval` is marked as required in `Routes`, but its value is `undefined`. in Routes (created by Connect(Routes)) in Connect(Routes) (created by Context.Consumer) in withRouter(Connect(Routes)) (at pages/index.js:57) in MetamaskNotificationsProvider (at pages/index.js:56) in MetamaskIdentityProvider (at pages/index.js:55) in AssetPollingProvider (at pages/index.js:54) in LegacyI18nProvider (at pages/index.js:53) in I18nProvider (at pages/index.js:52) in LegacyMetaMetricsProvider (at pages/index.js:51) in MetaMetricsProvider (at pages/index.js:50) in RenderedRoute (created by Routes) in Routes (created by CompatRouter) in Router (created by CompatRouter) in CompatRouter (at pages/index.js:49) in Router (created by HashRouter) in HashRouter (at pages/index.js:48) in Provider (at pages/index.js:47) in Index (at ui/index.js:213) Warning: Failed prop type: Invalid prop `transactionsMetadata` of type `object` supplied to `Routes`, expected an array. in Routes (created by Connect(Routes)) in Connect(Routes) (created by Context.Consumer) in withRouter(Connect(Routes)) (at pages/index.js:57) in MetamaskNotificationsProvider (at pages/index.js:56) in MetamaskIdentityProvider (at pages/index.js:55) in AssetPollingProvider (at pages/index.js:54) in LegacyI18nProvider (at pages/index.js:53) in I18nProvider (at pages/index.js:52) in LegacyMetaMetricsProvider (at pages/index.js:51) in MetaMetricsProvider (at pages/index.js:50) in RenderedRoute (created by Routes) in Routes (created by CompatRouter) in Router (created by CompatRouter) in CompatRouter (at pages/index.js:49) in Router (created by HashRouter) in HashRouter (at pages/index.js:48) in Provider (at pages/index.js:47) in Index (at ui/index.js:213) ``` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29689?quickstart=1) ## **Related issues** Fixes: N/A ## **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/main/.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/main/.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. commit 0f01f6fdba073b99ebe9fffdef42a9d47ec864d8 Author: Xiaoming Wang <7315988+dawnseeker8@users.noreply.github.com> Date: Tue Jan 14 18:39:45 2025 +0800 fix: upgrade @metamask/eth-ledger-bridge-keyring library to latest (#29687) I dont know why the previous PR didnt include @metamask/eth-ledger-bridge-keyring` library upgrade which will contain the latest parameter typed information for signEIP712Message. This PR wll upgrade the libraray to latest to fix the issue in EIP712 typed sign issue. This PR also fix the lido stacking blind signing issue. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29687?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. change the network to `ETh mainnet` in metamask, and select your ledger account as connected account 2. load the `https://clear-signing-tester.vercel.app/` dapp for testing the clear signing 3. click `connect` to connect dapp with your ledger account 4. Go to `EIP712` section in the dapp 5. Test both `USDC Permit on mainnet` and `Uniswap X on mainnet` 6. click `send` button to trigger the sign 7. your ledger devices sould not display any `blind signing warning.` ## **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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: MetaMask Bot commit e356f86ea54cfd201694cc9902fa2d9c65443a6f Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Mon Jan 13 12:57:31 2025 -0800 refactor: NFT Grid View (#29445) ## **Description** Refactors the NFT experience into a grid view. This changes the way that we are logically considering NFTs in that they are no longer sublisted by category, but are rather displayed in a flat grid. They should however, still be sorted by default by the NFT collection, meaning that NFTs from the same collection should appear next to eachother in the grid. More sorts and filters will be added incrementally. Also extends an initial privacy mode implementation onto NFT grid, to quickly show/hide NFTs. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29445?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-247 ## **Manual testing steps** 1. Ensure that NFTs render in a grid, and are responsive to different screen sizes. 2. Ensure that grid looks good on extension view. When viewing and sending NFT 3. Ensure that NFT detail view looks on par with existing implementation 4. Display NFT Media and IPFS gateway settings should remain unchanged, and should behave nicely with privacy mode. ## **Screenshots/Recordings** ### **Before** Screenshot 2025-01-09 at 6 14 55 PM ### **After** https://github.com/user-attachments/assets/3783a8fc-a7e7-494a-9431-c9fb21e82cb9 https://github.com/user-attachments/assets/b57ab2ee-6981-4710-9ade-ea4998885679 ## **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/main/.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/main/.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. commit 4e32d32994a61f1f8a8839b4ac5b6d19308a53be Author: Bryan Fullam Date: Mon Jan 13 21:41:20 2025 +0100 feat: persist bridge state through ToS click (#29366) ## **Description** Currently, clicking on Terms takes you to an external page and resets your bridge progress. We want to follow the same UX as swaps, where if the user clicks on the ToS link, the extension will close, but when the user reopens the extension, they remain on the bridge page. Acceptance Criteria: When clicking on the ToS link, the extension closes. But when the user re-opens the extension, they see the bridge page with tokens inputted (but not amounts) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29366?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to bridge 2. Put in tokens/amounts to get quote 3. Click "terms" link 4. Reopen extension and see inputs persisted ## **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/main/.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/main/.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. commit 531643c4bd83024d936d7c43fcfab3f0a123d1b2 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Tue Jan 14 05:32:19 2025 +0900 chore: xchain tx details use short names (#29413) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29413?quickstart=1) This PR uses short names for networks to improve spacing. ## **Related issues** Fixes: ## **Manual testing steps** 1. Do a bridge tx 2. Click on bridge tx in Activity 3. Observe shorter names ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-20 at 5 19 46 PM](https://github.com/user-attachments/assets/2fb314ba-a926-4992-91c1-97cda321fae3) ![Screenshot 2024-12-20 at 5 19 54 PM](https://github.com/user-attachments/assets/47eea644-a60f-4a87-b7dd-4fd17ad2609b) ### **After** ![Screenshot 2024-12-20 at 5 20 13 PM](https://github.com/user-attachments/assets/714ffbd6-caa4-4e0f-8e31-248f72e8b6af) ![Screenshot 2024-12-20 at 5 20 20 PM](https://github.com/user-attachments/assets/54dfc06c-c53d-4f1d-86d7-e73335b2f1d9) ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 1e7fe77a46a0bd547ca68fd424f4c688ce7af879 Author: Matthew Walsh Date: Mon Jan 13 20:14:05 2025 +0000 fix (cherry-pick): duplicate content in multiple pending connect confirmations (#29653) (#29670) ## **Description** Cherry-pick of #29653 for release `12.10.0`. ## **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/main/.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/main/.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. commit de10c46b13a2b2bb9f84f5984fa5c3d03beb00b9 Author: hunty Date: Mon Jan 13 13:16:38 2025 -0600 feat: improve xchain swaps slippage settings with decimals and warnings (#29617) ## **Description** The slippage settings for xchain transactions was not allowing users to input a decimal (i.e. 0.5%), only whole numbers (0, 1, so on...). It also did not display a warning for low slippage settings. This addresses both by allowing the user to input decimal numbers, and displaying a warning when the custom slippage is set to <0.5% in the settings modal. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29617?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1830 ## **Manual testing steps** 1. Go to the xchain swaps transaction preparation page. 2. Edit slippage settings. 3. Notice decimals are now allowed, and a warning is displayed with low slippage. ## **Screenshots/Recordings** ### **Before** Screenshot 2025-01-09 at 4 49 04 PM ### **After** Screenshot 2025-01-09 at 2 29 30 PM Screenshot 2025-01-09 at 2 29 22 PM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] 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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit 840f2145fe02017d1f589c7f276a10aff3328ccc Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Mon Jan 13 21:40:04 2025 +0400 chore: clarifying comments for some github actions (#29642) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29642?quickstart=1) This small PR adds some clarifying comments for some GitHub Actions, namely: ``` # For a `pull_request` event, the branch is `github.head_ref``. # For a `push` event, the branch is `github.ref_name`. ``` to make sure the contributor is aware which variable corresponds to which event. No functional changes, only cosmetic. ## **Related issues** Fixes: ## **Manual testing steps** 1. Everything should work the same as before ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 9380040f7e4164d07a59976c914abdbf9da2c850 Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Mon Jan 13 21:38:26 2025 +0400 feat: runway (#29632) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29632?quickstart=1) This PR adds the `Runway` GitHub Action to help upload our builds and releases to Runway for easier release management. As Runway has a GitHub App connected to our repository, it is enough to upload builds as GitHub artifacts, and they will be automatically pulled. Currently the builds are downloaded from CircleCI, so later, once builds are migrated to GitHub Actions, this workflow will need to be updated. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3619 ## **Manual testing steps** 1. Builds should show up in Runway. ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 43979784738121f91b7b56861960bcfed081c451 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Mon Jan 13 10:53:49 2025 -0500 fix: center pixel art in `no-nfts.svg` and remove use of embedded png (#29290) This reduces the size from 5.2 kB to 888 bytes while increasing sharpness and accuracy. See interactive image comparison on the "Files changed" tab: https://github.com/MetaMask/metamask-extension/pull/29290/files#diff-1e5c8159dc8241cb9cf9ed9a9127e7514b2efc1e65aac8e6477353f526c9f1fe commit 482213692b24b4cfb9fa14768475e2533c92b113 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Mon Jan 13 10:53:38 2025 -0500 fix: use correct color for bitcoin logo and remove the svg's embedded PNG (#29292) This change reduces the size from 8.3 kB to 1.2 kB. See interactive image comparison on the "Files changed" tab: https://github.com/MetaMask/metamask-extension/pull/29292/files#diff-3779e0799aea9b65b49651efae6d1c5af9018c75925336039c8c4b81665a9ed0 commit c517bf7ee9c81b72e4972a9b6ee2ee4389986900 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Mon Jan 13 10:53:26 2025 -0500 build: remove unused `info-fox.svg` (#29294) This image isn't used any more. commit 38b7bc3b73d42f789129e7308ae5cbbdb332fc3f Author: Matthew Walsh Date: Mon Jan 13 15:37:37 2025 +0000 fix: duplicate content in multiple pending connect confirmations (#29653) ## **Description** Fix duplicate content when there are multiple pending connect confirmations. Caused by props being copied to state but not updated. ## **Related issues** Fixes: #29594 ## **Manual testing steps** See issue. ## **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/main/.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/main/.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. commit 2b377b31154eb14be81215dfb672a53b43a4857e Author: Jongsun Suh Date: Mon Jan 13 10:13:11 2025 -0500 fix: Move foreground state properties from `metamask` slice to `appState` slice (#28703) ## **Description** 1. Moves properties in the `metamask` Redux slice that are not part of background (i.e. controller-derived) state into the `appState` slice. The affected state properties are as follows: ``` customNonceValue: string; isAccountMenuOpen: boolean; isNetworkMenuOpen: boolean; nextNonce: string | null; pendingTokens: { [address: string]: Token & { isCustom?: boolean; unlisted?: boolean }; }; welcomeScreenSeen: boolean; confirmationExchangeRates: ContractExchangeRates; ``` - Foreground properties that are a part of `AppStateController` state have not been moved into the `appState` slice, and will remain in the unflattened `metamask` slice. - It's not immediately clear why the properties listed above were excluded from `AppStateController` state, but since all of them appear to neither require persistence beyond a given session, nor anonymization of PII, I'm opting to maintain the status quo and keep them out of controller state. - The `isInitialized` property is not included, because it's derived from background state before the Redux store is instantiated, and is updated by the `PatchStore` which supplies it directly to the `metamask` slice. 2. Groups `appState` slice selectors in `ui/selectors/selectors.js` together. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28703?quickstart=1) ## **Related issues** - Closes https://github.com/MetaMask/metamask-extension/issues/29601 - Closes https://github.com/MetaMask/MetaMask-planning/issues/2895 - 0 of 7 PRs that will close https://github.com/MetaMask/metamask-extension/issues/29600 - Blocking https://github.com/MetaMask/MetaMask-planning/issues/2894 - https://github.com/MetaMask/metamask-extension/pull/28723 - https://github.com/MetaMask/metamask-extension/pull/28929 - https://github.com/MetaMask/metamask-extension/pull/28776 ## **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. commit 9344d0a057f31f87a62e3fa97b8e2793cade2abb Author: Mark Stacey Date: Mon Jan 13 11:35:57 2025 -0330 fix: Fix LavaMoat build failures and restore RegExp OOM mitigation (#29637) ## **Description** LavaMoat was updated recently, but the patch we had of `lavamoat-core` was not updated with it. This restores the changes made in that patch. Note that the old patch still exists because we're still using the older version of `lavamoat-core` for the `lavamoat-viz` tool. The patch had three changes; the third was upstreamed already so it was not required, but the first two (RegExp cache and skipping policy write) were still required. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29637?quickstart=1) ## **Related issues** Fixes #29482 ## **Manual testing steps** There is no consistent way to test the intermittent build failure issue, though we could try running this over and over. For the RegExp cache, we never had a clear reproduction for the OOM error that was meant to mitigate either. ## **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/main/.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/main/.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 commit a55c0ce3a2f3f45705d6a441b8313795611f08ec Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Mon Jan 13 23:24:51 2025 +0900 fix: crashing after bridge tx in Firefox (#29631) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29631?quickstart=1) This PR fixes an issue in Firefox only where the Extension would crash after submitting a bridge tx and would not be able to recover after a restart, forcing the user to wipe the Extension completely to fix it. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29630 ## **Manual testing steps** 1. If you have experienced this crash in FF before, start from a fresh install of the Extension 2. Submit a bridge tx 3. The app should not crash ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit b91a9622393f37b79a63ba4a240cda7efa3cc5bc Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Mon Jan 13 08:48:50 2025 +0000 fix: change alert modal copy (#29475) ## **Description** This PR aims to update the copy of the alert modal (description). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29475?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3731 ## **Manual testing steps** You need to trigger a Danger alert (resimulation, domain mismatch or signing or Submitting) 1. Go to this the Test Dapp 2. Trigger a request 3. Acknowledge the Danger alert ## **Screenshots/Recordings** ![image](https://github.com/user-attachments/assets/2908742f-fed8-45ec-9d1f-cb5a5ebf3116) ### **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/main/.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/main/.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. commit c067a247a3defb5a8db32a4c41615ab351a946a6 Author: Marina Boboc <120041701+benjisclowder@users.noreply.github.com> Date: Fri Jan 10 21:09:49 2025 +0200 chore: V12.10.0 changelog (#29635) ## **Description** RC 12.10.0 Changelog [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29635?quickstart=1) commit b2c53144a0e67971b625b4f9d430aeba40f11c87 Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Fri Jan 10 21:46:34 2025 +0400 fix: metamaskbot comment nits (#29636) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29636?quickstart=1) This PR adds minor fixes and enhancements to things that were not worth blocking the original metamaskbot PR for. Things like, comments, style, reusability should be improved by this PR. Interestingly, I also saw that the `$OWNER` environment variable was missing, but somehow the workflow still worked. I added this environment variable just to make it sane. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28572 ## **Manual testing steps** 1. Everything should still work ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit f58258b7ac25137c8e3155e60c6620b206a10f9f Author: jiexi Date: Fri Jan 10 07:51:34 2025 -0800 refactor: remove unused end param in ethereum-chain-util helpers (#29619) ## **Description** Removes unused `end` param in the ethereum-chain-util helpers * validateChainId * validateAddEthereumChainParams * validateSwitchEthereumChainParams [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29619?quickstart=1) Extending E2E timeout to get past "no timings found" error: ``` flags = { "circleci": { "timeoutMinutes": 30 } } ``` ## **Related issues** See: https://github.com/MetaMask/metamask-extension/pull/27847#discussion_r1907939905 ## **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/main/.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/main/.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. commit 1df79a9306a84319121493f1f00605dc9d987713 Author: Mark Stacey Date: Fri Jan 10 12:14:11 2025 -0330 chore: Remove obsolete keys (#29372) ## **Description** Remove references to obsolete keys. * PUBNUB_*: These were used for the old mobile state sync feature, which was removed a long time ago. * ETHERSCAN_KEY: This was used for incoming transactions, but we've since switched to our own API. * OPENSEA_KEY: We've switched to Reservoir The `ETHERSCAN_KEY ` environment variable is still refrenced in the `gridplus-sdk` package, so it has been left in the `build.yml` file as `null` to indicate that it's intentionally unset. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29372?quickstart=1) ## **Related issues** N/A ## **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/main/.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/main/.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. commit 3ff4aba94aacf6a46488735c7656e6a1b7c1cdc2 Author: OGPoyraz Date: Fri Jan 10 15:35:34 2025 +0100 fix: Remove unwanted empty `div` from signature confirmations (#29622) ## **Description** https://github.com/MetaMask/metamask-extension/pull/28854/files introduced an unwanted empty div on top of signature content. This PR aims to relocate that into child component. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29622?quickstart=1) ## **Related issues** ## **Manual testing steps** N/A ## **Screenshots/Recordings** ### **Before** Screenshot 2025-01-10 at 09 59 45 ### **After** Screenshot 2025-01-10 at 09 58 38 ## **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/main/.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/main/.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. commit dd26784d605c92cad0bbce04985724c9b1c088c7 Author: Pedro Figueiredo Date: Fri Jan 10 14:27:07 2025 +0000 feat: Nonce is always editable in advanced details view (#29627) ## **Description** Previously, the nonce can only be edited if the option to do it is enabled in the corresponding settings toggle. This PR removes the link with that toggle in the redesigned transaction screens. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29627?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29512 ## **Manual testing steps** 1. Go to the test dApp 2. Disable `Advanced > Customize transaction nonce` in settings 3. Create a send eth transaction 4. Toggle on advanced details 5. The edit nonce icon should be visible in the confirmation screen ## **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/main/.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/main/.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. commit 5778b4a64a2b7aeba40c46e0afb295d214b36355 Author: Pedro Figueiredo Date: Fri Jan 10 12:18:33 2025 +0000 feat: Display clickable cursor on hover on petname component (#29477) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29477?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/2340 ## **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/main/.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/main/.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. commit 64400d8f15db2abb0995ce43dd5a140b58841309 Author: Mathieu Artu Date: Fri Jan 10 11:35:48 2025 +0100 chore: bump `@metamask/profile-sync-controller` to `v3.2.0` (#29598) ## **Description** This PR bumps `@metamask/profile-sync-controller` to `v3.2.0`. This will add better error logging related to all things authentication & profile syncing [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29598?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/main/.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/main/.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. commit 46bf1cfc4332d0cdda1091e4d69ccacfc3c2d596 Author: jiexi Date: Thu Jan 9 15:11:18 2025 -0800 test: Remove obsolete permitted chains feature flag tests (#29618) ## **Description** Removes tests that are no longer applicable pertaining to permitted chains. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29618?quickstart=1) ## **Related issues** See: https://github.com/MetaMask/metamask-extension/pull/27847#discussion_r1908944940 ## **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/main/.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/main/.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. commit edaae773872e404ddedd460c6ff423eaea35378b Author: Mark Stacey Date: Thu Jan 9 19:05:04 2025 -0330 fix: Fix bundle size tracking (#29486) ## **Description** The bundle size tracking was accidentally broken in #29408. This restores the bundle size tracking. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29486?quickstart=1) ## **Related issues** Fixes #29485 Resolves bug introduced by #29408 ## **Manual testing steps** 1. Check the "mv3: Bundle Size Stats" link in the `metamaskbot` comment 2. Once this is merged, check the bundle size tracker to ensure it's working again: https://github.com/MetaMask/extension_bundlesize_stats * Unfortunately I am not sure how to easily test this on the PR. The tracker is only updated when commits are made to `main`. ## **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/main/.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/main/.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. commit b5f4ce18f7a5028ae35dd777615cf1cda6a25dcb Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Fri Jan 10 05:36:03 2025 +0900 cherrypick fix: xchain linea bugs (#29409) (#29511) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29409?quickstart=1) This PR cherry picks [481505f](https://github.com/MetaMask/metamask-extension/commit/481505f692bccd7bb2f4f78d0f40d4b7c1799b13) into 12.10.0 RC. This fixes the bug: https://github.com/MetaMask/metamask-extension/issues/29591 This PR fixes a couple of issues and does the following: 1. Shows a loading spinner on the Bridge button after submitting 2. Does not wait 5 sec to submit a bridge tx on Linea if there is no approval tx ## **Related issues** Fixes: ## **Manual testing steps** Gas token 1. Start a bridge from Linea to any chain for ETH 3. Observe that you are redirected to Activity page almost instantly ERC20 1. Start a bridge from Linea to any chain for an ERC20 2. Observe that a loading spinner shows on the button 4. Observe you are redirected to Activity page after 5 sec ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/24445f52-ddaf-4d08-b3ce-755c10b3f976 https://github.com/user-attachments/assets/ae93204d-80f6-4beb-aff5-b8274e4541e7 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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/29511?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/main/.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/main/.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. commit 36fc9598623b3d8c1e2bd906c3983b325c553945 Author: MetaMask Bot Date: Thu Jan 9 20:28:17 2025 +0000 Update Attributions commit 87c524e4a34749cff8c94cc43c1cee8d83ff107c Author: Mark Stacey Date: Thu Jan 9 14:19:56 2025 -0330 ci: Migrate metamaskbot PR comment (#29373) ## **Description** Migrate the `metamaskbot` PR comment from CircleCI to GitHub Actions. CircleCI is still used for artifact storage for linked artifacts. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29373?quickstart=1) ## **Related issues** Relates to #28572 These changes were extracted from #29256 ## **Manual testing steps** * Test that all links in the `metamaskbot` comment work correctly, except those that are already broken. Links already broken on `main` include: * Some build links (fixed in #29403 ): - Beta builds - Firefox test build, and Firefox flask-test build * MV3 performance stats reports (fixed in #29408 ): * mv3: Background Module Init Stats (link works, page is broken) * mv3: UI Init Stats (link works, page is broken) * mv3: Module Load Stats (link works, page is broken) * Coverage report (fixed by #29410) * mv3 bundle size stats (fixed by #29486) ## **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/main/.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/main/.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. commit 2d443ffef31e826afa7bed96766fc9f084655365 Author: Xiaoming Wang <7315988+dawnseeker8@users.noreply.github.com> Date: Fri Jan 10 00:41:13 2025 +0800 feat: Enable Ledger clear signing feature (#28909) ## **Description** This PR enable the clear signing feature in metamask mobile. Please refer to this feature requests for detail: https://github.com/MetaMask/accounts-planning/issues/544 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28909?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/544 ## **Manual testing steps** - Test the clear signing using this dapp provided by ledger team: https://clear-signing-tester.vercel.app/ - EIP712 sign message (Polygon mainnet or Ethereum mainnet). - Sign transaction (Linea testnet). - Swap in a layer 2 chain like Linea or Polygon. ## **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/main/.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/main/.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: Sébastien Van Eyck Co-authored-by: MetaMask Bot commit 4cbce35280445237862f064dcb4f07d2c12637f9 Author: Alex Donesky Date: Thu Jan 9 10:18:59 2025 -0600 chore: Remove toggle to turn on/off Per Dapp Selected Network Feature (#29301) ## **Description** The "Select networks for each site" preference toggle on the experimental settings page has been live for many releases now since the toggle has been turned on by default. We meant to remove it a while ago. Screenshot 2024-11-19 at 10 58 04 AM This PR removes this toggle ~and integrates new versions of the QueuedRequestController and SelectedNetworkController which remove the backend logic it operated.~ - We have delayed the updates to the controllers side because of some mobile side requirements. See [this PR](https://github.com/MetaMask/core/pull/5065#issue-2736965186) for more context: > We are not yet ready to release per-dapp selected network functionality on the mobile client and with this change there is no clean way to https://github.com/MetaMask/metamask-mobile/issues/12434#issuecomment-2537358920 in the mobile client without having the Domains state starting to populate and possibly become corrupt since its not being consumed by/updated by the frontend in the expected way and may need to be migrated away when its time to actually start using the controller. > Without this revert the @MetaMask/wallet-framework team is blocked from completing their goal to get both clients up to the latest versions of all controllers. Beyond the fact that this removal is overdue, another reason we should remove this now is that having this setting when turned off is [causing a bug](https://github.com/MetaMask/metamask-extension/issues/28441) with `wallet_switchEthereumChain` and the interaction with the new chain permissions feature. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/2844 ## **Manual testing steps** 1. Go to experimental tab of settings 2. See that there is no longer a toggleable preference called "Selected Networks for each site" 3. See that Per Dapp Selected Network Functionality is still on by default ## **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/main/.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/main/.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. commit 6e05923cb64e689628697ac05cbee8c86d7b12e2 Author: Pedro Figueiredo Date: Thu Jan 9 16:04:20 2025 +0000 fix: Catch errors from the assets controller (#29439) ## **Description** This PR addresses a group of errors reported on Sentry that occur when `getTokenStandardAndDetails` in `assetsContractController` returns "Unable to determine contract standard". [Sentry Issue Link](https://metamask.sentry.io/issues/5660074561/?project=273505&query=is%3Aunresolved%20getTokenStandardAndDetails&referrer=issue-stream&sort=date&statsPeriod=7d&stream_index=0). The error happens fairly often because only contracts that strictly adhere to the standard ABIs for ERC20, ERC721, and ERC1155 are correctly parsed. When a contract does not conform to these standards, the function fails and throws an error. This PR introduces error handling to catch and silence the error when assetsContractController.getTokenStandardAndDetails fails to determine the contract standard. The control flow of the function is adjusted to ensure it continues to execute normally even when the error is caught. ### Changes - Added a try...catch block around the assetsContractController.getTokenStandardAndDetails call to catch and log the error. - Ensured that the function continues to execute and return appropriate details even when the error is caught. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29439?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/25212 ## **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/main/.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/main/.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. commit a36a42c6963482b8f8e9c7679342ed89bb8fd511 Author: Monte Lai Date: Thu Jan 9 21:40:23 2025 +0800 fix: show localized snap name in snap tag (#29049) ## **Description** This PR fixes the issue where the localized snap name is not being used when in the account tag. It also removes tech debt which is the `mergeAccount` function that isn't needed any more. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29049?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/769 ## **Manual testing steps** 1. Install the BTC snap 2. Create a BTC account 3. Click on the account menu and see that the name is correct. ## **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/main/.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/main/.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. commit 3154019858c172231111698f519b19577fc1e2f6 Author: Guillaume Roux Date: Thu Jan 9 14:25:56 2025 +0100 fix(snaps): Scrollbar being partially hidden behind footer (#29435) ## **Description** This fixes the scrollbar in Snap dialogs being partially hidden behind the footer. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29435?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ![image](https://github.com/user-attachments/assets/66cf18d5-4e0c-49b4-ad44-353384404be8) ![image](https://github.com/user-attachments/assets/81df4231-7b23-4377-b6fe-e18e829d50dd) ### **After** ![image](https://github.com/user-attachments/assets/6390d11c-223e-4e73-8f8e-3f361e0c069f) ![image](https://github.com/user-attachments/assets/269ac240-a7e0-4716-844d-680f2389166f) ## **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/main/.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/main/.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. commit c16460e7078a0510bd018768df4ad4f2ba5dfd8e Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Thu Jan 9 14:22:41 2025 +0100 test: [POM] Migrate connections e2e tests to TS and Page Object Model (#29384) ## **Description** - Migrate connections e2e tests to TS and Page Object Model ``` test/e2e/tests/connections/edit-account-flow.spec.ts test/e2e/tests/connections/edit-networks-flow.spec.ts ``` - Remove the spec `test/e2e/tests/connections/connect-with-metamask.spec.js` as it's already completely tested in another spec. We don't want to repeat testing. - create more class methods for permission page class [![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/29440 ## **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. commit 93b1e13410a19bf3659e6dbb12f9b614852472da Author: Priya Date: Thu Jan 9 14:08:28 2025 +0100 chore: fix flaky e2e for nft token send (#29476) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29476?quickstart=1) ## **Related issues** Fixes: [#29382](https://github.com/MetaMask/metamask-extension/issues/29382) ## **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/main/.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/main/.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. commit 00a5db79f52c2e179f70561a72b2ea3463ba7f88 Author: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Thu Jan 9 12:50:38 2025 +0100 chore(deps): bump `@metamask/eth-trezor-keyring` to `^6.0.0` (#27689) ## **Description** This PR bumps `@metamask/eth-trezor-keyring` from `^3.1.3` to `^6.0.0`. ``` ## [6.0.0] ### Added - **BREAKING:** Add ESM build ([#40](https://github.com/MetaMask/accounts/pull/40)) - It's no longer possible to import files from `./dist` directly. ## [5.0.0] ### Changed - **BREAKING**: Bump `@metamask/eth-sig-util` dependency from `^7.0.3` to `^8.0.0` ([#79](https://github.com/MetaMask/accounts/pull/79)) - `signTypedData` no longer support `number` for addresses, see [here](https://github.com/MetaMask/eth-sig-util/blob/main/CHANGELOG.md#800). ## [4.0.0] ### Changed - **BREAKING**: `addAccounts` will now only return newly created accounts ([#64](https://github.com/MetaMask/accounts/pull/64)) - This keyring was initially returning every accounts (previous and new ones), which is different from what is expected in the [`Keyring` interface].(https://github.com/MetaMask/utils/blob/v9.2.1/src/keyring.ts#L65) ``` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27689?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** These changes directly impact Trezor devices: 1. Add one or more Trezor accounts 2. Sign message 3. Sign typed data 4. Sign transaction 5. Remove Trezor accounts ## **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. commit ec75a8b61240697c4a0857d3f33a212474ef21fd Author: Priya Date: Thu Jan 9 09:11:53 2025 +0100 test: fix flaky snap signature test (#29480) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29480?quickstart=1) ## **Related issues** Fixes: [#29380](https://github.com/MetaMask/metamask-extension/issues/29380) ## **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/main/.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/main/.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. commit ed0362c167323f9e7c079a65a784315cc4cb7f8f Author: David Drazic Date: Wed Jan 8 20:58:14 2025 +0100 fix(snaps): Ensure that adjacent form elements take up to 50% width (#29436) ## **Description** Add some SCSS changes to ensure that adjacent form elements take up to 50% width in Snaps custom UI. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29436?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/snaps/issues/2948 ## **Manual testing steps** 1. Install a Snap with custom UI with adjacent form elements like in the example code provided. 2. Make sure that adjacent form elements take up to 50% of the space available. Snaps JSX used for testing: ```typescript
Option 1 Option 2 Option 3 Option 11 Option 22 Option 33
``` ## **Screenshots/Recordings** ### **Before** ![Screenshot 2025-01-06 at 13 16 47](https://github.com/user-attachments/assets/2c1ade00-a5e3-42e8-8619-e6d59262c1fb) ### **After** ![Screenshot 2025-01-08 at 13 45 12](https://github.com/user-attachments/assets/805e67aa-8b43-43fb-a0b5-a0bcbc038d05) ![Screenshot 2025-01-08 at 13 45 31](https://github.com/user-attachments/assets/a7685bc8-c014-41ee-acaa-6987da4c7bb1) ![Screenshot 2025-01-08 at 13 46 51](https://github.com/user-attachments/assets/ef2670a8-1c68-43e3-ae6b-0fcb6ebe9d39) ![Screenshot 2025-01-08 at 13 47 05](https://github.com/user-attachments/assets/585bba34-eaaf-4ddc-9634-a6226f5b5969) ## **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/main/.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/main/.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. commit ae565bc4518fd3d0ee3831088dfddbbb0fe266c1 Author: Zbyszek Tenerowicz Date: Wed Jan 8 19:45:53 2025 +0100 chore: add the new policy.json review process documentation and config (#29383) ## **Description** Introducing the document outlining policy review process. The current change to CODEOWNERS doesn't remove the all devs group - this makes it a soft-launch of the process where a specific approving review is not yet mandatory. We'll be releasing a training video early January. After merging this, I'm hoping to reference the document in multiple places to aid people in discovering and following the process. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29383?quickstart=1) ## **Related issues** Fixes: lack of attention to policy updates ## **Manual testing steps** 1. Follow the process and experience joy ## **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/main/.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/main/.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: Mark Stacey commit 7a4fa94a26874bbf743199f034e056a0be349360 Author: Frederik Bolding Date: Wed Jan 8 14:40:37 2025 +0100 cherry-pick: Correct theme value for Snap UI footer buttons (#29495) ## **Description** Cherry-pick https://github.com/MetaMask/metamask-extension/commit/1fbb63cc4e2c15ffc344155f8fa6e6f240d8dd74 to the RC. The Snap UI footer buttons use the DS button which forces light theme, this does not work for the colors used in the Snap buttons, therefore we revert back to using the actually selected theme. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29434?quickstart=1) commit 20b417c84f7d9d29593f89d7eeb367261fb8f669 Author: Daniel <80175477+dan437@users.noreply.github.com> Date: Wed Jan 8 14:37:34 2025 +0100 fix: Bump smart-transactions-controller to ^16.0.1 (#29478) ## **Description** Release notes for 16.0.1: https://github.com/MetaMask/smart-transactions-controller/releases/tag/v16.0.1 ## **Related issues** Fixes: ## **Manual testing steps** ## **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/main/.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/main/.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. commit 78c00b3f4501ddfe598b7bbe7a3796ebfea6a59f Author: Salim TOUBAL Date: Wed Jan 8 14:13:14 2025 +0100 fix: add kaia network logo (#29494) ## **Description** This PR is to update the Klaytn details to Kaia because of rebranding changes of the network. Please find more details here. https://www.binance.com/en/support/announcement/binance-will-support-the-kaia-klay-rebranding-to-kaia-kaia-f75f933759ee49d0af1dfbce7e32144c?hl=en https://medium.com/klaytn/say-hello-to-kaia-4182ccafe456 https://kaia.io [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27383?quickstart=1) ## **Related issues** https://github.com/MetaMask/metamask-extension/issues/27382 Fixes: ## **Manual testing steps** 1. Go to this networks section. 2. Add kaia network from 3. https://chainlist.org/?search=kaia 4. Currently it shows the Klaytn logo and also shows for KAIA ticker. ## **Screenshots/Recordings** ### **Before** Screenshot 2024-09-25 at 2 59 30 PM ### **After** Once updated it should show with new Kaia logo. ## **Pre-merge author checklist** - [ X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [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. commit 0861cb8775f0e9cc1384af16a7e8d9ffbed94e01 Author: Salim TOUBAL Date: Wed Jan 8 13:25:31 2025 +0100 fix: add lnk network logo (#29493) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29368?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Add Ink network according to (network information)[https://docs.inkonchain.com/general/network-information] and ensure ink.svg is displayed properly 2. Add Ink Sepolia network according to (network information)[https://docs.inkonchain.com/general/network-information] and ensure ink-sepolia.svg is supported ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-19 at 3 35 46 PM](https://github.com/user-attachments/assets/4ba64987-ba8b-49c4-af10-4c0e9f3e1e4a) ### **After** ![Screenshot 2024-12-19 at 3 35 12 PM](https://github.com/user-attachments/assets/cd27a1ed-cbd5-4206-8431-a759bffc6002) ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 36c5fbfacc14aab3d8734c46eae5b990f8763645 Author: Frederik Bolding Date: Wed Jan 8 12:31:48 2025 +0100 fix: Bump Snap UI selector `min-height` (#29496) ## **Description** Bump `min-height` of the Snaps UI selector component to be at least as tall as the Snaps UI input. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29496?quickstart=1) ## **Screenshots/Recordings** ### **Before** ![image](https://github.com/user-attachments/assets/3051b905-9f77-4b27-836b-3baf1db40725) ### **After** ![image](https://github.com/user-attachments/assets/a36dbb3c-7e62-4a8d-a65b-8204a4ab05d2) commit 7fe00dc2803b845118f48c12232edfe0f447bc47 Author: cmd-ob Date: Wed Jan 8 10:05:38 2025 +0000 test: [POM] Migrate token tests (#29375) ## **Description** * Updates `asset-list` Page Object - Adds methods for interacting with token list (i.e sorting, getting list, assertion on increase/decrease price and percentage) * Adds new method for importing a custom token using contract address * Adds new method for adding multiple tokens by search in one step * Minor update to `send-token` page for warning message * New page object for `token-overview` * Tests Updated - `import-tokens`, `send-erc20-to-contract`, `token-list` and `token-sort` ## **Related issues** Fixes: ## **Manual testing steps** * All tests must pass on CI ## **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/main/.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/main/.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: Chloe Gao Co-authored-by: chloeYue <105063779+chloeYue@users.noreply.github.com> Co-authored-by: MetaMask Bot commit e5ff4713aa1f84fc6bb0121416fda693f74448b8 Author: Jyoti Puri Date: Wed Jan 8 15:23:56 2025 +0530 feat: Adding more metrics parameters for signature decoding (#29197) ## **Description** Adding more parameters to signature decoding metrics: 1. decoding_latency - this should capture the amount time it takes to display the decoding results to users. This is similar to the existing simulation_latency property for simulations. 2. decoding_description - this should capture any error message sent along by the API. This is similar to how security_alerts_description property works for blockaid. 3. decoding_in_progress - this value should be passed to decoding_response property when the user approves or rejects the signature request before we display the decoding output. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3778 ## **Manual testing steps** 1. Enable metrics locally, go to test dapp 2. Submit permit sign 3. Check metrics for signature ## **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/main/.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/main/.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. commit 40417b086aa97bff50a6cb01ba7ef8eb7130d424 Author: Davide Brocchetto Date: Wed Jan 8 09:53:28 2025 +0100 test: Swap tests addition (#29442) ## **Description** Adding Linea Tests as well as test for getting a quote on Mainnet. To run the tests locally use `yarn playwright test --project=swap` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29442?quickstart=1) ## **Related issues** Fixes: ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 8d9fa1aa56b259b29df8ef2266e8c657b03f9ed0 Author: Jyoti Puri Date: Wed Jan 8 11:32:58 2025 +0530 fix: Fixes in NFT listing label and values (#29046) ## **Description** Fixes in NFT listing: 1. Fix order of listing state changes 2. Change label from `You receive` to `Listing price` 3. Show received value in gray background ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28977 ## **Manual testing steps** 1. Submit NFT listing confirmation 2. Check the confirmations page 3. Ensure that order of state changes is correct and label is also correct ## **Screenshots/Recordings** Screenshot 2024-12-10 at 4 03 42 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit dabf1732e2a5448cc9935bc48aa9d34c8f7378b8 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed Jan 8 06:19:39 2025 +0900 chore: remove second inner scroll bar from tx details (#29412) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29412?quickstart=1) This PR removes the 2nd inner scrollbar from Bridge tx details. ## **Related issues** Fixes: ## **Manual testing steps** 1. Do a bridge tx 2. Get navigated to Activity list 3. Click on a bridge tx 4. Observe only 1 scroll bar ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-20 at 5 03 15 PM](https://github.com/user-attachments/assets/52904de2-7264-4959-bf23-6301eb1b7000) ### **After** ![Screenshot 2024-12-20 at 5 19 06 PM](https://github.com/user-attachments/assets/744401eb-80bf-4726-8ee4-844068a54978) ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit d9129d17ddfc55bfe5975915f08d663ae2d3584d Author: sahar-fehri Date: Tue Jan 7 22:07:44 2025 +0100 fix: import all detected tokens automatically (#29357) ## **Description** Now that we have most users autodetect tokens, we can make the UX even more streamlined. This PR automatically add in tokens we detect on behalf of users. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29357?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-439 ## **Manual testing steps** 1. import an account that have tokens 2. Notice that tokens are imported automatically Make sure the token detection is working properly: 1. Go to settings and turn off "autodetect tokens" setting 2. Add a new account that have tokens 3. Notice tokens are not added unless you turn the setting back ON ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/3fc491d0-c7ae-48da-ae3d-a0a6e4af0f62 ### **After** https://github.com/user-attachments/assets/61938975-a476-4fd6-a4cf-183c5fb8d96e ## **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/main/.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/main/.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. commit 136b5a87518e551bf56f30c36b3c1382fb28cc55 Merge: 8a0684371f e665f5e18f Author: Dan J Miller Date: Tue Jan 7 16:10:28 2025 -0330 Merge pull request #29484 from MetaMask/cp-Version-v12.10.0-merge-master1 Merge master into v12.10.0 commit e665f5e18fa814e8ef178e9db18d4c287c9df486 Author: Derek Brans Date: Tue Jan 7 13:56:47 2025 -0500 lint changelog commit f4f82ecb350e7a4876bc7429899e0b8640baf225 Author: Derek Brans Date: Tue Jan 7 13:50:42 2025 -0500 lint package.json commit 32529a0a1c2d63010e0e746db151428d16255701 Author: Derek Brans Date: Tue Jan 7 13:45:44 2025 -0500 fix changelog commit 3e6410225026c26661e1f807fe069da00eaa5b03 Author: Derek Brans Date: Tue Jan 7 13:40:30 2025 -0500 fix bad merge in changelog commit bd751988df9e80b74b7d10900dda67985a51e596 Merge: 8a0684371f 2e0fe07f0e Author: Derek Brans Date: Tue Jan 7 13:35:09 2025 -0500 Merge remote-tracking branch 'origin/master' into Version-v12.10.0-merge-master1 commit 9deeaddf486dc834cdc3636fa0e6401fb7663a61 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed Jan 8 02:30:47 2025 +0900 fix: hide you received until bridge tx done (#29411) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29411?quickstart=1) This PR fixes an issue where the "You Received" row in the Activity item for a bridge tx would be partially filled in. Now we just hide it until the bridge tx completes and we have all the necessary data to display the row properly. ## **Related issues** Fixes: ## **Manual testing steps** 1. Start a bridge tx 2. Get navigated to Activity list 3. Click on bridge tx 4. Observe that "You received" is not present until the bridge is completed. ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/e8c3eb2e-9fd7-429b-a119-319defc42bda ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit d0775ad14d990e3c4fc53698582dce5c37df20ad Author: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue Jan 7 08:51:35 2025 -0800 fix: 'Incomplete Asset Displayed' called excessively via useTrackERC20WithoutDecimalInformation + clean: token details logic (#29320) ## **Description** Fixes and cleanup around confirmation: - useTrackERC20WithoutDecimalInformation should only call trackEvent once per use - useGetTokenStandardAndDetails should consistently call useAsyncResult by removing early return - add missing tokenId dep prop to PermitSimulationValueDisplay [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29320?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3362 ## **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/main/.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/main/.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. commit 481505f692bccd7bb2f4f78d0f40d4b7c1799b13 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed Jan 8 00:46:48 2025 +0900 fix: xchain linea bugs (#29409) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29409?quickstart=1) This PR fixes a couple of issues and does the following: 1. Shows a loading spinner on the Bridge button after submitting 2. Does not wait 5 sec to submit a bridge tx on Linea if there is no approval tx ## **Related issues** Fixes: ## **Manual testing steps** Gas token 1. Start a bridge from Linea to any chain for ETH 3. Observe that you are redirected to Activity page almost instantly ERC20 1. Start a bridge from Linea to any chain for an ERC20 2. Observe that a loading spinner shows on the button 4. Observe you are redirected to Activity page after 5 sec ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/24445f52-ddaf-4d08-b3ce-755c10b3f976 https://github.com/user-attachments/assets/ae93204d-80f6-4beb-aff5-b8274e4541e7 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 1ed30c576439bc369d29d0e32375cff180af2800 Author: Mark Stacey Date: Tue Jan 7 11:41:23 2025 -0330 ci: Fix `metamaskbot` comment test build links (#29403) ## **Description** Various build links in the `metamaskbot` PR comment were broken. All links have been fixed except beta (which is trickier to fix, requires more substantial changes to the beta workflows, tracked as #29404). The beta link has been removed until we can fix it. Additionally, the Firefox MMI build link has been removed (this build does not work) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29403?quickstart=1) ## **Related issues** Fixes: #29402 ## **Manual testing steps** * Test that all build links work ## **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/main/.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/main/.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. commit 80e6e147b52c478e893dc3a046492bb908f81426 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Tue Jan 7 15:42:43 2025 +0100 fix: Add missing allowed action to the `SmartTransactionsController` messenger (#29473) Add missing allowed action `NetworkController:getState` to the SmartTransactionsController messenger. This fixes and error in smart transaction publish hook, falling back to regular transaction submission `Error: Action missing from allow list: NetworkController:getState`. ![image](https://github.com/user-attachments/assets/952d8ae3-c13c-4293-b096-944b0c06c30a) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29473?quickstart=1) ## **Related issues** Fixes: ## **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/main/.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/main/.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. commit 41930af0408c73d2f4bc4ed3bfa5c3cfcf792fe5 Author: Eric Bishard Date: Tue Jan 7 07:37:44 2025 -0500 feat: enable STX by default with migration and notification (#28854) ## **Description** This PR enables Smart Transactions (STX) by default through migration number 135 for users who have either opted out or haven't interacted with the STX toggle, provided they have no recorded STX activity. How it works: - Upon Migration 135, alert displays on transaction confirmations: - Legacy transaction flow - New transaction flow (experimental) - Swaps confirmation flow - Contract deployment - Contract interactions (minting, etc.) In the case a user migrates from a previous version of the extension and the migration runs and sets STX toggle "ON" in `Settings > Advanced > Smart Transactions`, they will receive an STX Banner Alert on transaction confirmation screens until dismissed through a close button, or by clicking on the "Higher success rates" link within the alert that goes to: [What is 'Smart Transactions'?](https://support.metamask.io/transactions-and-gas/transactions/smart-transactions/) for more information. Edge Cases: If a user is new and setting up a wallet for the first time, they will not receive the Banner Alert. If a user imports a new wallet during a fresh install of the extension on a new browser or recovers a wallet, it's possible they may not see the alert if STX was on in a previous install. The STX Banner Alert is dismissed and will not show again if a user is in the state to get shown the banner and toggles STX off independently even if they do not physically dismiss the STX Banner Alert. Migration Logic: 1. If `smartTransactionsOptInStatus` is `null` (new/never interacted) - Sets status to true - Enables notification flag 2. If status is false (previously opted out): - With no Ethereum Mainnet STX activity: Sets to true with notification - With existing Mainnet STX activity: Preserves user preference 3. If status is true: No changes needed UI Components: - Implements SmartTransactionsBannerAlert component for user notification The notification system bridges the migration changes with the UI, ensuring users are informed of the STX enablement while maintaining their ability to opt out through settings. **Target release:** TBD **Affected user base:** ~5.7M users who previously opted out of STX but have no STX activity. --- ## **Related issues** Fixes: N/A --- ## **Running Unit Tests** Migration Test: ```bash yarn jest app/scripts/migrations/135.test.ts --verbose ``` Smart Transaction Banner Component Test: ```bash yarn jest ui/pages/confirmations/components/smart-transactions-banner-alert/smart-transactions-banner-alert.test.ts --coverage=false ``` Confirm Transaction base Test: ```bash yarn jest ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js --coverage=false ``` Preferences Controller Test: ```bash yarn jest app/scripts/controllers/preferences-controller.test.ts --coverage=false ``` Transaction Alerts Component Test: ```bash yarn jest ui/pages/confirmations/components/transaction-alerts/transaction-alerts.test.js --coverage=false ``` ## **Manual testing steps** **Test Migration (using a wallet/account with no STX Transactions)** 1. Switch branch: `git checkout tags/v12.5.0` and run: ```bash yarn yarn webpack ``` - Generate a `dist/chrome` directory 6. In Chrome Extension Manager, "Load Unpacked" from this directory 7. Import or setup a wallet without STX transactions, launch the wallet and choose "No Thanks" on the "Enhanced Transaction Protection" popup. 8. Check that toggle is OFF in: `Settings > Advanced > Smart Transactions` 9. Close MetaMask Extension and toggle the Extension "OFF" in the Extension Manager 10. Switch to branch from this PR `git checkout feat/enable-stx-migration` ```bash yarn yarn webpack ``` 11. Open MetaMask Extension and Check that toggle is ON in: `Settings > Advanced > Smart Transactions` **Test STX Banner Alert that it shows on Transaction Confirmations and not Sign Confirmations)** **_(using new confirmations flow)_** 12. Check that `Improved transaction requests` is ON in `Settings > Experimental` 13. Open the E2E TestDapp, try several Signs (ETH Sign, Personal Sign, Sign Typed Data, etc..) and ensure the STX Banner Alert does not show on those confirmations screens. 14. Create a Send transaction to your own wallet for `0.0001` ETH 15. Ensure that Smart Transactions Banner Alert IS showing 16. Start a Swaps transaction on Ethereum Mainnet 17. Ensure that Smart Transactions Banner Alert IS showing **Test STX Banner Alert that it shows on Transaction Confirmations and not Sign Confirmations)** **_(using old confirmations flow)_** 18. Check that `Improved transaction requests` is OFF in `Settings > Experimental` 19. Open the E2E TestDapp, try several Signs (ETH Sign, Personal Sign, Sign Typed Data, etc..) and ensure the STX Banner Alert does not show on those confirmations screens. 20. Create a Send transaction to your own wallet for `0.0001` ETH 21. Ensure that Smart Transactions Banner Alert IS showing 22. Start a Swaps transaction on Ethereum Mainnet 23. Ensure that Smart Transactions Banner Alert IS showing 24. Without clicking on Check that "Higher success rates" link (inspect) goes to: [What is 'Smart Transactions'?](https://support.metamask.io/transactions-and-gas/transactions/smart-transactions/) 25. Dismiss the Smart Transactions Banner Alert and set `Improved transaction requests` back to ON in `Settings > Experimental` 26. Create a Send transaction to your own wallet for `0.0001` ETH 27. Ensure that Smart Transactions Banner Alert IS NOT showing **Congrats, you have manually tested the happy path, now we just need to test the edge cases:** 1. Remove the extensions from Extension Manager and Repeat steps 1 to 10 above to run migration again 2. Create a Send transaction to your own wallet for `0.0001` ETH 3. Ensure that Smart Transactions Banner Alert IS showing - DO NOT DISMISS THE ALERT. Instead 4. Open MetaMask Extension and Check that toggle is ON in: `Settings > Advanced > Smart Transactions` 5. Turn it off 6. Create a Send transaction to your own wallet for `0.0001` ETH 7. Ensure that Smart Transactions Banner Alert IS NOT showing 8. Perform any other Signing and/or Transaction Confirmations and ensure there are no modals that show errors and that the Banner Alert does not show anymore. **Test edge case that after STX migration runs and Banner is being shown that clicking on "Higher success rates" link:** 1. Remove the extensions from Extension Manager and Repeat steps 1 to 10 above to run migration again 2. Create a Send transaction to your own wallet for `0.0001` ETH 3. Ensure that Smart Transactions Banner Alert IS showing - DO NOT DISMISS THE ALERT WITH CLOSE BUTTON... INSTEAD 4. Click on "Higher success rates" link and ensure that it goes to: [What is 'Smart Transactions'?](https://support.metamask.io/transactions-and-gas/transactions/smart-transactions/) 5. Open MetaMask Extension and Check that toggle is ON in: `Settings > Advanced > Smart Transactions` 6. Turn it off 7. Create a Send transaction to your own wallet for `0.0001` ETH 8. Ensure that Smart Transactions Banner Alert IS NOT showing 9. Perform any other Signing and/or Transaction Confirmations and ensure there are no modals that show errors and that the Banner Alert does not show anymore. Because the NEW confirmation flow does not support alerts using hooks that are dismissible, we have used the old style Banner Alert, and it is normal for their to be some variation on where the alert shows up and it's surroundings. But overall they should look similar. --- ## **Screenshots/Recordings** ### **Before** 01-stx_before 02-legacySend_before 03-legacySwap_before 04-signTypedDataV4_before 05-contractDeployment_before 06-contractInteraction_before 07-wideSwap_before ### **After** 01-stx_after 02-legacySend_after 03-legacySwap_after 04-signTypedDataV4_after 05-contractDeployment_after 06-contractInteraction_after 07-wideSwap_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 for the new behavior, covering: - [x] Version update handling - [x] All logic branches: - [x] `null` opt-in status - [x] `false` opt-in status with no STX activity - [x] `false` opt-in status with existing STX activity - [x] `true` opt-in status - [x] Notification flag setting - [x] Error handling - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format where 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. pulled and built the branch, ran the app, and tested the changes described above). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket and includes the necessary testing evidence (e.g., recordings, screenshots, or detailed descriptions). --------- Co-authored-by: Dan J Miller Co-authored-by: georgeweiler Co-authored-by: dan437 <80175477+dan437@users.noreply.github.com> commit 3c220da34df09a6ca1413229923f2f1020e4c2dd Author: Frederik Bolding Date: Tue Jan 7 11:21:40 2025 +0100 fix: Use correct prop for Snap UI Avatar size (#29466) ## **Description** Fixes an issue where the `size` prop would not work for the Snap UI `Avatar` component. This prop was simply missed in the mapping function, this PR corrects that. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29466?quickstart=1) ## **Manual testing steps** ```ts export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request, }) => { switch (request.method) { case 'hello': return snap.request({ method: 'snap_dialog', params: { type: 'confirmation', content: ( ), }, }); default: throw new Error('Method not found.'); } }; ``` commit 47fdbe4e5ba86a2d2ff408369a4d5025ff0def04 Author: Priya Date: Tue Jan 7 09:12:54 2025 +0100 chore: Fix flaky snap signature insights tests (#29437) ## **Description** The sign button is sometimes not enabled after the delay, just added a better waiting condition to wait for the button to be enabled [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29437?quickstart=1) ## **Related issues** Fixes: [#29227](https://github.com/MetaMask/metamask-extension/issues/29227) ## **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/main/.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/main/.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. commit 634b672205f355f0643262a3d37c41aa081abbec Author: Mark Stacey Date: Mon Jan 6 17:46:19 2025 -0330 chore: Remove broken coverage report link (#29410) ## **Description** The unit test coverage report was previously available in the `metamaskbot` comment, but this link has been broken since we migrated unit tests from CircleCI to GitHub Actions in #25570 The broken link has been removed for now. It can be restored in the future, after migrating the `metamaskbot` comment to GitHub actions as well. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29410?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** Check that the broken link is no longer shown in the `metamaskbot` PR comment. ## **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/main/.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/main/.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. commit 8a0684371fe188c8c83ab2613fccd7135d4d139c Author: Matthew Walsh Date: Mon Jan 6 15:12:38 2025 +0000 fix (cherry-pick): remove reliance on transaction decode in confirmations #29341 (#29399) ## **Description** Cherry-pick of #29341 for release `12.10.0`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29399?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/main/.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/main/.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. commit 4ee7e3f33faf439e90a6486c308eb6a6bda03b4a Author: Prithpal Sooriya Date: Mon Jan 6 13:23:30 2025 +0000 feat: add some authentication state to sentry logs (#29432) ## **Description** This adds some authentication controller state to sentry logs. We do not log sensitive and important info such as the `accessToken`. This should help with logging some of the service failures that rely on authentication. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29432?quickstart=1) ## **Related issues** Fixes: N/A ## **Manual testing steps** 1. Follow the steps in the [development readme](https://github.com/MetaMask/metamask-extension/blob/main/development/README.md#debugging-sentry) to invoke a sentry error - `DEBUG=metamask:sentry:*` and `METAMASK_DEBUG=1` can help to show the sentry state we are sending. ## **Screenshots/Recordings** ### **Before** ![Screenshot 2025-01-06 at 09 04 48](https://github.com/user-attachments/assets/0469fb5d-9eb3-4616-96f2-e863f2ddcb7c) ### **After** ![Screenshot 2025-01-06 at 11 28 23](https://github.com/user-attachments/assets/b81e6830-bb0f-4276-a345-0a0f7f097fdd) ## **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/main/.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/main/.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. commit c7d14a001f534fabd09866824eef7ce6a34a885e Author: Hassan Malik <41640681+hmalik88@users.noreply.github.com> Date: Mon Jan 6 08:07:23 2025 -0500 chore: replace local `isSnapId` definition with `isSnapId` from `@metamask/snaps-utils` (#29422) ## **Description** Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? Deduplication of code 2. What is the improvement/solution? Using the `isSnapId` function from the `@metamask/snaps-utils` package. ## **Related issues** Fixes: #29280 ## **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/main/.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/main/.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. commit 34fbdc277e32eb051e9c3bc4fa63588d99fb660a Author: Alejandro Garcia Anglada Date: Mon Jan 6 12:33:55 2025 +0100 [cherry-pick to Version-v12.10.0] feat: bump solana snap (#29350) (#29378) Cherry picks https://github.com/MetaMask/metamask-extension/pull/29350 ## **Description** Solana snap bump https://github.com/MetaMask/snap-solana-wallet/releases/tag/v1.0.4 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29350?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/main/.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/main/.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/29378?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/main/.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/main/.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: Antonio Regadas Co-authored-by: Javier commit 1fbb63cc4e2c15ffc344155f8fa6e6f240d8dd74 Author: Frederik Bolding Date: Mon Jan 6 12:20:27 2025 +0100 fix: Correct theme value for Snap UI footer buttons (#29434) ## **Description** The Snap UI footer buttons use the DS button which forces light theme, this does not work for the colors used in the Snap buttons, therefore we revert back to using the actually selected theme. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29434?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29388 ## **Screenshots/Recordings** ### **Before** ![Image](https://github.com/user-attachments/assets/fb14d5c1-44f9-473a-81bf-6f0f01c46686) ### **After** ![image](https://github.com/user-attachments/assets/c9f08368-58f5-43b3-a129-fe7689572242) commit 768716d250f945b9573e0a49ab22397dde8fa102 Author: Priya Date: Mon Dec 23 16:03:31 2024 +0100 test: fix flaky native send and transaction decoding test (#29362) ## **Description** The test dapp page loads and the element is visible but not enabled yet and this causes sometimes for the test to click on the button too soon and to fail. Added a new wait condition in the waitForSelector method to wait until the element is enabled and then click on it. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29362?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28485 ## **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/main/.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/main/.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. commit c91b4ee95c5c95a693c181f95608d93682d778a9 Author: Frederik Bolding Date: Sat Dec 21 10:05:18 2024 +0100 fix: Use `break-word` for Snaps UI text wrapping (#29387) ## **Description** Use `break-word` for Snaps UI text wrapping to prevent squishing of text in `Section` among other components. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29387?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/snaps/issues/2816 ## **Screenshots/Recordings** ![image](https://github.com/user-attachments/assets/9466464c-862d-4cc2-84a5-97896cd521f4) commit 57d564d6603f00d7d7a568efd742412810846489 Author: Mark Stacey Date: Fri Dec 20 19:47:24 2024 -0330 chore: Remove broken MV3 perf stats (#29408) ## **Description** Remove broken MV3 reports. These reports relied upon data from the `mv3-perf-stats` E2E test job, which itself relied upon the `user-data-dir` chromedriver setting removed in #24696. They have been broken since that PR. These reports were very useful in prioritizing MV3 work at the time, but we haven't needed them recently. The `mv3-stats` E2E test suite has also been removed (this is an older version of `mv3-perf-stats` that has been unused for even longer), along with the charts that were used for this report. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29408?quickstart=1) ## **Related issues** Relates to https://github.com/MetaMask/metamask-extension/issues/28572 ## **Manual testing steps** Check that the `metamaskbot` comment no longer has the links to these broken reports. They look like this: - mv3: Background Module Init Stats - mv3: UI Init Stats - mv3: Module Load Stats ## **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/main/.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/main/.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. commit b24c4e95915973a715b761ede457ea61911b63b9 Author: Matthew Walsh Date: Fri Dec 20 22:34:31 2024 +0000 fix (cherry-pick): navigation between watch asset approvals #29279 (#29401) ## **Description** Cherry-pick of #29279 for release `12.10.0`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29401?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/main/.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/main/.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. commit 2e0fe07f0e5dacfdcf6f0ae67fa63d9410d28db9 Merge: c5dd2e381a 19635b4341 Author: Dan J Miller Date: Fri Dec 20 18:30:46 2024 -0330 Merge pull request #29358 from MetaMask/Version-v12.9.3 Version v12.9.3 RC commit f64a2d003e6e69be0305c43534334461fadc6e7e Author: Matthew Walsh Date: Fri Dec 20 20:55:52 2024 +0000 fix: hide first interaction alert if token transfer recipient is internal account (#29389) ## **Description** Hide the first-time interaction alert if the transaction is a token transfer, and the recipient is an internal account. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29389?quickstart=1) ## **Related issues** Fixes: #29225 ## **Manual testing steps** Create token transfer to internal account and verify no alert is displayed. ## **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/main/.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/main/.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. commit ce8b502529b1131c65506d23a6ec7b5f68c5b526 Author: Mark Stacey Date: Fri Dec 20 17:23:21 2024 -0330 ci: Migrate LavaMoat validation to GitHub Actions (#29369) ## **Description** Migrate LavaMoat policy validation from CircleCI to GitHub actions. No functional changes. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29369?quickstart=1) ## **Related issues** Relates to #28572 These changes were extracted from #29256 ## **Manual testing steps** * Checkout this branch (`migrate-lavamoat-validation`), then from there create a new branch to test with * On this new branch, make a dependency change with a policy impact (e.g. add or remove a package, upgrade something, etc.), but make sure the build still passes (validation requires a passing build) * Create a draft PR, and verify that the policy validation fails * Use the `metamaskbot update-policies` bot command to update the policies, then verify the validation now succeeds. PR with errors - https://github.com/MetaMask/metamask-extension/pull/29396 Failure - https://github.com/MetaMask/metamask-extension/actions/runs/12434996100/job/34719873040?pr=29396 Passing - https://github.com/MetaMask/metamask-extension/actions/runs/12435253146/job/34720674397?pr=29396 ## **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/main/.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/main/.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. commit 19635b434187363920226f3541c666209daeaee7 Author: MetaMask Bot Date: Fri Dec 20 20:39:21 2024 +0000 Update Attributions commit 9788cef6e16072b073b5163b57fad9c75508a3fa Author: Dan J Miller Date: Fri Dec 20 16:57:56 2024 -0330 Changelog v12.9.3 (#29407) commit 6f11eda56785b6ca1bd253a6fc6a3498eef5bc5f Author: Mark Stacey Date: Fri Dec 20 16:13:27 2024 -0330 ci: Migrate lint CI steps (#29371) ## **Description** Migrate lint steps from CircleCI to GitHub Actions. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29371?quickstart=1) ## **Related issues** Relates to #28572 These changes were extracted from #29256 ## **Manual testing steps** Branch from here, create a new draft PR, Introduce lint errors, then ensure the jobs fail. https://github.com/MetaMask/metamask-extension/pull/29390 ## **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/main/.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/main/.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. commit 506995095c53ee3d144fc0f727df73fe13ec6c8f Author: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> Date: Fri Dec 20 14:39:19 2024 -0500 fix (cherry-pick):Add main frame URL property to req object whenever req is triggered from an iframe #29337 (#29405) Chery pick PR: #29337 into `V12.9.3` ## **Description** See the attached issue in metamask planning for more details. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29337?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to `https://develop.d3bkcslj57l47p.amplifyapp.com/` 2. Click on Proceed anyways (This phishing warning page here is expected) 3. Open the network tab to monitor network requests 4. Connect your wallet and click on a signature or transaction 5. Verify that mainFrameOrigin is included in the payload of the network request to the security alerts API Screenshot 2024-12-20 at 10 46 05 AM ## **Screenshots/Recordings** Below are screenshots demonstrating the behavior of a test HTML page I created: 1. In the first screenshot, before the iframe is loaded, the console shows only the origin of the main frame. 2. In the second screenshot, after clicking the button to load an iframe pointing to example.com, the solution correctly identifies both the mainFrameOrigin (main frame) and the origin (iframe). Screenshot 2024-12-18 at 10 24 48 PM Screenshot 2024-12-18 at 10 24 54 PM ### **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/main/.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/main/.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. commit a02799d97397fdfda492341566851203ae04027d Author: Mark Stacey Date: Fri Dec 20 14:49:06 2024 -0330 ci: Migrate dependency linting (#29370) ## **Description** Migrate dependency/lockfile linting steps from CircleCI to GitHub actions. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29370?quickstart=1) ## **Related issues** Relates to #28572 These changes were extracted from #29256 ## **Manual testing steps** Review logs to ensure the same commands are run. Introduce errors on a branch from here to ensure the problems are caught. https://github.com/MetaMask/metamask-extension/pull/29391 ## **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/main/.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/main/.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. commit 611f3bbbabda9dfbe2ae998e7e8153bcaa81b758 Author: Dan J Miller Date: Fri Dec 20 14:32:49 2024 -0330 chore (cherry-pick): fix: nanoid audit issue (#29268) (#29398) Cherry pick 0e10bab6bc (#29268) to v12.9.3 Co-authored-by: Alejandro Garcia Anglada Co-authored-by: MetaMask Bot commit 547b264a3993aa4d40caad5d2993df2a1c7ca32e Author: Pedro Figueiredo Date: Fri Dec 20 17:46:37 2024 +0000 chore: Update to the latest transaction controller (#29395) ## **Description** Updates from v42 to v42.1 in order to get the validation of the gas limit hexadecimal string properties. See https://github.com/MetaMask/core/pull/5093 for more details. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29395?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3826 ## **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/main/.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/main/.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 commit 1712e287c0df901a475d8c4c4cbf6c20d1c30e28 Author: Matthew Walsh Date: Fri Dec 20 17:25:52 2024 +0000 fix (cherry-pick): remove reliance on transaction decode in confirmations #29341 (#29397) ## **Description** Cherry-pick of #29341 for release 12.9.3. Difference to `main` is that redesigned transactions are disabled if the `Decode smart contracts` toggle is disabled. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29397?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/main/.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/main/.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. commit 695d0db025fff9d9b29d6ca2c03c42bfd58cf57e Author: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> Date: Fri Dec 20 12:15:55 2024 -0500 fix: Add main frame URL property to req object whenever req is triggered from an iframe (#29337) ## **Description** See the attached issue in metamask planning for more details. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29337?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to `https://develop.d3bkcslj57l47p.amplifyapp.com/` 2. Click on Proceed anyways (This phishing warning page here is expected) 3. Open the network tab to monitor network requests 4. Connect your wallet and click on a signature or transaction 5. Verify that mainFrameOrigin is included in the payload of the network request to the security alerts API Screenshot 2024-12-20 at 10 46 05 AM ## **Screenshots/Recordings** Below are screenshots demonstrating the behavior of a test HTML page I created: 1. In the first screenshot, before the iframe is loaded, the console shows only the origin of the main frame. 2. In the second screenshot, after clicking the button to load an iframe pointing to example.com, the solution correctly identifies both the mainFrameOrigin (main frame) and the origin (iframe). Screenshot 2024-12-18 at 10 24 48 PM Screenshot 2024-12-18 at 10 24 54 PM ### **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/main/.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/main/.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. commit d510d5cab2e7e5265e9cf6580498a4a03ede660c Author: Danica Shen Date: Fri Dec 20 15:45:29 2024 +0000 feat(14507): improve error message for failed txn in activity details view (#29338) ## **Description** When we encounter a failed transaction: - in activity list from home view, it renders as `failed`, hovering the status will render an error message - when clicking this activity, the popup view for txn details will render `failed` as well, with no hover effect - In the meanwhile, there's a new and more user friendly error banner showing for user when txn failed. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29338?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/14507 Figma: https://www.figma.com/design/ZzVQ6iu13C67K807Z1bg5I/Smart-Transactions-1.0?node-id=4296-25303&t=ff749RbiH6F4IUqk-0 ## **Manual testing steps** 1. Render extension and test dapp 2. Trigger a failed txn from test dapp 3. Check activity for that txn 4. Check activity details by clicking item from step 3 and validate the banner, as well as hover ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-18 at 18 25 45 ### **After** Screenshot 2024-12-19 at 01 54 39 ## **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/main/.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/main/.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. commit 36f084c904b97c5f34432e44b1434acc5dff5430 Author: Danica Shen Date: Fri Dec 20 15:34:09 2024 +0000 fix(28081): design tweak for network badge (#29324) ## **Description** This PR address feedback from design quality check to tweak board color and width for network badge. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29324?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28081 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** See PR for related changes ### **After** See PR for related changes ## **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/main/.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/main/.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: georgewrmarshall commit 4e255c17d41925096c778711f9c7d51e27d5b174 Author: Frederik Bolding Date: Fri Dec 20 16:25:20 2024 +0100 chore: Bump Snaps packages (#29275) ## **Description** Bump Snaps packages and handle any required changes. Summary of Snaps changes: - Allow async initialization logic for Snap bundles - Add `onSettingsPage` export - Add `Banner` component - Support `size` prop on `Button` - Support `fontWeight` prop on `Text` - Add `loading` state for `Button` - Use BigInt for processing insight chain IDs [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29275?quickstart=1) ## **Related issues** Closes https://github.com/MetaMask/snaps/issues/2939 Closes https://github.com/MetaMask/snaps/issues/2947 Closes https://github.com/MetaMask/snaps/issues/2874 Closes https://github.com/MetaMask/snaps/issues/2694 --------- Co-authored-by: David Drazic Co-authored-by: Guillaume Roux commit 863d77c6620d46594e3a787fddbfa818156c6cae Author: Frederik Bolding Date: Fri Dec 20 15:59:50 2024 +0100 fix: Use margin instead of padding for the Snaps UI Container (#29385) ## **Description** Use `margin` instead of `padding` for the Snaps UI Container. This fixes an issue where using certain components (such as Section) at the root would look weird. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29385?quickstart=1) ## **Screenshots/Recordings** ### **Before** ![image](https://github.com/user-attachments/assets/ba7950e6-d41a-411d-aba1-a156a74efaad) ### **After** ![image](https://github.com/user-attachments/assets/c7e475d4-8b61-4ec9-ac55-084c0bb8e2df) commit 29cbe954a31c6a6c712cb299d83ff33be616608c Author: Danica Shen Date: Fri Dec 20 14:42:18 2024 +0000 fix(29226): fix error for undefined unitInput for sending NFT (#29386) ## **Description** When switching to another non-ERC721 NFT in send flow, it is possible that the input is undefined when rendered. As we didn't need to use `scrollTo` for this input (introduced [here](https://github.com/MetaMask/metamask-extension/pull/25307/files#diff-5b53d7ec060521a384c4dff7468398b01e302ac17511f243b79a5dd1d1cb69d3)) , putting a conditional running is sufficient. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29386?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29226 ## **Manual testing steps** 1. Go to NFT 2. Pick a non ERC721 NFT to send 3. Should not see errors in console when in the sending form ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/98268028-fcb1-419e-8638-bb3cbc482b3c ### **After** https://github.com/user-attachments/assets/830f40c3-0601-4eab-971f-eb69f253c084 ## **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/main/.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/main/.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. commit fde9fa1779a99170e5f869990357ee07e0393137 Author: Matthew Walsh Date: Fri Dec 20 13:19:20 2024 +0000 fix: navigation between watch asset approvals (#29279) ## **Description** Ignore any additional watch token and watch NFT approvals in the confirmation navigation, since both types of watch asset confirmation combine data from multiple approval requests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29279?quickstart=1) ## **Related issues** Fixes: #29189 ## **Manual testing steps** See issue. ## **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/main/.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/main/.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. commit 1c6391ce0f5a7ff8777b2b2e9a13d988e2296d87 Author: Matthew Walsh Date: Fri Dec 20 13:18:11 2024 +0000 fix: remove reliance on transaction decode in confirmations (#29341) ## **Description** Disable all advanced transaction data decoding using Sourcify, 4Byte and Uniswap, if the `Decode smart contracts` toggle is disabled. Remove all reliance on the advanced decoding excluding the `Data` section. Specifically: - Create `useTokenTransactionData` hook to decode all token transactions locally using the ABIs. - Replace all usages of `useDecodedTransactionData` with the new hook, except for the `TransactionData` component. - Update related unit and integration tests to decode valid test data rather than rely on mocking hooks. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29341?quickstart=1) ## **Related issues** ## **Manual testing steps** Regression of redesigned transaction confirmations. Specifically: - Token Transfer Recipient - Token Transfer Amount - Approval Spender - Approval Spending Cap ## **Screenshots/Recordings** ### **Before** ### **After** New Toggle Description ## **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/main/.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/main/.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. commit 2eced173cb21121a054ec5f271391f7dcc005068 Author: Jyoti Puri Date: Fri Dec 20 17:41:44 2024 +0530 fix: Adding validation for primary type of types sign signatures (#29379) ## **Description** Types sign request to not allow invalid primary types. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/22899 ## **Manual testing steps** 1. Go to test dapp 2. Submit signature with text "Invalid primary type" 3. Request should be rejected and not open MetaMask ## **Screenshots/Recordings** Screenshot 2024-12-20 at 3 34 16 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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 commit 02230c725bcc28b327df9b81c2198e74c9e875ea Author: Salim TOUBAL Date: Fri Dec 20 12:22:10 2024 +0100 fix: remove Text in the Activity Empty State (#29318) ## **Description** This PR updates the Activity empty state by removing the placeholder text. The change simplifies the UI, ensuring a cleaner and more visually appealing experience when no activity is present. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29318?quickstart=1) ## **Related issues** Fixes: #26669 ## **Manual testing steps** 1. Go to activity tab for any network where you don't have transactions 2. check the message ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-18 at 13 35 07 ### **After** Screenshot 2024-12-18 at 13 35 12 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 3fd27a9e9a170da753963ddc751adc9cf585596d Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri Dec 20 11:12:48 2024 +0000 fix: `gasFeeEstimates` property undefined (#29312) ## **Description** This PR fixes an issue created by Sentry where the property `gasFeeEstimates` is `undefined`. Added an early return when the property is `undefined` and covered the scenario with unit tests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29312?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27501 https://github.com/MetaMask/metamask-extension/issues/27241 ## **Manual testing steps** 1. Go to this test dapp 2. Block requests against gas-api 3. Start a send or contract interaction 4. Verify the console ## **Screenshots/Recordings** [block-gas-api.webm](https://github.com/user-attachments/assets/1c3cf638-905e-4df9-b875-84f0056979b1) ### **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/main/.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/main/.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. commit 317b923c2f250fca38bd5f4c60d167ff65bc569b Author: OGPoyraz Date: Fri Dec 20 12:04:25 2024 +0100 chore: Cherry pick `29343` (#29376) This PR cherry picks https://github.com/MetaMask/metamask-extension/pull/29343 commit 8a6f4f9198e19356ce545fb4d2561d86daabb6f5 Author: Nidhi Kumari Date: Fri Dec 20 10:59:13 2024 +0000 fix: fixed truncation issue for long help text (#29269) This PR aims to fix the truncation issue in send flow. The decimal value in error message used to show all the decimal numbers after decimal. This PR updates the max value to be shown after decimals to be 4 which is also the standard decimal value we try to show in other places of code ## **Related issues** Fixes: #26766 ## **Manual testing steps** 1. Go to Send Flow 2. Try sending a token with large input than the available balance 3. Check the value in helptext doesn't overlap and only shows 4 numbers ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-17 at 11 17 19 AM](https://github.com/user-attachments/assets/8f9e39ba-60a6-4cd9-88f3-c7e56aa14e1c) ### **After** ![Screenshot 2024-12-17 at 11 18 05 AM](https://github.com/user-attachments/assets/08a6913e-13a1-40ad-bd48-ae8528412287) ## **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/main/.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/main/.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. commit 7be1b0d3b193764eef0489ba3c9df49eba01f2fa Author: Priya Date: Fri Dec 20 11:55:45 2024 +0100 test: remove duplicate signature tests (#29377) ## **Description** Removing duplicated tests that are already covered in test/e2e/tests/confirmations/signatures [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29377?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/main/.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/main/.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. commit 356ad476f75183e17f9493b75625575daedfd018 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri Dec 20 11:38:04 2024 +0100 test: [POM] Migrate watch account tests (#29314) ## **Description** - Created a new page class `AccountDetailsModal`. Previously, it was a part of `AccountList`. I think it's better to separate it and make it an independent class. - I also took the chance to improve the function `addAccount` and remove the origin `addNewAccount`. So now for creating ethereum, bitcoin, solana accounts, we use the same `addAccount` function with the account type as a parameter. - Migrate watch account e2e tests to Page Object Model - Created `watchEoaAddress` flow that can be reusable. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** ## **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. commit a33f52631f87275f86459b74d0e7ae12511b954c Author: Jyoti Puri Date: Fri Dec 20 15:26:18 2024 +0530 fix: UI is not displaying gas limit set by dapp (#29352) ## **Description** UI is not displaying correct gas limit set by dapp. It always displays `21000`. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/18417 ## **Manual testing steps** 1. Submit confirmation request with gas limit not `21000` 2. Check gas limit in gas editing popup ## **Screenshots/Recordings** Screenshot 2024-12-19 at 7 20 45 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit fd6e75559bc374698e84a1a4c42808e542c2838a Author: OGPoyraz Date: Fri Dec 20 10:34:24 2024 +0100 fix: Use `toUnicode` function to normalize ens domains in the UI (#29231) ## **Description** In `ENSController`(https://github.com/MetaMask/core/blob/main/packages/ens-controller/src/EnsController.ts#L375) right before saving the ens domain in the state we use `toASCII`. This function is typically used to convert a domain name from its Unicode representation to ASCII, specifically using the `Punycode` package encoding. This is necessary because the Domain Name System (DNS) operates with ASCII characters, and internationalized domain names (IDNs) need to be converted to a format that DNS can understand. On the other side, in the client, we are not converting/normalizing this domain value. That is causing that unwanted ASCII coded domain in the UI when using smileys in the ENS domain. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29231?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28610 ## **Manual testing steps** See https://github.com/MetaMask/metamask-extension/issues/28610 for repro steps ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-16 at 13 37 04](https://github.com/user-attachments/assets/62163ce0-007a-404b-8f2e-7a49eaa7b927) ### **After** ![Screenshot 2024-12-16 at 13 36 32](https://github.com/user-attachments/assets/cd287cb0-aa81-49da-aafb-1e753d8544e7) ## **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/main/.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/main/.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. commit 367769b9e299428e836e6e9d5bb49a1ebbf04cf0 Author: seaona <54408225+seaona@users.noreply.github.com> Date: Fri Dec 20 10:19:26 2024 +0100 test: [POM] Dapp subscribe network switch spec migration (#29346) ## **Description** This PR migrates the `test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js` spec to use page object model and typescript. It also updates the assertions to test what it's intended. 1. We go to the test dapp and connect 2. We subscribe to the newHeads event ``` await window.ethereum.request({ "method": "eth_subscribe", "params": [ "newHeads" ], }); ``` 3. We add an event listener for subscribe messages, and we'll store this into a window variable, so we can access it later ``` window.ethereum.on('message', (message) => { if (message.type === 'eth_subscription' && message.data.subscription === '0x4bc2639eb3ac769db7a90f60a47b33c4') { console.log('New block header:', message.data.result); } }) ``` 4. We switch networks from MM wide screen 5. We go back to the dapp 6. We mine a block deterministically --> In ganache we have setup auto-mining by default, however this happens every some seconds, by performing a mine ourselves, we know for sure that this happened at least once at the point we want 7. We wait a couple of seconds to see if more event logs appear 8. We assert that we got more events, after switching networks [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29346?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29348 ## **Manual testing steps** 1. Check ci continues to pass 3. Run spec manually `yarn test:e2e:single test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts --browser=chrome --leave-running=true ## **Screenshots/Recordings** Messages when we console log them in the spec ![Screenshot from 2024-12-19 12-04-23](https://github.com/user-attachments/assets/7d55edc0-107f-419f-9f2a-d7bdbe3fc80a) This is the flow that happens in the spec, done manually. https://github.com/user-attachments/assets/cd68d1ac-c0ac-4bd2-b089-fec605ffed6d ## **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/main/.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/main/.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. commit d4c5a7368df7ce83ea8d63caf6fe2f592f3aaa28 Author: digiwand <20778143+digiwand@users.noreply.github.com> Date: Fri Dec 20 14:46:25 2024 +0700 fix: Network URL toPunycodeUrl preserve no path slash (#29325) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29325?quickstart=1) ## **Related issues** Related to: https://github.com/MetaMask/metamask-extension/pull/29322 ## **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/main/.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/main/.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. commit fd3c51cf42b124277effd0a0f68e693f76872c45 Author: OGPoyraz Date: Fri Dec 20 08:29:46 2024 +0100 fix: Sanitize `signTypedDatav3v4` params before calling security API (#29343) ## **Description** This PR aims to filter request params before calling security API call if method is `signTypedDatav3v4` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29343?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3830 ## **Manual testing steps** 1. Copy the following payload ``` // Request the current account addresses from the Ethereum provider const addresses = await window.ethereum.request({ "method": "eth_accounts" }); // Construct the JSON string for eth_signTypedData_v4, including the dynamic owner address const jsonData = { domain: { name: "USD Coin", version: "2", chainId: "1", verifyingContract: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" }, types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" } ], Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" } ] }, primaryType: "Permit", message: { owner: addresses[0], spender: "0xa2d86c5ff6fbf5f455b1ba2737938776c24d7a58", value: "115792089237316195423570985008687907853269984665640564039457584007913129639935", nonce: "0", deadline: "115792089237316195423570985008687907853269984665640564039457584007913129639935" } }; // Use the first account address for signing the typed data window.ethereum.sendAsync({ method: "eth_signTypedData_v4", params: [ addresses[0], JSON.stringify(jsonData), {}, {}, {} ] }); ``` 2. Navigate to MM E2E Test Dapp > Connect Wallet > Open up the console > Paste the payload above > Hit enter 3. Notice that the transaction is considered as malicious (which was not flagged before) ## **Screenshots/Recordings** https://github.com/user-attachments/assets/ffcdd83f-bb79-4490-b729-f96559ce5769 ### **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/main/.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/main/.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. commit c4dce82e6e016f79e2f2c67cdbaa350e75989f1a Author: Priya Date: Fri Dec 20 07:10:35 2024 +0100 chore: remove duplicated tests for metrics for redesigned signatures (#29359) ## **Description** Removes the duplicated tests for redesigned signature metrics, the same metrics are checked in the tests in the folder : test/e2e/tests/confirmations/signatures [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29359?quickstart=1) ## **Related issues** Fixes: [29228](https://github.com/MetaMask/metamask-extension/issues/29228) ## **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/main/.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/main/.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. commit 46562e0e21ee7e1f6322f1ab5c77b2c8e42d37a3 Author: Brian Bergeron Date: Thu Dec 19 14:34:18 2024 -0800 chore(cherry-pick): fix erc20 token balances showing 0 (#29365) cherry picks https://github.com/MetaMask/metamask-extension/pull/29361 to v12.9.3 commit 021a2657b5fe0f958bf6aa5c57f121d37193e5a4 Author: Brian Bergeron Date: Thu Dec 19 12:10:15 2024 -0800 fix: erc20 token balances showing 0 (#29361) ## **Description** Fixes an issue where erc20 token balances were incorrectly showing 0. On the repro we have, we noticed a token in state with address `0x0000000000000000000000000000000000000000` on mainnet, which is not a valid erc20 token. This caused the multicall to revert, preventing other balances from updating. There's a fix in the controller here: https://github.com/MetaMask/core/pull/5083 which will fall back to parallel `balanceOf` calls if the multicall reverts. And we're also doing a migration here to remove zero address tokens on mainnet. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29361?quickstart=1) ## **Related issues** ## **Manual testing steps** The current version of the wallet should not allow importing an invalid erc20 address through any mechanism, so not easy to reproduce naturally. The migration can be tested by checking out an older version like `git checkout v12.9.0 `, upgrading to this branch, verifying the migration ran in background logs, and that your tokens remain. ## **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/main/.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/main/.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. commit 891c956b46d38e09fec868079e4648b3ce00e486 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Thu Dec 19 11:56:46 2024 -0800 chore: Remove unused loadingTokens localized string (#29329) ## **Description** localized string `loadingTokens` is no longer being used. This PR removes it. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29329?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/main/.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/main/.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. commit cd39d7b33cf5d0bf44b57f284e9875eb618679af Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Thu Dec 19 10:52:16 2024 -0800 chore: low return warning alert for bridging (#29171) ## **Description** Changes the Low Return tooltip into an alert Banner, and highlights network fee in the quote card [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29171?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1814 ## **Manual testing steps** 1. Request quotes with a low return 2. Verify that new treatment is shown ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-17 at 10 47 21 AM](https://github.com/user-attachments/assets/c30a4682-7174-4041-83df-58726127f3c5) ![Screenshot 2024-12-17 at 10 47 46 AM](https://github.com/user-attachments/assets/f4576048-7f41-4677-b205-710421ecd9c6) ### **After** ![Screenshot 2024-12-17 at 10 32 28 AM](https://github.com/user-attachments/assets/c8db2d88-fce9-491e-9949-271b3d9faf96) ## **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/main/.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/main/.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. commit a2f4ee5aba89aca52997a47f1fd93cb354158e21 Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Thu Dec 19 10:38:33 2024 -0800 fix [cherry-pick]: sentry e2e test fix for bridge loading states (#29360) commit b114f880693f324dab60bd5d8424a013953796bc Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Thu Dec 19 09:33:14 2024 -0800 refactor: shared bridge types (#29254) ## **Description** There is a lint rule that flags lines in which controllers and ui components import types/variables/methods from restricted directories. This change moves the imported elements to the `shared` directory to satisfy the linter. Also uses `import type` to remove type definitions from runtime bundles when possible [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29254?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1829 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A - no functional changes ## **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/main/.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/main/.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. commit 9e42c34699f5566ee88c80590b651d476c926cfb Author: MetaMask Bot Date: Thu Dec 19 17:10:49 2024 +0000 Version v12.9.3 commit b030ba95b6b9dd125ce27e8318c270b9902c7645 Author: OGPoyraz Date: Thu Dec 19 17:33:06 2024 +0100 fix: Prevent unwanted `updateEditableParams` calls on send flow (#29048) ## **Description** This PR aims to prevent unwanted `updateEditableParams` calls in send flow when `useMaxValue` is settled. After investigating [Sentry error mentioned in the task](https://metamask.sentry.io/issues/5973118037/events/1ee3e017ad454f09b3666089fba3c2bd/?project=273505&referrer=previous-event) noticed `updateEditableParams` is throwing when `useMaxValue` is set to `true`. This issue is happening when we submit/cancel confirmation, component gets an update, `updateMaxValue - updateEditableParams` gets called but transaction is not `unapproved` state. So adding a condition to updating transaction value only when transaction is `unapproved` status, this will guarantee to prevent unwanted calls. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29048?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27742?reload=1 ## **Manual testing steps** N/A ## **Screenshots/Recordings** ### **Before** N/A ### **After** 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/main/.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/main/.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. commit 09d413695487a47ee20e07ee13186acaf2c81453 Author: Pedro Figueiredo Date: Thu Dec 19 16:18:48 2024 +0000 fix: Add origin pill to wallet_addEthereumChain confirmation (#29317) ## **Description** Adds Origin Pill component and references it on the confirmation template. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29317?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/26656 ## **Manual testing steps** 1. Go to the test dApp 2. Add a custom chain ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2024-12-18 at 12 08 31 Screenshot 2024-12-18 at 11 58 38 ## **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/main/.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/main/.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. commit 22490c38d46f993633d35f5f1f56224894a929ba Author: Pedro Figueiredo Date: Thu Dec 19 15:19:21 2024 +0000 fix: Wrong icon for ETH on L2s being displayed on transfer confirmations (#29353) ## **Description** We were inadvertently referencing the network icon map instead of the native token map. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29353?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29351 ## **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/main/.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/main/.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. commit 13a1fcfc694f3f23b95e99510ad86258dfa381ff Author: Pedro Figueiredo Date: Thu Dec 19 15:07:00 2024 +0000 feat: Display Unlimited for really large spending caps on Permit (#29102) ## **Description** Uses `UNLIMITED_THRESHOLD` to determine wether or not to show a permit amount as "Unlimited". Updates unit tests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29102?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3763 ## **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/main/.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/main/.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. commit 927ef8c3b19da8ade4462e6adbd0e3e4cb8ca32c Author: Alejandro Garcia Anglada Date: Thu Dec 19 15:53:53 2024 +0100 feat: bump solana snap (#29350) ## **Description** Solana snap bump https://github.com/MetaMask/snap-solana-wallet/releases/tag/v1.0.4 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29350?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/main/.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/main/.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: Antonio Regadas Co-authored-by: Javier commit cf6a43d54e5dea8c0694d048fddf44cbb16f0c8e Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu Dec 19 15:24:07 2024 +0100 chore: bump `@metamask/smart-transactions-controller` to `16.0.0` (#29344) ## **Description** This PR bumps `@metamask/smart-transactions-controller` to `16.0.0` [CHANGELOG](https://github.com/MetaMask/smart-transactions-controller/blob/main/CHANGELOG.md#1600) - `@metamask/transaction-controller` has been bumped to `42.0.0` which match the current version used in the client. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29344?quickstart=1) ## **Related issues** Fixes: ## **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/main/.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 - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 398ec979ef0ae27214e11ec644f6415df404a897 Author: Mark Stacey Date: Thu Dec 19 10:08:56 2024 -0330 ci: Improve accuracy of `wait-for-circleci-workflow-status` (#29310) ## **Description** The GitHub Actions workflow `wait-for-circleci-workflow-status` has been updated to ensure that it waits for the correct workflow. Previously it always chose the most recent workflow for the given branch, but it may have chosen a workflow corresponding to the wrong commit. It has been updated to find one matching the same commit that triggered the GitHub Actions workflow. A log has been added to help diagnose any future problems with this workflow, and to help with verification. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29310?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** Check the logs of the "Wait for CircleCI workflow status" job, and see that the workflow ID it's waiting on is correct when making multiple successive commits (comparing the timing using the CircleCI dashboard) Unfortunately the ID logged in the action is not shown on the CircleCI UI, but you can download the pipeline data with this command: `curl 'https://circleci.com/api/v2/project/gh/MetaMask/metamask-extension/pipeline?branch=[branch]' > pipeline.json` and look through the `pipeline.json` file for an entry that matches the logged ID. The `number` field is the pipeline number, which is in the CircleCI workflow URL (e.g. `https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/[pipeline number]/workflows/[some other number]`) ## **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/main/.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/main/.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. commit e8d6765952d311e73cc08ca270326e2b46650701 Author: Nidhi Kumari Date: Thu Dec 19 12:20:17 2024 +0000 fix: updated margin for import token banner (#29283) This PR is to update the margin bottom in the detected token banner ## **Description** This PR updates the Top right bottom and left margin to 16px, 16px, 4px and 16px for import token banner ## **Related issues** Fixes: #26670 ## **Manual testing steps** 1. Must be a new user with not all tokens added 2. Open MetaMask 3. If user has detected tokens, check the margin of the banner ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-17 at 3 06 39 PM](https://github.com/user-attachments/assets/b912aa7a-04da-4354-88bc-c37ca4433b25) ### **After** ![Screenshot 2024-12-17 at 3 38 33 PM](https://github.com/user-attachments/assets/b7d4afba-615a-4df3-8598-ef2b82d18143) ## **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/main/.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/main/.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. commit d06dad70d8ade01aa569280cfddd05da24bfaa54 Author: Jony Bursztyn Date: Thu Dec 19 12:18:54 2024 +0000 feat: add metametrics events to carousel (#29141) ## **Description** This PR introduces tracking metrics for banners in the carousel to monitor their effectiveness. The following events are added: - **Banner selected:** Triggered when a banner or a button within a banner is clicked. - **Close all banners:** Triggered when the last banner in the carousel is closed. - **Banner shown:** Triggered when a banner is displayed in the carousel. ### Tracking Implementation Details: #### Banner selected When a banner or button in a banner is clicked: ```javascript trackEvent({ event: MetaMetricsEventName.BannerSelect, category: MetaMetricsEventCategory.Banner, properties: { banner_name: e.g. buy, bridge, sell, card } }); ``` #### Close all banners When the last banner in the carousel is closed: ```javascript trackEvent({ event: MetaMetricsEventName.BannerCloseAll, category: MetaMetricsEventCategory.Banner }); ``` #### Banner shown When a banner is displayed in the carousel: ```javascript trackEvent({ event: MetaMetricsEventName.BannerDisplay, category: MetaMetricsEventCategory.Banner, properties: { banner_name: e.g. buy, bridge, sell, card } }); ``` ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3764 ## **Manual testing steps** 1. Open the carousel. 2. Click on banners or buttons within banners to trigger the "Banner selected" event. 3. Close the last banner to trigger the "Close all banners" event. 4. Navigate through the carousel to ensure the "Banner shown" event is fired for each displayed banner. ## **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/main/.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/main/.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. commit 5855938e202d2babe05cb1cd8785ef12fb3e0959 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu Dec 19 12:36:23 2024 +0100 chore: bump `@metamask/user-operation-controller` to `^21.0.0` (#29089) ## **Description** This PR bumps `@metamask/user-operation-controller` to `^21.0.0` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29089?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28986 ## **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/main/.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/main/.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 commit 3b7c9cbc9f0399d138229ad152ebec4eeadd13da Author: Pedro Figueiredo Date: Thu Dec 19 11:32:15 2024 +0000 fix: Ellipsis displayed for petname even though the full name is visible (#29282) ## **Description** Previously, if a pet name with 12 characters would be set, the truncation would be triggered with the 12 chars plus the three ellipsis dots. This would result in a elongated pill component that would be too long. To fix this, we now truncate at 12 characters, but showing 9 characters only plus the three dots, totalling the same maximum 12 chars. Examples: - Very large account name (23 characters) -> Very larg... (9 + 3 characters) - My DeFi Account (15 characters) -> My DeFi A... (9 + 3 characters) - DeFi Account (12 characters) -> DeFi Acco... (9 + 3 characters) - Own Account (11 characters) -> Own Account (11 characters) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29282?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3775 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2024-12-17 at 14 50 02 Screenshot 2024-12-17 at 14 49 33 ## **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/main/.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/main/.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. commit a1b41c4e49497805f437011b643e99cf1680ebb2 Author: Jyoti Puri Date: Thu Dec 19 16:38:26 2024 +0530 fix: personal sign message - decode message to UTF-8 string only if it is valid UTF-8 (#29232) ## **Description** Converts personal sign message to UTF-8 string only if it can be converted to valid UTF-8 string. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/3931 ## **Manual testing steps** 1. Send personal sign request to extension with message string `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` 2. Check on confirmation page that it is displayed as UTF-8 string ## **Screenshots/Recordings** Screenshot 2024-12-16 at 6 53 30 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 749d0acc42a1a10b00da25e0dd1571a0f2054d0f Author: Pedro Figueiredo Date: Thu Dec 19 11:01:41 2024 +0000 fix: Spending cap flicker as decimals are determined (#29206) ## **Description** Sometimes, when approving a token, the spending cap displayed in the transaction simulation component flickers for a split second. This can also seen when editing the spending cap on the same screen. In the approve screen, `useAssetDetails` returns `decimals` at first as `undefined` while it's determined asynchronously. Since `useApproveTokenSimulation` takes `decimals` as an argument, a default of `'0'` was set to quiet the type-checker. This default is what provokes the UI flicker. In the example video below, the token has 4 decimals and the spending cap is 70000 / 10 ** 4 = 7. But while decimals is still `undefined`, `'0'` is used in `useApproveTokenSimulation` to determine the spending cap (dividing the value by 10 to the number of decimals). This amounts to `70000` instead of `7` for a split second, before decimals `'4'` is returned by `useAssetDetails`. The fix for this bug is to let the loading spinner linger a few miliseconds longer while decimals is still `undefined`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29206?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/a09f449a-78ea-4083-b47b-2f329126d4b6 ### **After** https://github.com/user-attachments/assets/68142912-ae20-47f6-8dd4-2b9184b57bbf ## **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/main/.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/main/.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. commit dd659446809d793399ee97f581cdc9f285493988 Author: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com> Date: Thu Dec 19 11:46:31 2024 +0100 feat: added create solana account test (#28866) ## **Description** - E2E tests covering different scenarios for creating/removing Solana accounts. - Refactor around some snap logic for BTC/Solana [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29054?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Running flask tests should 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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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: Ulisses Ferreira Co-authored-by: Dan J Miller Co-authored-by: Charly Chevalier commit fd3366caba51fc512d217b44e5bb25227330adc1 Author: Prithpal Sooriya Date: Thu Dec 19 10:35:50 2024 +0000 fix: handle notification api calls settings (#29327) ## **Description** Adds effect checks; removes old hooks unused; and adds tests. I really HATE the useFootguns we have everywhere for notifications. I want us to do a wider cleanup task. 1. Remove the Provider values. We can keep the provider to run initial effects, but other than that, we should not expose anything from it! 2. Remove nearly all of the useEffects scattered across all the components. Instead we can utilise 1 reusable hook for fetching. - potentially we can expose this in the provider if we really want to fetch only once! [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29327?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28173 ## **Manual testing steps** 1. Go through onboarding, and settings pages > Notifications 2. Check console and network tab, are we making API calls and throwing errors in console? ## **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/main/.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/main/.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. commit c8a3c58dff76890f3ca0302daa7799db15d35463 Author: David Walsh Date: Wed Dec 18 17:38:47 2024 -0600 fix: MMASSETS-475 - Add network name to asset details page (#29211) ## **Description** Adds the network name to the assets details page so the user can be sure to know what network a given asset lives on. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29211?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-475 ## **Manual testing steps** 1. Click any number of asset items in the asset list 2. Ensure that the network name that displays matches the asset network and images ## **Screenshots/Recordings** ### **Before** ### **After** SCR-20241213-osij ## **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/main/.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/main/.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. commit f05425713723e74a6cba5ab86ce5f7dcbc709e4e Author: George Marshall Date: Wed Dec 18 12:43:54 2024 -0800 chore: updating menu item to use text component (#29304) ## **Description** This PR updates the MenuItem component to better align with our design system by: 1. Implementing responsive text sizes - 14px for small screens and 16px for large screens 2. Adjusting the padding to improve visual spacing and alignment These changes improve consistency across the application and enhance the overall user experience by providing better readability and spacing across different device sizes. 1. Reason for change: MenuItem text size and padding are not following our design system guidelines 2. Solution: Update text sizing and padding using our design system's specifications [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29304?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/26668 ## **Manual testing steps** 1. Open MetaMask extension 2. Click on any menu with MenuItems 3. Verify text is 14px on small screens (< 768px) 4. Verify text is 16px on large screens (≥ 768px) 5. Verify padding provides proper spacing around menu items 6. Verify all alignments and spacing remain consistent with icons and subtitles ## **Screenshots/Recordings** ### **Before** Menu item is locked to 16px font size and y:14px x:16px padding https://github.com/user-attachments/assets/6978a281-7de2-4141-a664-117952f63195 ### **After** Menu item uses responsive typography to 14px/16px font size and y:16px x:16px padding https://github.com/user-attachments/assets/0dd13e70-0be2-4f85-8399-6337ce379e02 Other menus using the `MenuItem` still function as expected in both expanded and popup views. https://github.com/user-attachments/assets/4d3a215a-6778-452d-84e1-535bb10b9b2e ## **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/main/.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/main/.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. commit 99c393ed75ad40ed3511088d83114144728183f3 Author: Ramon AC <36987446+racitores@users.noreply.github.com> Date: Fri Dec 13 16:09:18 2024 +0100 fix: remove mmi tests from ci (#29201) ## **Description** Remove all MMI tests [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29201?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/main/.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/main/.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. commit b0e603385b3196781742deea2f059cf7175c1a81 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri Dec 13 10:54:14 2024 +0100 chore: fix changelog lint 12.10.0 (#29186) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29186?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/main/.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/main/.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. commit 7b09fdd16fa174480ae6bfc57104e191d39d8d6f Author: MetaMask Bot Date: Fri Dec 13 00:56:31 2024 +0000 Version v12.10.0 commit cb263b4c718776847cfec0956b8c713a8ad6b284 Merge: 0e10bab6bc c5dd2e381a Author: Dan J Miller Date: Wed Dec 18 15:54:28 2024 -0330 Merge remote-tracking branch 'origin/master' into Version-v12.10.0 commit e7f99411c97daa4d6935360c39f1cf975be0b2d1 Merge: f8a1d4fca3 ab8ecae76f Author: Dan J Miller Date: Wed Dec 18 15:34:44 2024 -0330 Merge pull request #29328 from MetaMask/master-sync chore: Master sync following v12.9.2 commit ab8ecae76f14e9d6037b90a55922375db5a761ec Merge: c5dd2e381a f8a1d4fca3 Author: Dan J Miller Date: Wed Dec 18 15:18:43 2024 -0330 Merge origin/main into master-sync commit f8a1d4fca314c890fd03f6d76d98183052a9fe7f Author: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Wed Dec 18 18:45:58 2024 +0100 fix: block tracker stops polling when switching away from the network (#29045) ## **Description** This PR bumps `@metamask/network-controller` and `@metamask/eth-json-rpc-middleware` by a patch version to fix an issue related to `@metamask/eth-block-tracker`, used to listen to new blocks emitted by networks. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29045?quickstart=1) ## **Related issues** Fixes: #17040 ## **Manual testing steps** This only applies to views that show a single chain: since now the wallet home shows all networks, requests will be fired regardless of the globally selected network. To test this, the chain filter in the home should be set to a specific chain, instead of all 1. Add a local ganache network 2. Turn off the local ganache server 3. Observe requests failing when navigating to the home of the wallet (while showing all networks) 4. Filter a single chain which is not the local one (e.g. mainnet) 5. Polling to localhost should stop ## **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/main/.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/main/.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. commit 2258c15fe513bb6a9bc952fc879b357a27842bfd Author: cmd-ob Date: Wed Dec 18 17:44:54 2024 +0000 test: [POM] Migrate add-multiple-tokens test (#29288) ## **Description** * Moving `test/e2e/tests/tokens/add-multiple-tokens.spec.js` to use POM and TS * Adds new generic modal PO for when we just need to click confirm or cancel `test/e2e/page-objects/pages/dialog/dialog.ts` * New PO for add tokens dialog `test/e2e/page-objects/pages/dialog/add-tokens.ts` * Minor updates to asset-list and dapp POs [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29288?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Tests should pass, and test logic should be checked for correctness. ## **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/main/.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/main/.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. commit 19f486158281121a00995ca5befc7684e16d3894 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Wed Dec 18 12:02:46 2024 -0500 refactor: remove duplication of selectors in `selectors/confirm-transaction.js` (#27641) These selectors were duplicated because our circular dependencies were not letting us import them. Now that I've untangled a bunch of files we can import them just fine! commit bdd92438275867c9904cb8d1ec5e1f6c930f706b Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed Dec 18 08:54:55 2024 -0800 fix: Token details should not display zero balance for tokens without marketData (#29299) ## **Description** On main asset list, if a token doesn't have marketData, we do not display the fiat value. On tokenDetails we were falling back to zero balance (this is incorrect) This PR adds a fix to not fallback to zero balance, and to instead simply omit the value. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29299?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29244 ## **Manual testing steps** 1. Add a memcoin without marketData 2. Validate that no fiat value is shown on main asset list 3. Validate that no fiat value is shown on token details (should not display zero value) 4. Ensure nothing results in `NaN` value. ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-17 at 1 39 10 PM Screenshot 2024-12-17 at 1 39 26 PM ### **After** Screenshot 2024-12-17 at 1 34 09 PM Screenshot 2024-12-17 at 1 34 26 PM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 22bcc760ab137d4faf44fdcd27e3d6f1c5bb9a52 Author: George Marshall Date: Wed Dec 18 08:04:16 2024 -0800 chore: fix security settings layout (#29258) ## **Description** This PR improves the layout of the basic security section by aligning the toggle button with the heading text. The changes include: 1. Restructuring the layout using Box components with proper alignment 2. Moving the toggle button to be inline with the heading 3. Improving the visual hierarchy by using proper Typography components 4. Maintaining the description text below with appropriate spacing [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29258?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/26667 ## **Manual testing steps** 1. Go to Settings > Security & Privacy 2. Observe the Basic Configuration section at the top 3. Verify the toggle button is properly aligned with the heading 4. Verify the description text appears below with proper spacing ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/bab9a14c-8f10-4865-b159-5343537a2785 ### **After** https://github.com/user-attachments/assets/b9d02fd8-8e35-4dfa-a00d-b4e6952625df ## **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/main/.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/main/.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. commit 41b46a04daa8f1fa610c65e1a0666a8b7130d4dd Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Wed Dec 18 16:51:16 2024 +0100 test: [POM] Migrate portfolio e2e tests and permission requests tests to TS and Page Object Model (#29274) ## **Description** - Migrate portfolio e2e tests and permission requests e2e tests to TS and Page Object Model ``` test/e2e/tests/portfolio/portfolio-site.spec.ts test/e2e/json-rpc/wallet_revokePermissions.spec.ts test/e2e/json-rpc/wallet_requestPermissions.spec.ts ``` - Remove unused ganach setup [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** ## **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. commit acdaefe8d03d79364171188f09dbc299c48d8e7d Author: Brian Bergeron Date: Wed Dec 18 07:50:24 2024 -0800 chore: remove `NewNetworkInfo` (#29293) ## **Description** In https://github.com/MetaMask/metamask-extension/pull/28765 the `NewNetworkInfo` modal stopped being rendered. This PR removes the component entirely, since it's no longer referenced anywhere. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29293?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-477 ## **Manual testing steps** No changes expected. Switching networks should continue to work. ## **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/main/.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/main/.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. commit 804a56f252b5d65777b183648139ec04cd450270 Author: Brian Bergeron Date: Wed Dec 18 07:50:17 2024 -0800 feat: event when token list is refreshed (#29300) ## **Description** Adds an event when the user refreshes the token list [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29300?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-440 ## **Manual testing steps** - Enable metametrics - Click this refresh button on the tokens tab: Screenshot 2024-12-17 at 2 11 33 PM - Verify an event is emitted. I test this by adding a console.log in `MetaMetricsController.trackEvent`, but in a production build you may be able to check network requests. ## **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/main/.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/main/.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. commit fd9eb4837070abd041c405a94ab78d9c7e755ad8 Author: Jony Bursztyn Date: Wed Dec 18 15:16:19 2024 +0000 feat: center backup toast (#29200) ## **Description** Centers the backup warning toast [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29200?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-13 at 14 54 15 ### **After** Screenshot 2024-12-13 at 14 53 25 Screenshot 2024-12-13 at 14 53 36 ## **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/main/.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/main/.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. commit 86bd5dcfc76bcb1007fbe9f6a8599df0e0dbd39c Author: Jony Bursztyn Date: Wed Dec 18 15:12:29 2024 +0000 fix: close network/accounts toasts when opening the Edit page (#29239) ## **Description** This PR fixes the issue where toasts persist after navigating between `Permissions view` > `Edit networks` or `Edit accounts`. The fix ensures the toast dismisses upon navigating to edit in the Permissions modal. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29239?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27808 ## **Manual testing steps** 1. Connect a dapp and grant permissions. 2. Go to the Connection icon > Permissions view > Accounts > Edit. 3. Change the account selection. 4. Update and verify the toast confirms account permissions were updated. 5. Navigate to Networks > Edit. 6. Change the network selection. 7. Update and verify the toast confirms network permissions were updated. 8. Confirm no stale toast persists after each navigation. ## **Screenshots/Recordings** ### **Before** ![Brave Browser 2024-10-11 at 4 24 18 PM](https://github.com/user-attachments/assets/a011d199-e0cc-40a7-b059-16d808c18964) ### **After** https://github.com/user-attachments/assets/6cdd3b9d-90ce-4435-aea8-cf990b8b985a ## **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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g., pulled and built the branch, ran the app, tested the 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. commit d5335c6329e8cb1e1feac85b08cd248a1573faa3 Author: Frederik Bolding Date: Wed Dec 18 15:10:00 2024 +0100 fix: Remove details option if permission has no description (#29313) ## **Description** Remove details option if permission has no description, for instance `eth_accounts` has no description currently. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29313?quickstart=1) ## **Manual testing steps** 1. Install Ethereum Provider Snap 2. Connect the Snap to an account 3. Go to Snaps settings 4. See that there is no "details" button for the eth_accounts permission commit e87c714283f674bbb45974c18fb66e9cc07bc1f7 Author: Jyoti Puri Date: Wed Dec 18 19:09:02 2024 +0530 fix: Remove use of boolean value for metrics properties (#29315) ## **Description** Remove use of boolean value for metrics property. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3811 ## **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/main/.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/main/.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. commit 5aa29623c173e3fd2ae5c4a65f27e132716b902e Author: Zbyszek Tenerowicz Date: Wed Dec 18 13:31:28 2024 +0100 chore: Update lavamoat to a version with more diff-friendly policy ordering (#29311) ## **Description** This lavamoat update brings a different sorting comparator for policy.json files that will produce more readable diffs. This PR has 2 commits - one that reorders the policy without making any changes and another that updates lavamoat packages on top of that. For better clarity on the policy.json files, inspect each commit separately. This is going to be hard to review and merge because conflict resolution requires a redo. Gonna have to schedule it carefully. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29311?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. run a local policy update and verify the diff is not ridiculously large (that would mean the update failed to apply properly after policy got reordered here) ## **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/main/.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/main/.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 commit c4429bc98352e5021e06e47a747f8555f8cb3cdd Author: Charly Chevalier Date: Wed Dec 18 11:57:18 2024 +0100 refactor: use new `@metamask/keyring-api` layout (split packages) (#28861) ## **Description** Bumping accounts related dependencies + use the new `keyring-api` layout. Those tests requires a higher timeout (which might not be true once merged to `main`) `flags = {"circleci": {"timeoutMinutes": 35}}` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28861?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** N/A ## **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/main/.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/main/.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: Howard Braham Co-authored-by: MetaMask Bot Co-authored-by: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> commit b018c81fb51c796294da8ea0f4ec618b30793981 Author: Jyoti Puri Date: Wed Dec 18 14:59:35 2024 +0530 fix: Fix icon alignment in signature pages message section (#29284) ## **Description** Fix icon alignment in signature pages message section. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28793 ## **Manual testing steps** 1. Go to test dapp 2. Submit permit 3. Check expand and copy icons position in message section ## **Screenshots/Recordings** Screenshot 2024-12-17 at 9 06 53 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 26f7c5adffb1b30d9d896f262bd2818abc299d1e Author: Brian Bergeron Date: Wed Dec 18 00:54:12 2024 -0800 fix: 'Metamask' casing on token details page (#29250) ## **Description** Fixes the casing of 'Metamask' to 'MetaMask' on the token details page. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29250?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-399 ## **Manual testing steps** 1. Click on an erc20 token 2. Verify token lists section uses casing 'MetaMask' ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-16 at 1 27 31 PM ### **After** Screenshot 2024-12-16 at 1 27 09 PM ## **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/main/.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/main/.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. commit d630827718010c559d678909324b5cf04d503dca Author: Brian Bergeron Date: Wed Dec 18 00:53:54 2024 -0800 feat: add eth native icon for zora network (#29257) ## **Description** The zora network uses ETH as its native token, so it can have an ETH icon. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29257?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Add zora network 2. Verify native eth asset has eth icon ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-16 at 2 55 29 PM ### **After** Screenshot 2024-12-16 at 2 55 08 PM ## **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/main/.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/main/.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. commit 9020abff2bbe13d489f68d5eea27ec1a6dc9d263 Author: Danica Shen Date: Wed Dec 18 00:16:10 2024 +0000 fix(26772): improve helptext for sending NFTs (#29296) ## **Description** Helper text for sending NFTs to another address is not accurate. We got feedback from design to implement the following: ``` Balance: 1 NFT or Balance: [2-1000000] NFTs ``` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29296?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/26772 ## **Manual testing steps** 1. Select an NFT 2. Send NFT to another address 3. Check the message ## **Screenshots/Recordings** ### **Before** 2 ### **After** Screenshot 2024-12-17 at 16 34 22 Screenshot 2024-12-17 at 16 34 42 ## **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/main/.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/main/.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. commit 80ec747c335cb7c932bc5ae4f4b8982ef21664dd Author: Nidhi Kumari Date: Tue Dec 17 22:42:39 2024 +0000 fix: updated message string in onboarding screen (#29286) This PR is to update the onboarding screen message with proper punctuation ## **Related issues** Fixes: #26763 ## **Manual testing steps** 1. Go to onboarding 2. Check the message has proper punctuation on this screen ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-17 at 4 40 06 PM](https://github.com/user-attachments/assets/94bf0296-b412-460f-a136-034264d07140) ### **After** ![Screenshot 2024-12-17 at 4 39 51 PM](https://github.com/user-attachments/assets/bea72254-5479-4252-b2dc-03f0dac2316f) ## **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/main/.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/main/.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. commit 241c9d8b9ac386c6b9a5d934bb17ff3fe7872f17 Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Tue Dec 17 10:19:15 2024 -0800 fix: sentry e2e test for bridge tokens loading status (#29285) ## **Description** Fixes sentry tests for bridge token loading status in Firefox [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29285?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/main/.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/main/.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. commit 460536a587b34022969d928df604f16a22ce6e1d Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Tue Dec 17 18:02:27 2024 +0100 test: [POM] Create base classes and methods for bitcoin e2e tests (#29235) ## **Description** - Create a Bitcoin home page class and methods. The locators on homepage for Bitcoin accounts are different from the locators for other accounts. I think the best way to implement this is to create a `BitcoinHomepage` class that extends from `Homepage`. This approach will be flexible and easier to implement when we have new modifications for Bitcoin account functionalities. - Migrate bitcoin account e2e tests to TS and Page Object Model ``` test/e2e/flask/btc/btc-account-overview.spec.ts test/e2e/flask/btc/btc-dapp-connection.spec.ts test/e2e/flask/btc/btc-experimental-settings.spec.ts ``` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** ## **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> commit ae1520db53e8281a79f7f6d257441bb6632e41b0 Author: Alejandro Garcia Anglada Date: Tue Dec 17 17:59:17 2024 +0100 fix: remove network picker from non evm accounts (#29247) ## **Description** Removes the new network picker from the non-EVM accounts token list [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29247?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Enable the Solana account via Settings > Experimental > Enable Solana account 2. Create a Solana account from the account-list menu ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-16 at 20 53 34 ### **After** Screenshot 2024-12-16 at 21 02 08 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit da16971b40660d5eaeea5818091c717fb8f685c5 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Tue Dec 17 12:18:50 2024 -0500 refactor: move `getCurrentCurrency` from `ui/selectors/selectors.js` to `ui/ducks/metamask/metamask.js` (#27648) This change is related to circular dependency work; no runtime code has changed. QA is not required. This PR moves `getCurrentCurrency` into `ui/ducks/` instead of keeping it in `ui/selectors/` because `ui/ducks/metamask/metamask.js` already exports a `getNativeCurrency`, so it seems to fit in well there. commit 6fe17a04cb37b9b54999a071b3a55982a30533fa Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Tue Dec 17 08:40:22 2024 -0800 chore: quote timeout treatment for bridging (#29172) ## **Description** when the quote is older than 30s - if the user has sufficient balance, hide quote and show a button that restarts polling on click - if the user doesn't have sufficient balance, keep showing the quote quote but display a button that restarts polling on click [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29172?quickstart=1) ## **Related issues** Fixes: N/A ## **Manual testing steps** 1. Request quotes 2. Wait for ~3 minutes after fetching starts 3. Quote should time out 4. Clicking the Fetch button should restart polling ## **Screenshots/Recordings** ### **Before** N/A ### **After** ![Screenshot 2024-12-12 at 1 50 40 PM](https://github.com/user-attachments/assets/1a0e2c1e-1260-40fb-bd10-2f78ac61b1e1) ![Screenshot 2024-12-12 at 1 46 18 PM](https://github.com/user-attachments/assets/5836cc59-dd32-4afe-9213-6cadad3e7fab) ## **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/main/.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/main/.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. commit fe809431e6cbfdd7d18a2d5ba90570d6c5ae1337 Author: George Marshall Date: Tue Dec 17 08:40:14 2024 -0800 fix: add focus state to swaps input for improved accessibility (#29252) ## **Description** The current swaps input implementation hides the focus indicator, making it inaccessible for users with vision impairments and those relying on keyboard navigation. This creates barriers for: - Users with low vision who need clear visual indicators - Users with motor impairments who navigate via keyboard - Users of screen magnification tools who need to track their position This PR improves accessibility by: 1. Removing 'outline: none' from the swaps input to restore focus visibility 2. Adjusting padding (from 0 to 4px) and margins to maintain visual consistency 3. Adding proper focus visibility for keyboard navigation These changes ensure compliance with [WCAG 2.1 Success Criterion 2.4.7 (Focus Visible)](https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html), which requires that keyboard focus indicators be visible and distinguishable. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/26662 ## **Manual testing steps** 1. Navigate to the swaps page 2. Use Tab key to move focus to the input field 3. Verify that a visible focus indicator appears around the input 4. Ensure the text alignment and spacing remain consistent 5. Test that the input remains right-aligned with the new padding ## **Screenshots/Recordings** ### **Before** Input field shows no focus indicator when navigating with keyboard https://github.com/user-attachments/assets/961ea815-2014-47e3-b4f2-514197fae9dd ### **After** Input field shows clear focus indicator when navigating with keyboard, with padding-right: 4px https://github.com/user-attachments/assets/27a27dd5-51f3-473a-ad5b-bbde22caa7e2 ## **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/main/.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 ## **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 commit 8ca78e7fcbbf921c24abf8e415089e5a45f7e7fe Author: Danica Shen Date: Tue Dec 17 16:04:04 2024 +0000 fix(27140): fix styles for Slippage Tolerance Button Group in Transaction Settings Modal (#29246) This PR aims to fix a broken visual display for Slippage Tolerance Button Group in Transaction Settings Modal. After inspecting the styles for `ButtonGroup`, which is the component applies to this part of UI, has overwritten issues of styles between `button-group__button` and `radio-button`. Hence the solution is to specify css based on variant more clearly and avoid the overwrite problem. Screenshot 2024-12-16 at 18 59 19 ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29246?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27140 ## **Manual testing steps** 1. Click "Swap" from the home screen. 2. In the swap interface, click the cog icon in the top right to open the transaction settings modal. 3. Observe the slippage tolerance button group—it's visually broken. ## **Screenshots/Recordings** ### **Before** 1 ### **After** Screenshot 2024-12-16 at 19 19 04 ## **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/main/.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/main/.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. commit 18bcad9eb83d03f6bb69a312975881503d3aae21 Author: Jyoti Puri Date: Tue Dec 17 21:12:35 2024 +0530 fix: Fix low gas display label in speed-up modal (#29277) ## **Description** Fix issue that `undefined` is sometime visible on speedup modal. It was actually visible in edge case when `10% low` is less than low estimate. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28840 ## **Manual testing steps** 1. Go to test dapp 2. Submit a transaction with less gas so that it takes longer to complete 3. Open speedup modal and switch to `10% less` gas estimate 4. You should not see undefined string ## **Screenshots/Recordings** Screenshot 2024-12-17 at 7 05 58 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit a31d80803815fd5c7ec22f0ca2a887ee68bd17a3 Author: seaona <54408225+seaona@users.noreply.github.com> Date: Tue Dec 17 16:18:39 2024 +0100 test: [POM] Migrate Send tx Revoke Permissions spec (#29273) ## **Description** - Migrates `test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.js` spec to POM and ts - Updates method classes to accommodate new functions needed in this spec - [A new bug](https://github.com/MetaMask/metamask-extension/issues/29272) has been discovered thanks to this migration, and a new TODO has been added, to add a new spec to cover that flow, once the bug is fixed [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29273?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29276 ## **Manual testing steps** 1. Check ci 2. Run test ` yarn test:e2e:single test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.ts --browser=chrome --leave-running=true` ## **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/main/.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/main/.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. commit 0ef0d54f5c0e1cafad3dd8c18391f192dc1da61e Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Tue Dec 17 14:55:15 2024 +0000 fix: increase gas limit validation threshold (#29264) ## **Description** This PR aims to increase the gas limit validation threshold to 30M. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29264?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/21927 ## **Manual testing steps** 1. send a transaction 2. edit gaslimit on advance tab ## **Screenshots/Recordings** [Screencast from 17-12-2024 08:51:19.webm](https://github.com/user-attachments/assets/a8ff1e67-681f-4d6f-9dd8-b0ff79d86baa) ### **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/main/.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/main/.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. commit b9c57634048694a8cfba9e6794747a49bbd0a768 Author: seaona <54408225+seaona@users.noreply.github.com> Date: Tue Dec 17 14:45:38 2024 +0100 test: [POM] Privacy Mode spec (#29263) ## **Description** - Migrates `test/e2e/tests/privacy-mode/privacy-mode.spec.js` spec to POM and ts - Updates method classes to accommodate new functions needed in this spec - Fixes several spec issues that where found during the migration (see comments) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29263?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29262 ## **Manual testing steps** 1. Check ci is green 2. Run test `yarn test:e2e:single test/e2e/tests/privacy-mode/privacy-mode.spec.ts --browser=chrome --leave-running=true` ## **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/main/.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/main/.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. commit 0e10bab6bc28bb83916c14426fa0439f69903418 Author: Alejandro Garcia Anglada Date: Tue Dec 17 12:39:47 2024 +0100 fix: nanoid audit issue (#29268) ## **Description** Fixes `nanoid` audit [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29268?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/main/.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/main/.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 commit 37e79458f3fb8299be2c5d583796fd79f28cb60b Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Tue Dec 17 09:14:17 2024 +0100 test: [POM] Create permission pages base classes and methods for e2e tests (#29097) ## **Description** - Create permission pages base classes and methods - Migrate permission e2e tests to TS and Page Object Model ``` test/e2e/tests/multichain/all-permissions-page.spec.js test/e2e/tests/multichain/permission-page.spec.js ``` [![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/29099 ## **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> commit c5dd2e381a72a9ebe9d5af03c666b2cc27f3b0c5 Merge: 8f3fb205f4 4070d34045 Author: Dan J Miller Date: Mon Dec 16 19:03:15 2024 -0330 Merge pull request #29160 from MetaMask/Version-v12.9.2 Version v12.9.2 RC commit edd7b25ba7b912768341b6f34c369b1ca409b1c4 Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Mon Dec 16 14:06:48 2024 -0800 feat: cross-chain swaps ui re-design (#28373) ## **Description** Changes include - layout and styling updates for the landing screen - re-styling input fields, including asset pickers - integrates the multichain asset list into bridge's token list generator - style + copy updates in quote display components - advanced settings modal - input and quote validation alerts - bug fixes This depends on 3 open PRs 1. tracking events: https://github.com/MetaMask/metamask-extension/pull/28713 2. multichain AssetPicker: https://github.com/MetaMask/metamask-extension/pull/28975 3. NetworkAvatar style update: https://github.com/MetaMask/metamask-extension/pull/28976 Changes from #2 and #3 are currently included here, but will mainly just contain bridge component updates after those are merged. Since those are being reviewed by external teams, reviews on this one should be focused on bridge-specific files/directories Figma: https://www.figma.com/design/IuOIRmU3wI0IdJIfol0ESu/Cross-Chain-Swaps?node-id=7-24563&node-type=canvas&m=dev [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28373?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1451 ## **Manual testing steps** 1. Set these in .metamaskrc and run `yarn webpack --watch` ``` SEGMENT_HOST='http://localhost:9090' SEGMENT_WRITE_KEY='FAKE' BRIDGE_USE_DEV_APIS=1 ``` 2. Try out the Bridge page, asset picker, submitting txs, viewing quotes etc 3. Open the background console network tab to see emitted events ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-11 at 3 02 20 PM](https://github.com/user-attachments/assets/826f7cba-202d-4a52-b15f-ca16c29d7a96) ### **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: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Co-authored-by: Jack Clancy commit 4070d3404574f5bf4e792ea14f667cee91b656fc Author: Brian Bergeron Date: Mon Dec 16 13:57:50 2024 -0800 chore(cherry-pick): migration to remove tokens with null decimals (#29251) Cherry picks https://github.com/MetaMask/metamask-extension/pull/29245 to v12.9.2 Co-authored-by: Salim TOUBAL commit 7985a8904560899338ff7b284e3c5ad0304c0218 Author: Dan J Miller Date: Mon Dec 16 17:59:13 2024 -0330 Update changelog for v12.9.2 (#29249) commit f59ba9a9ac0ec287378edba448952701ea04c1b1 Author: Salim TOUBAL Date: Mon Dec 16 21:43:28 2024 +0100 fix: Add migration to remove tokens with null decimals and log affected tokens (#29245) ## **Description** This PR introduces a new migration (version 135) to remove tokens with decimals === null from the following properties within the TokensController state: - `allTokens` - `allDetectedTokens` - `tokens` - `detectedTokens` Additionally, it logs the addresses of the affected tokens using global.sentry?.captureMessage before removing them from the state. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29245?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. mock tokens on the state and set decimals to null on the main branch 2. switch branch , the token should be hidden ## **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/main/.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/main/.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. commit 58ca7af401213bb35783f2c9cc4fae8a0fec69cf Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Mon Dec 16 12:35:54 2024 -0500 build(webpack): fix `--zip` in Node.js 22 by cloning assets into `Uint8Array`s before zipping (#29177) Use a copy of the Buffer via `Buffer.from(asset)`, as Zip will *consume* it, which breaks things if we are compiling for multiple browsers at once. `Buffer.from` uses the internal pool, so it's superior to `new Uint8Array` if we don't need to pass it off to a worker thread. Additionally, in Node.js 22+ a Buffer marked as "Untransferable" (like ours) can't be passed to a worker, which `AsyncZipDeflate` uses. See: https://github.com/101arrowz/fflate/issues/227#issuecomment-2540024304 This can probably be simplified to `zipFile.push(Buffer.from(asset), true);` if the above issue is resolved. This fix should hopefully unblock https://github.com/MetaMask/metamask-extension/pull/28368 commit 207245fb240ff41362bb90a9db83ca7faef1a2f7 Author: Frederik Bolding Date: Mon Dec 16 18:20:58 2024 +0100 fix: Prevent loss of focus when using UI inputs without labels (#29238) ## **Description** Prevents loss of focus when using UI inputs without labels due to the layout of the children being different. This PR fixes it by using a query selector instead of assuming the child to be a index 0. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29238?quickstart=1) ## **Manual testing steps** ```ts import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { Box, Text, Bold, Input, Field } from '@metamask/snaps-sdk/jsx'; /** * Handle incoming JSON-RPC requests, sent through `wallet_invokeSnap`. * * @param args - The request handler args as object. * @param args.origin - The origin of the request, e.g., the website that * invoked the snap. * @param args.request - A validated JSON-RPC request object. * @returns The result of `snap_dialog`. * @throws If the request method is not valid for this snap. */ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request, }) => { switch (request.method) { case 'hello': return snap.request({ method: 'snap_dialog', params: { type: 'confirmation', content: ( Hello, {origin}! This custom confirmation is just for display purposes. But you can edit the snap source code to make it do something, if you want to! ), }, }); default: throw new Error('Method not found.'); } }; export const onUserInput = async ({ id, event }) => { await snap.request({ method: 'snap_updateInterface', params: { id, ui: ( Hello world! This custom confirmation is just for display purposes. But you can edit the snap source code to make it do something, if you want to! {event.value ?? ''} ), }, }); }; ``` commit 149c7c9edf79e62a5003e494b721a873020e0e73 Author: Frederik Bolding Date: Mon Dec 16 18:07:50 2024 +0100 fix: `FileInput` displaying wrong outside of `Field` (#29243) ## **Description** The `FileInput` was broken outside of its use in `Field` due to the mapping logic being flawed. This PR fixes this by correcting the mapper. This issue way not was not caught due to the component not being E2E tested outside a Field. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29243?quickstart=1) ## **Manual testing steps** ```ts import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { Box, Text, Bold, Input, Field, FileInput, } from '@metamask/snaps-sdk/jsx'; /** * Handle incoming JSON-RPC requests, sent through `wallet_invokeSnap`. * * @param args - The request handler args as object. * @param args.origin - The origin of the request, e.g., the website that * invoked the snap. * @param args.request - A validated JSON-RPC request object. * @returns The result of `snap_dialog`. * @throws If the request method is not valid for this snap. */ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request, }) => { switch (request.method) { case 'hello': return snap.request({ method: 'snap_dialog', params: { type: 'confirmation', content: ( Hello, {origin}! This custom confirmation is just for display purposes. But you can edit the snap source code to make it do something, if you want to! ), }, }); default: throw new Error('Method not found.'); } }; ``` commit 67d1a12386723f7dbc449b18836cfee22eb1dbcb Author: seaona <54408225+seaona@users.noreply.github.com> Date: Mon Dec 16 18:04:09 2024 +0100 test: [POM] Basic functionality spec migration (#29229) ## **Description** - Migrates `test/e2e/tests/privacy/basic-functionality.spec.ts` to POM and ts - Updates classes to accommodate more methods needed for the spec above [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29229?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29230 ## **Manual testing steps** 1. Check ci passes 2. Run manually ` yarn test:e2e:single test/e2e/tests/privacy/basic-functionality.spec.ts --browser=chrome --leave-running=true` ## **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/main/.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/main/.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. commit 6c4cc0efa069d7f0c52da35edbc1bcfd1adcb29c Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Mon Dec 16 12:01:51 2024 -0500 fix: correct a typo (#29207) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29207?quickstart=1) This PR fixes a typo that uses a non existent field on an object. ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 78ea51a0113eb47f447841d3c2a85b45a819032f Author: Howard Braham Date: Mon Dec 16 20:38:44 2024 +0530 ci: add flags.circleci.timeoutMinutes (#29156) ## **Description** 1. Allows you to customize the E2E timeout by writing this in the PR description: `flags = {"circleci": {"timeoutMinutes": 35}}` 2. change to git-diff-default-branch.ts to be able to run it as a direct script or as a library 3. changes the default value for the merge queue 4. increases runAll.js's time-default from 30s to 50s [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29156?quickstart=1) --------- Co-authored-by: Mark Stacey commit 45c7bfd515f5d0758a3a7cc2c3b7139484f508e0 Author: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com> Date: Mon Dec 16 14:18:27 2024 +0100 feat: added solana to flask build (#29147) ## **Description** Added Solana feature on Flask build [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29195?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** - Open Experimental settings and enable Solana - You should see Solana option when creating 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/main/.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/main/.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: Charly Chevalier commit fb0b414cd026f5a67b4ca39de878b8a36c0eb9bb Author: Mathieu Artu Date: Mon Dec 16 13:45:39 2024 +0100 feat: decouple authentication logic from `useMetametrics` (#29070) ## **Description** This PR decouples the authentication logic that was incorporated into the `useMetametrics` hook. It now lives in `metamask-controller.js`. Auth sign-in / sign-out will now be performed whenever the `MetaMetricsController`'s state changes to signify that a user changed the value of `participateInMetaMetrics` Scenarios: - Sign-in will be performed if a user enables either metametrics or profile syncing - Sign-out will be performed if a user disables metametrics & has profile syncing turned off - Alternatively, sign-out will be performed if a user disables profile syncing & has metametrics turned off [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29070?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to the settings page 2. Turn off profile syncing and metametrics 3. Turn only metametrics back on 4. Look into the network tab and see requests to `https://authentication.api.cx.metamask.io` ## **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/main/.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/main/.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. commit 98be9681decfbf74c86715f4a50f214d3f3c7de3 Author: Mathieu Artu Date: Mon Dec 16 11:13:04 2024 +0100 chore: bump profile-sync-controller to version v3.1.1 (#29100) ## **Description** This PR bumps `@profile-sync-controller` to version `v3.1.1`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29100?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. No 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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: MetaMask Bot commit 02cb6d6e0cd74868332f28e9fb631b49c1c43c89 Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri Dec 13 17:17:12 2024 +0000 fix (cherry-pick): increase signing or submitting alert severity to danger (#29140) (#29192) ## **Description** Cherry-pick of #29140 for release `12.9.2`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29192?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29138 ## **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/main/.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/main/.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. commit bb1c77e80d547ded8307a47038af95883b693d5a Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Fri Dec 13 11:16:10 2024 -0500 chore: replace portfolio links to bridge with native bridge exp (#29175) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29175?quickstart=1) This PR replaces a link in Swaps to the Portfolio Bridge with a link to the native Bridge experience. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to Swaps 2. Click on the Bridge link 3. Get directed to native Bridge exp ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/6f27cefd-d43b-4872-a523-5b2d868ea09c ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit b7c2198c460b045b90c24dbf1d74bee09aa3e8cc Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Fri Dec 13 11:16:04 2024 -0500 fix: crashing after submitting tx (#29203) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29203?quickstart=1) This PR fixes an issue where the app would crash after submitting a bridge transaction. ## **Related issues** Fixes: ## **Manual testing steps** 1. Do a bridge tx 2. Should be routed to activity list and not crash ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit f46bdefb3ef59f42ed7986a75220f72620a08f95 Author: Bryan Fullam Date: Fri Dec 13 22:50:20 2024 +0700 feat: show info message when a HW user declines a tx (#29198) ## **Description** When a hardware wallet user declines a transaction during the bridge process, they are redirected back to the bridge setup page. This PR adds an info message to clarify why they were redirected and prompt them to get a new quote. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29198?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to bridge with a hardware wallet 2. Attempt bridge 3. Decline transaction on hardware wallet 4. See info message when you're redirected ## **Screenshots/Recordings** ### **Before** ### **After** ![Screenshot 2024-12-13 at 14 53 00](https://github.com/user-attachments/assets/abbeca68-39d9-4926-b50f-5321949f58ec) ## **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/main/.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/main/.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. commit c671bf3aed17bf28ee315ce04d96f32c0116e77d Author: Jongsun Suh Date: Fri Dec 13 10:08:12 2024 -0500 chore: Bump `reselect` to `^5.1.1` for heterogenously-typed selectors support (#29094) ## Motivation #### Homogenous selector types: ```ts const selectorOne = (state: State) => state.something; const selectorTwo = (state: State) => state.other; createSelector([selectorOne, selectorTwo], () => ...); ``` #### Heterogenous selector types: ```ts const selectorOne = (state: { something: string }) => state.something; const selectorTwo = (state: { other: number }) => state.other; createSelector([selectorOne, selectorTwo], () => ...); ``` Support for heterogenous typing is essential for selectors to function properly, because selectors must be both mergeable and atomic. Without heterogenous selectors, these becoming conflicting objectives. - **Mergeable:** - Without heterogenous typing for selectors, the size of the state type for all selectors tend to inflate. This is because a selector's state must be a supertype for the intersection of the state types of all of its merged selectors, including selectors nested in the definitions of merged selectors. - Eventually, many selectors end up being defined with a state type that is close to the entire Redux state. - Paradoxically, selectors that only need access to very few properties end up needing to have the widest state type, because they tend to be merged into the most selectors across several nested levels. - **Atomic:** - When selectors are actually invoked, including in test files, it's not always practical to prepare and pass in a very large state object. - It's both safer and more convenient to restrict the state being passed into the selector to the minimum size required for the selector to function. - This requirement becomes incompatible with the mergeability requirement if all selectors must share a common state type. Enabling merged selectors to accept different, even disjoint state types resolves this issue. > [!NOTE] > See the [`MultichainState`](https://github.com/MetaMask/metamask-extension/blob/4f970df0acec3e3bc80da08373aa3b16f23aae41/ui/selectors/multichain.ts#L53) type for an example of a bloated selector state type which will become unnecessary with this update. ## **Description** - `reselect@4.0.0` supports heterogenous typing for selector inputs to `createSelector` and `createDeepEqualSelector`. - https://github.com/reduxjs/reselect/issues/351 - https://github.com/reduxjs/reselect/pull/274 - https://github.com/reduxjs/reselect/pull/315 - Upgrade to `^5.1.1` (latest) is necessary to fix type issues in `4.0.0` - https://app.circleci.com/jobs/github/MetaMask/metamask-extension/4320173 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29094?quickstart=1) ## **Related issues** - Blocks TypeScript conversion of selectors for https://github.com/MetaMask/MetaMask-planning/issues/2894. - https://github.com/MetaMask/metamask-extension/pull/29014 ## **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/main/.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/main/.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 commit 57f5a6bc9c29b92803493c44b66140315c310ce4 Author: Frederik Bolding Date: Fri Dec 13 14:02:07 2024 +0100 fix: Disable link out modal for preinstalled Snap links (#29142) ## **Description** Disables the link out modal for links displayed by preinstalled Snaps, reducing the UX friction. Also memoizes a frequently used selector in Snaps UI components. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29142?quickstart=1) ## **Related issues** Closes https://github.com/MetaMask/snaps/issues/2936 ## **Manual testing steps** 1. Go to the Solana send flow and fill out the inputs 2. Proceed to the review screen 3. Click the links on the review screen and notice that they open right away commit 788e4a23d7db808acc4ca46e89b194b0dc617b8d Author: OGPoyraz Date: Fri Dec 13 12:36:10 2024 +0100 fix: Fix not set `estimate_type` and rename `dappSuggested` transaction metric property value (#29052) ## **Description** This PR aims to rename `dappSuggested` transaction event value to `dapp_proposed`. Also it fixes the `estimate_type` property to `default_estimate` on not EIP1559 transactions. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29052?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3595 ## **Manual testing steps** N/A ## **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/main/.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/main/.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. commit 9f0571a286b691ace297109d0162232fc7d70571 Author: Brian Bergeron Date: Fri Dec 13 02:41:34 2024 -0800 chore(cherry-pick): Use correct selector to pull name from non-popular networks (#29164) cherry picks https://github.com/MetaMask/metamask-extension/pull/29121 to 12.9.2 Co-authored-by: Nick Gambino <35090461+gambinish@users.noreply.github.com> commit 37e51c10c78424ab7d1bec80078099278ead40f2 Author: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> Date: Fri Dec 13 05:40:50 2024 -0500 fix: [cherry-pick] add websocket support for c2 detection (#28782) (#29173) cherry-picks #28782 ## **Description** This pull request adds WebSocket support to the MetaMask extension's phishing detection functionality. Scammers have started using WebSocket connections for command-and-control (C2) operations to bypass traditional HTTP-based phishing detection. This PR allows the extension to intercept and block WebSocket handshake requests (`ws://` and `wss://`) in addition to HTTP/HTTPS requests. The key changes include: 1. Adding WebSocket schemes (`ws://*/*` and `wss://*/*`) to the `urls` filter in `background.js`. 2. Updating the `manifest.json` to include WebSocket permissions in the `host_permissions` field. This ensures that malicious WebSocket connections can be detected and blocked. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28782?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3788 ## **Manual testing steps** 1. Navigate to `example.com` 2. Initiate a WebSocket connection to a known safe domain (e.g., `wss://example.com`) and verify it works as expected by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://example.com/")` 3. Attempt a WebSocket connection to a domain flagged as phishing, and verify the connection is blocked and appropriate warnings are displayed by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://walietconnectapi.com/")` ## **Screenshots/Recordings** ### **Before** No support for detecting WebSocket phishing connections. --- ### **After** WebSocket phishing connections are detected and blocked during the handshake phase. ## **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: Mark Stacey commit 21dc6ad4420cb576f24a4c8ae3b766c27f81358c Author: OGPoyraz Date: Fri Dec 13 11:32:47 2024 +0100 chore: cherry-pick `29131` (#29185) ## **Description** This PR cherry-picks https://github.com/MetaMask/metamask-extension/commit/acdf7c6579e2d1ac06e2ab4f2a0917d616df0403 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29185?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3783 ## **Manual testing steps** See original PR ## **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/main/.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/main/.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. commit 527717e8c7ef60905f74bcb4d8bd9e80d4d60f30 Author: Pedro Figueiredo Date: Fri Dec 13 10:22:12 2024 +0000 feat: Migrate signature e2e tests for redesigned screens (#29023) ## **Description** A number of e2e tests uses `tempToggleSettingRedesignedConfirmations` to programatically disable the redesigned signature confirmations. This PR duplicates those tests and tucks them under an "Old confirmation screens" describe block. All original tests are correspondingly moved to under a "Redesigned confirmation screens" describe block, and the helper method is removed. Then each test is modified enough so its assertions can pass again. The "old confirmation screens" tests will be removed in https://github.com/MetaMask/MetaMask-planning/issues/3029. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29023?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3718 ## **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/main/.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/main/.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. commit 71cafe7411bd37d3a0ed88040509f7cb6caa2477 Author: Frederik Bolding Date: Fri Dec 13 11:03:28 2024 +0100 fix: Swap out Spinner component exposed to Snaps (#29143) ## **Description** Swap out the `Spinner` component exposed to Snaps for the `Preloader` component. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29143?quickstart=1) ## **Screenshots/Recordings** ![image](https://github.com/user-attachments/assets/d4c9a712-67a8-4d1f-b2f2-8d8550d87f27) commit 72e4987eee2e116998af31b19ca87f3f25f3a6fb Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri Dec 13 10:00:59 2024 +0000 fix: increase signing or submitting alert severity to danger (#29140) ## **Description** This PR addresses an issue where users are currently able to submit multiple Smart Transaction (STX) requests while one is still pending in the redesigned confirmation flow. This functionality is already correctly implemented in the legacy confirmation flow, and the redesigned flow is updated to match this behaviour. **Changes Introduced:** - Updates the alert text to match the copy used in the legacy confirmation flow. - Increases the severity of the alert from `Warning` to `Danger`. - Ensures that dangerous banner alerts block the confirm button to avoid user actions until resolved. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29140?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29138 ## **Manual testing steps** 1. Submit a STX 2. Trigger a new transaction ## **Screenshots/Recordings** [wait_submitted_tx.webm](https://github.com/user-attachments/assets/0eff285e-46b1-409f-8041-4c99fef89839) ### **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/main/.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/main/.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. commit 98be28228e3666e2660ffcd6c73163a11822e727 Author: Monte Lai Date: Fri Dec 13 11:54:09 2024 +0800 feat: display other currencies for non evm accounts. (#28963) ## **Description** This PR enables the user's selected currency to be displayed for non evm accounts. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28963?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/611 ## **Manual testing steps** 1. Create a BTC account with mainnet funds on flask. 2. Go to settings then general and select a currency 3. See that the currency is displayed in the overview and account list menu ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-12 at 20 37 44](https://github.com/user-attachments/assets/cfed63ed-1c97-46f5-984f-bf0ef35959a2) ![Screenshot 2024-12-12 at 20 37 36](https://github.com/user-attachments/assets/87f58158-7a10-42c4-aad7-6d732b6d7142) ![Screenshot 2024-12-12 at 20 37 33](https://github.com/user-attachments/assets/e1aeb6e2-0fe4-47bb-aa7a-bdd887770c9a) ### **After** ![Screenshot 2024-12-12 at 20 36 27](https://github.com/user-attachments/assets/59954640-49a2-45ae-8712-dbecde8a1c75) ![Screenshot 2024-12-12 at 20 36 23](https://github.com/user-attachments/assets/7697ad79-d134-4da2-a41c-4c99c42891a5) ## **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/main/.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/main/.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. commit 75f68267ee25ae5a54fef85b2977328583b7a082 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 19:33:47 2024 -0500 fix: send user to native bridge experience rather than portfolio when… (#29169) … carousel clicked ## **Description** This PR sends users to the in app Bridge experience rather than linking them out to the Porfolio when the Bridge cards in the carousel is clicked. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29169?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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit adf4083d1c561498018d33330bc68cd185d77d0a Merge: 16d169309c fe12fbe34a Author: Dan J Miller Date: Thu Dec 12 21:06:52 2024 -0330 Merge pull request #28912 from MetaMask/master-sync chore: Master sync PR following v12.8.0 and v12.9.0 commit fe12fbe34a14f6e1f8d997ba80ad7802f4c75863 Merge: 8f3fb205f4 16d169309c Author: Dan J Miller Date: Thu Dec 12 20:48:36 2024 -0330 Merge origin/main into master-sync commit 16d169309c13ff3901c65fcfd78677ad9b7682ac Author: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> Date: Thu Dec 12 16:16:37 2024 -0500 feat: add websocket support for c2 detection (#29150) ## **Description** This pull request adds WebSocket support to the MetaMask extension's phishing detection functionality. Scammers have started using WebSocket connections for command-and-control (C2) operations to bypass traditional HTTP-based phishing detection. This PR allows the extension to intercept and block WebSocket handshake requests (`ws://` and `wss://`) in addition to HTTP/HTTPS requests. The key changes include: 1. Adding WebSocket schemes (`ws://*/*` and `wss://*/*`) to the `urls` filter in `background.js`. 2. Updating the `manifest.json` to include WebSocket permissions in the `host_permissions` field. This ensures that malicious WebSocket connections can be detected and blocked. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28782?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3788 ## **Manual testing steps** 1. Navigate to `example.com` 2. Initiate a WebSocket connection to a known safe domain (e.g., `wss://example.com`) and verify it works as expected by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://example.com/")` 3. Attempt a WebSocket connection to a domain flagged as phishing, and verify the connection is blocked and appropriate warnings are displayed by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://walietconnectapi.com/")` ## **Screenshots/Recordings** ### **Before** No support for detecting WebSocket phishing connections. --- ### **After** WebSocket phishing connections are detected and blocked during the handshake phase. ## **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: Mark Stacey commit 1ea15697c372205567a9d657d239540120e9e9a1 Author: George Marshall Date: Thu Dec 12 13:15:41 2024 -0800 feat: Adding Skeleton component to component-library (#29082) Replacement of https://github.com/MetaMask/metamask-extension/pull/22278 ## **Description** Initial draft of the `Skeleton` component to review with the @MetaMask/design-system-engineers based on the findings in the [audit and component discovery sync](https://www.figma.com/file/bA81b0VvoGRgNS4E6059Pa/Loaders%2FSkeleton-Audit-and-Insight-Report?type=whiteboard&node-id=117%3A26&t=AnzMgSbVjzePPMmA-1) ## **Related issues** Fixes: #20874 ## **Manual testing steps** 1. Go to the latest storybook build on this PR 2. Search `Skeleton` 3. Check the docs, stories and controls ## **Screenshots/Recordings** ### **Before** N/A ### **After** https://github.com/user-attachments/assets/19e46ae0-d0ae-440a-9da4-99b037b6686b Colors have been adjusted see [this comment](https://github.com/MetaMask/metamask-extension/pull/29082/files#r1882837341) ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [x] In case it's not yet "ready for review", I've set it to "draft". - [x] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit ea75bf82115b7eaf3eb8fd929633e659f43e249b Author: George Marshall Date: Thu Dec 12 13:10:50 2024 -0800 feat: add background-muted color to design system (#29117) ## Description This PR adds support for the new `background-muted` color introduced in @metamask/design-tokens v4.2.0. ## Related issues Fixes: N/A ## Manual testing steps 1. Open Storybook 2. Navigate to Box component stories 3. Verify new background-muted color appears in BackgroundColor story 4. Verify color applies correctly when selected ## Screenshots/Recordings ### Before https://github.com/user-attachments/assets/c79a8669-0609-49dc-9759-ee19f9a80a83 ### After https://github.com/user-attachments/assets/35f685ce-b240-437d-9d60-35037704d340 ## 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/main/.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 ## 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 commit 06cf7459b963e26d0ae60312d4f9a342d07ab89d Author: Bryan Fullam Date: Fri Dec 13 02:00:16 2024 +0700 feat: routing for failed transactions (#29158) ## **Description** When a user has a transaction error, they should be routed correctly based on the type of user they are and the type of error they have encountered. Hardware wallet user: 1. Decline either approval or bridge tx -> back to bridge page with previous inputs restored 2. Tx fails for any other reason -> to activity tab, so they can see status of bridge operation Standard MM user: 1. Tx fails for any reason -> to activity tab, so they can see status of bridge operation [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29158?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to bridge while using hardware wallet 2. Approve quote to start bridge 4. Deny tx on hardware wallet 5. Get rerouted back to bridge page with previous inputs restored ## **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/main/.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/main/.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: IF <139582705+infiniteflower@users.noreply.github.com> commit fc054820318db24a334cd1614307e6db14bff8bf Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 13:51:04 2024 -0500 fix: show only 6 decimals max to reduce text cutoff in activity item (#29153) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29153?quickstart=1) This PR shortens Bridge txs amount sent so it's not cutoff as often. ## **Related issues** Fixes: ## **Manual testing steps** 1. Submit a bridge of over 6 decimal places, eg 0.0012345678 2. Go to Activity page 3. Observe that bridge amount should be 0.001235 ## **Screenshots/Recordings** ### **Before** ### **After** ![Screenshot 2024-12-12 at 12 53 31 PM](https://github.com/user-attachments/assets/9744080c-f784-4902-a165-b67b5317d3e2) ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit b08388ae40ebf103e0ac163dd420618ee0522317 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 13:32:20 2024 -0500 chore: disable button while submiting, use finally set submitting to … (#29149) …false ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29149?quickstart=1) This PR handles some additional comments from https://github.com/MetaMask/metamask-extension/pull/29109 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit e9ea5b215a2111cab9879df4a3ec508283833dfb Author: Mark Stacey Date: Thu Dec 12 14:40:13 2024 -0330 ci: Only check attributions on release candidates (#29043) ## **Description** The `check-attributions` workflow has been updated to only check on release candidates, as was initially intended. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29043?quickstart=1) ## **Related issues** Fixes #29037 ## **Manual testing steps** We could test this by creating a pretend release candidate, but that would mess up some of our other metrics/release automation. In this case it seems easier to test this by merging it, there are no significant negative consequences if it doesn't work as intended. ## **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/main/.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/main/.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. commit f784171996dcd322893c75c11b2afe302f995c44 Author: MetaMask Bot Date: Thu Dec 12 18:09:54 2024 +0000 Version v12.9.2 commit 1a239750147d79a0476bfe2aac4fa96af8388400 Author: Bryan Fullam Date: Fri Dec 13 00:45:23 2024 +0700 feat: hardware wallet confirmation screen (#29113) ## **Description** Bridge currently redirects to swap when trying to use a hardware wallet. This PR allows hardware wallet accounts to access swaps and adds an info screen to inform the user about the transactions they will need to approve on their device. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29113?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Connect with hardware wallet account 2. Go to bridge 3. Enter token, amount, and network 4. Confirm quote 5. See info screen ## **Screenshots/Recordings** ### **Before** ### **After** ![Screenshot 2024-12-11 at 20 52 29](https://github.com/user-attachments/assets/cfb97ae1-17e2-4904-9c55-d6b9b08fdb49) ## **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/main/.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/main/.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: IF <139582705+infiniteflower@users.noreply.github.com> commit f74a897e3312dfad8b61d4bd52e4eaf71d68fbf8 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 12:44:50 2024 -0500 chore: calc the max total gas fee as well (#29116) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29116?quickstart=1) This PR calculates the max total gas in order to block the submit button. ## **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/main/.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/main/.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. commit edab215510e3936567818c767108151a48633a13 Author: Mark Stacey Date: Thu Dec 12 13:54:16 2024 -0330 refactor: Refactor how manifest flags are set (#28686) ## **Description** The pieces of `set-manifest-flags.ts` related to _getting_ flags have been moved to the new `get-manifest-flags.ts` module. This module will be used in a later PR by a script run during a CI workflow. The migrated steps were also made asynchronous so that the asynchronous steps could be run in parallel rather than blocking the process. Documentation has been added to the `ManifestFlags` type as well, in preparation for adding a new property in the next PR. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28686?quickstart=1) ## **Related issues** Related to https://github.com/MetaMask/metamask-extension/issues/28685 ## **Manual testing steps** N/A, no functional changes. ## **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. --------- Co-authored-by: Howard Braham commit 0fb61dc6e1e32952e711180162e59dfc70027b40 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 11:34:19 2024 -0500 chore: delay linea bridge tx to make it less flaky (#29109) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29109?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Set network to Linea 2. Bridge an ERC20 to another network 3. Observe loading spinner on quotes page 4. Get redirected to activity list 5. See successful ERC20 approval and bridge tx ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/e6079f43-632e-4bd3-a9e0-54f7c427b541 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 4b48ee62e4bd62fcb2981c230f2c2ed383d2ec62 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Thu Dec 12 08:07:35 2024 -0800 fix: Use correct selector to pull name from non-popular networks (#29121) ## **Description** On token-list-item, we were using the wrong selector to select the network configuration, which included the network name needed for the fallback icon of non-popular networks. `getNetworkConfigurationsByChainId` returns chainId => networkConfiguration mapping, while `getNetworkConfigurationIdByChainId` returns a chainId => string mapping, which could be a networkId (random UUID string) This broke the fallback behavior, as we would render the first letter of the uuid, rather than the first letter of the network name. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29121?quickstart=1) ## **Related issues** Fixes: Incorrect network logo fallback letter ## **Manual testing steps** 1. Add Base Sepolia as custom network: https://chainlist.org/chain/84532 2. Verify that the fallback image on main token-list and token-detail page is `B` and matches the network picker. ## **Screenshots/Recordings** Before Screenshot 2024-12-11 at 5 29 02 PM After Screenshot 2024-12-11 at 5 28 21 PM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit fffb8c67d344acbb100dd6a6c36d88881cf53a66 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 11:02:50 2024 -0500 feat: update bridge tx details (#29075) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29075?quickstart=1) This PR update the Bridge tx details screen with: 1. Source/destination chain always visible 2. You sent/received 3. Activity log ## **Related issues** Fixes: ## **Manual testing steps** 1. Do a bridge tx 4. Click on the bridge tx in the activity list 5. Observe changes ## **Screenshots/Recordings** ### **Before** ### **After** ![Screenshot 2024-12-11 at 1 32 49 PM](https://github.com/user-attachments/assets/36879afa-e2ec-4d21-97a9-015d82dd8229) ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit b7aab59d69fe46208be153f2a605f5fdde74349a Author: Pedro Figueiredo Date: Thu Dec 12 15:44:41 2024 +0000 feat: Remove scroll to the bottom requirement for all personal sign requests (#29053) ## **Description** Users can now Confirm personal sign requests without scrolling to the bottom of the confirmation. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29053?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3760 ## **Manual testing steps** 1. Go to OpenSea 2. Login 3. See their personal sign request and how the Confirm button is disabled ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2024-12-10 at 14 16 32 ## **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/main/.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/main/.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. commit b3c4759639468d889f2248504cb58ae4b449f1dc Author: Derek Brans Date: Thu Dec 12 10:16:09 2024 -0500 chore: Add App Opened Metric Event (#28927) ## **Description** This PR adds tracking for when the MetaMask app is opened in order to track MAU/MTU. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28927?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3401 ## **Manual testing steps** 1. Ensure MM is not "fullscreen" in any tabs 2. Open chrome dev tools for service worker 3. Clear the network tab 4. Open metamask (via Dapp or by clicking on extension) 5. There should be one or more new network request for POST https://api.segment.io/v1/batch – One of those should should have event: "App Opened" ## **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/main/.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/main/.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: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> commit 2e941a841a82f50bbc757e349e8e95f2f8ae86e5 Author: Pedro Figueiredo Date: Thu Dec 12 14:57:57 2024 +0000 feat: Add new metric sending_value to Transaction * events (#29134) ## **Description** Adds a `useSendingValueMetric` hook that uses `updateTransactionEventFragment` to add the `sending_value` property on erc20 and native token transfers. The value is sent as an unformatted decimal javascript number. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29134?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3784 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Native token send: Screenshot 2024-12-12 at 11 40 43 ERC20 token send: Screenshot 2024-12-12 at 11 41 47 ## **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/main/.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/main/.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. commit 8f3fb205f4bee429ab4c10e63c2efc49bd2dc1df Merge: 26aae337f8 e792ba1b30 Author: Dan J Miller Date: Thu Dec 12 11:21:43 2024 -0330 Merge pull request #29069 from MetaMask/Version-v12.9.1 Version v12.9.1 RC commit c065d93f24625c9b9a50520d12d5fdd7912cc1c2 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Thu Dec 12 15:39:51 2024 +0100 test: [POM] Migrate hardware wallet e2e tests to follow Page Object Model (#28768) ## **Description** - Create base pages for hardware wallet related pages - Migrate hardware wallet e2e tests to Page Object Model - Remove dead code (We should not keep unused functions in the codebase because they can lead to several issues, such as increased maintenance overhead, added complexity, potential bugs, and misleading information.) [![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/28808 ## **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: Harika <153644847+hjetpoluru@users.noreply.github.com> Co-authored-by: Derek Brans commit e792ba1b3072bbd303b2d6157a8dad85988e64c7 Author: MetaMask Bot Date: Thu Dec 12 14:02:39 2024 +0000 Update Attributions commit 49a7c825a2c723a1bf9250066d24567cf43fde54 Author: Jyoti Puri Date: Thu Dec 12 19:27:24 2024 +0530 fix: design related fixes in confirmation pages (#29137) ## **Description** Small design related fixes in re-designed confirmation pages ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3478 ## **Manual testing steps** 1. Go to test dapp 2. Submit transaction pages and check design fixes ## **Screenshots/Recordings** Screenshot 2024-12-12 at 6 52 03 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit 88ffa10a612201020172be30569e4b2904f0f613 Author: Dan J Miller Date: Thu Dec 12 10:21:32 2024 -0330 Cherry pick 2e8ef0237 (#29125) to v12.9.1 (#29136) Cherry pick 2e8ef0237 (#29125) to v12.9.1 --------- Co-authored-by: Charly Chevalier Co-authored-by: MetaMask Bot commit 5c1c4b2107ecaace1dbe5330f0476143126f30be Author: Nidhi Kumari Date: Thu Dec 12 13:46:34 2024 +0000 feat: Permission page tour removal (#28966) This PR is to remove product tour from Permissions Page ## **Related issues** Fixes: [https://github.com/MetaMask/MetaMask-planning/issues/3755](https://github.com/MetaMask/MetaMask-planning/issues/3755) ## **Manual testing steps** 1. Run extension with `yarn start` 2. Install a fresh version 3. Go to Permissions Page, check there is no Product Tour ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-05 at 3 53 47 PM](https://github.com/user-attachments/assets/a020cd59-492c-43df-af41-0f7a5ec7f142) ### **After** ![Screenshot 2024-12-05 at 3 54 50 PM](https://github.com/user-attachments/assets/e77251f3-c3c9-433b-bef8-218dceba2bf5) ## **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/main/.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/main/.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. commit 717cd8780db00fe953958988d6ccfa6e2a9fb15a Author: Jyoti Puri Date: Thu Dec 12 18:32:28 2024 +0530 fix: Fix in label displayed for state change in signature decoding section (#29020) ## **Description** We currently displaying multiple "You list" and "Spending cap" for multiple assets. We should be displaying the copy only once similar to how we do it for simulations. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28944 ## **Manual testing steps** 1. Submit a typed sign v4 request with multiple NFT listed 2. Check simulation section on page displayed ## **Screenshots/Recordings** Screenshot 2024-12-09 at 6 05 46 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit acdf7c6579e2d1ac06e2ab4f2a0917d616df0403 Author: OGPoyraz Date: Thu Dec 12 13:57:45 2024 +0100 fix: Change visibility of `AmountRow` in contract interaction (#29131) ## **Description** This PR makes two visibility change on `AmountRow` in transaction details. - Regardless of the amount or simulated value, if transaction details is toggled, it must show `AmountRow` - Whenever the Amount being sent doesn't match with a 5% buffer what we're displaying in the "You send" row of simulations UI for contract interactions, `AmountRow` must be visible. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29131?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3783 ## **Manual testing steps** ### Scenario I 1. Trigger a contract interaction 2. Enable advanced view 3. With this change, you should now see the Amount row on the advanced view displaying the native asset value that is being sent along with the transaction. ### Scenario II 1. Trigger a contract interaction with the below payload (this payload should make the amount being sent don't match "You send" value within simulations) 2. With this change, you should now see the Amount row on the default view displaying the native asset value that is being sent along with the transaction. `{ to: "0x4805a248c9611c22a43ce956489c9aadb6108433", value: "0xDE0B6B3A7640000", data: "0x3158952e", }` ## **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/main/.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/main/.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. commit 66e9893a620128064cbf26a1a6e934b31b44fa9a Author: Dan J Miller Date: Thu Dec 12 08:09:24 2024 -0330 Update changelog for v12.9.1 (#29133) Update changelog for v12.9.1 commit 31ff3a2c077bae7c9a75508167b869deb5de1b7c Author: Dan J Miller Date: Thu Dec 12 07:47:50 2024 -0330 Cherry pick #28782 to v12.9.1 (#29130) Cherry-picks 1e3af312f6 (#28782) to v12.9.1 commit 2e8ef02370f72febe0fa40370db62b7d61b641f0 Author: Dan J Miller Date: Thu Dec 12 07:46:55 2024 -0330 fix: Revert gridplus sdk version bumps (#29125) ## **Description** This PR reverts #27973 and #28008, and then forces an secp256k1 resolution to deal with a yarn audit failure. This is necessary to fix typed message signing with the lattice gridplus hardware wallet. All of this had been done on release branches and master in the past, when it should have been done directly on develop. We do want to restore the #27973 and #28008 changes soon, but that requires getting to root of why those changes result in typed message signing failure with lattice griduplus [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29125?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Test "Sign Typed Message" v4 in the test dapp with a gridplus hardware wallet. It should succeed without error ## **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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: Charly Chevalier Co-authored-by: MetaMask Bot commit 714fa10bd5f1a78e20d46965802eae087ad6fa33 Author: Alejandro Garcia Anglada Date: Thu Dec 12 11:51:28 2024 +0100 fix: solana balance on accounts selector (#29054) Screenshot 2024-12-10 at 15 45 14 ## **Description** Solana native balance weren't showing before [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29054?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** As of right now, manually testing is a bit complex, it needs to run the snap manually and the extension, since we 1st need to publish a new release to npm with more up to date work. The snap version we have in npm is outdated and won't support this flow. That said, if you want to go ahead and run locally the steps are the following: 1. Clone the [ Solana Snap monorepo](https://github.com/MetaMask/snap-solana-wallet) and run it locally with `yarn` and then `yarn start` 2. In the extension, at this branch, apply the following changes and run the extension as flask: ``` At builds.yml add the solana feature to the flask build: features: - build-flask - keyring-snaps + - solana At shared/lib/accounts/solana-wallet-snap.ts point the snap ID to the snap localhost: -export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +//export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +export const SOLANA_WALLET_SNAP_ID: SnapId = "local:http://localhost:8080/"; ``` 3. Manually install the snap via the snap dapp at http://localhost:3000 4. Enable the Solana account via Settings > Experimental > Enable Solana account 5. Create a Solana account from the account-list menu and see the account balance on it ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 09b4c80d46cf040ef77dc01c7d279f9bd7db7d30 Author: Pedro Figueiredo Date: Thu Dec 12 10:06:40 2024 +0000 feat: Add link to pending transaction alert (#28721) ## **Description** This PR substitutes a purely text based alert for pending transactions with one that includes a hyperlink to the docs. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28721?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28308 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ![Screenshot 2024-12-02 at 11 13 58](https://github.com/user-attachments/assets/146b353d-a515-40db-95cd-5e091700cf18) ## **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. commit b8f6ba7395c95a9333ab93025d07951c637ecaf3 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 12 04:52:26 2024 -0500 fix: send up requestId for squid (#29042) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29042?quickstart=1) This PR fixes issues with calling the Bridge API for `getTxStatus` for bridges that expect a `requestId`. ## **Related issues** Fixes: ## **Manual testing steps** 1. Get a bridge quote 2. Select Axelar/Squid 3. Execute the bridge 4. See your status update correctly ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 1e3af312f6e7866468acd02208569a5e323bbe5a Author: Dan J Miller Date: Thu Dec 12 06:55:04 2024 -0330 chore: Revert "feat: add websocket support for c2 detection (#28782)" (#29122) This reverts commit e0f6575a6dc80913532f33202b1d3e91b31137b4, which is causing failing e2e tests on main ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29122?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/main/.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/main/.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. commit 17d1f9036803876c6d89f8ba0f7dc84bddfcd16f Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed Dec 11 20:10:46 2024 -0800 fix (cherry-pick): Specify popular network icons in token network filter (#29112) (#29119) ## **Description** Popular Networks are now only showing data from 9 popular networks, only show icons for those networks. Also show tooltip to specify which networks these are. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29112?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/9787102b-f07a-4a64-b59b-194d01b105c7 ## **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/main/.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/main/.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/29119?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/main/.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/main/.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. commit 0a790f54a0531fc36c38dbd75607b00b395b9f52 Author: hunty Date: Wed Dec 11 20:58:08 2024 -0600 fix: (MMS-1789) bridge api called when external services disabled (#29077) ## **Description** Bridge API gets called even when all privacy toggles are disabled: https://bridge.api.cx.metamask.io/getAllFeatureFlags [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29077?quickstart=1) ## **Related issues** Fixes: checks if external services are enabled (Basic Functionality toggle) before calling this API in the useBridging hook. ## **Manual testing steps** 1. Start onboarding through the MetaMask wallet. 2. During onboarding, toggle the security feature 'Basic Functionality' OFF. 3. Observe network calls to ensure this endpoint is not hit. ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 7ac49996258d26b508e96137067e3780bb77766d Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Wed Dec 11 15:34:22 2024 -0800 chore: add autofocus prop to AssetPicker (#29118) ## **Description** Adds an autofocus prop to the AssetPicker, which sets the focus of the Search bar when the asset-picker is open [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29118?quickstart=1) ## **Related issues** Fixes: N/A ## **Manual testing steps** There should be no changes to the Send page, the only experience that uses this component ## **Screenshots/Recordings** ### **Before** N/A ### **After** 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/main/.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/main/.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. commit 4f970df0acec3e3bc80da08373aa3b16f23aae41 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed Dec 11 14:33:21 2024 -0800 fix: Specify popular network icons in token network filter (#29112) ## **Description** Popular Networks are now only showing data from 9 popular networks, only show icons for those networks. Also show tooltip to specify which networks these are. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29112?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/9787102b-f07a-4a64-b59b-194d01b105c7 ## **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/main/.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/main/.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. commit 25d1b62fa30d5da4c5bb43cb29c79c734db36b21 Author: Jony Bursztyn Date: Wed Dec 11 22:21:17 2024 +0000 feat: carousel component (#28956) ## **Description** This PR introduces a carousel component for the homepage that displays banners about new changes. It leverages the existing `react-responsive-carousel` library to handle carousel functionality and the `BannerBase` component for rendering banner content and images. The carousel component has been integrated into the `account-overview-layout.tsx` file. Key features: - Skeleton is displayed during app startup or if there is no connection. - Implements banner-specific behaviors based on user interaction (e.g., navigation dots, horizontal drag, close button). - Supports up to 5 banners with no auto-forwarding or loop functionality. - Non-dismissible 'Fund your wallet' banner remains in the first position when applicable. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3758 ## **Manual testing steps** 1. Start the app and navigate to the homepage. 2. Verify that the carousel skeleton appears during app initialization or when there’s no connection. 3. Test navigation between banners using dots, horizontal drag, and clicking edges. 4. Verify that banners behave as expected when interacting with the close button: - For last banners, ensure the container is removed. - For 'Fund your wallet' banner, ensure it remains in place when others are closed. 5. Confirm URLs open correctly when clicking Bridge, Funds, Sell, and Card banners. 6. Test with more than 5 banners to ensure only the first 5 are displayed. 7. Verify no auto-forwarding or loop behavior. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2024-12-09 at 22 38 04 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability. - [ ] I’ve included tests for the carousel component and its behaviors. - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format where applicable. - [ ] I’ve applied the appropriate labels as per the [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md). ## **Pre-merge reviewer checklist** - [ ] I’ve manually tested the PR by pulling the branch, running the app, and verifying functionality. - [ ] I confirm that this PR meets all acceptance criteria from the ticket. - [ ] I’ve reviewed and approved the testing evidence provided (screenshots/recordings). --------- Co-authored-by: MetaMask Bot Co-authored-by: Nidhi Kumari Co-authored-by: NidhiKJha Co-authored-by: georgewrmarshall commit 2aa02051d9d73f31df121d8105f78ca975322ab4 Author: Brian Bergeron Date: Wed Dec 11 13:36:05 2024 -0800 chore(cherry-pick): token detection across multiple networks (#29115) cherry picks https://github.com/MetaMask/metamask-extension/pull/29108 to 12.9.1 commit 16764b8ea74717366f3f30f9b27d368b950a026f Author: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> Date: Wed Dec 11 15:57:41 2024 -0500 fix: [cherry-pick] add websocket support for c2 detection (#28782) (#29114) cherry-picks #28782 ## **Description** This pull request adds WebSocket support to the MetaMask extension's phishing detection functionality. Scammers have started using WebSocket connections for command-and-control (C2) operations to bypass traditional HTTP-based phishing detection. This PR allows the extension to intercept and block WebSocket handshake requests (`ws://` and `wss://`) in addition to HTTP/HTTPS requests. The key changes include: 1. Adding WebSocket schemes (`ws://*/*` and `wss://*/*`) to the `urls` filter in `background.js`. 2. Updating the `manifest.json` to include WebSocket permissions in the `host_permissions` field. This ensures that malicious WebSocket connections can be detected and blocked. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28782?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3788 ## **Manual testing steps** 1. Navigate to `example.com` 2. Initiate a WebSocket connection to a known safe domain (e.g., `wss://example.com`) and verify it works as expected by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://example.com/")` 3. Attempt a WebSocket connection to a domain flagged as phishing, and verify the connection is blocked and appropriate warnings are displayed by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://walietconnectapi.com/")` ## **Screenshots/Recordings** ### **Before** No support for detecting WebSocket phishing connections. --- ### **After** WebSocket phishing connections are detected and blocked during the handshake phase. ## **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. commit 70bdc86711205c487d1126a2ed918144b525a1ff Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Wed Dec 11 12:38:54 2024 -0800 chore: enable multiselect in asset-picker network modal (#28975) ## **Description** Figma: https://www.figma.com/design/IuOIRmU3wI0IdJIfol0ESu/Cross-Chain-Swaps?node-id=7-24563&node-type=canvas&m=dev Changes - enables multi network selection in the Asset Picker, and displaying tokens from multiple chains - adds support for searching tokens by address - updates the PickerNetwork component to show multiple network icons - refactors TokenListItem for less truncation [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28975?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. To test locally set `BRIDGE_USE_DEV_APIS=1` in .metamaskrc, then try selecting networks/tokens in the Bridge experience 2. Also try to do some e2e tests in the Send and Swap/Send flows ## **Screenshots/Recordings** ### **Before -> After** ![Screenshot 2024-12-05 at 9 36 14 PM](https://github.com/user-attachments/assets/0a533f9e-f8f5-4574-8e99-08e11a0915f2)![Screenshot 2024-12-05 at 9 34 06 PM](https://github.com/user-attachments/assets/b7704ac0-6b78-4034-b4b5-2c504f0c93ef) ![Screenshot 2024-12-05 at 9 36 23 PM](https://github.com/user-attachments/assets/890a2aee-8fa7-46e0-9123-13556ca86c3a)![Screenshot 2024-12-05 at 9 33 54 PM](https://github.com/user-attachments/assets/cc239f12-6000-4496-9963-fcaa2c341b37) ![Screenshot 2024-12-05 at 9 36 37 PM](https://github.com/user-attachments/assets/aac2278c-ddb3-4087-94a0-be17fb659d2b)![Screenshot 2024-12-05 at 9 37 45 PM](https://github.com/user-attachments/assets/b4a8d208-2f3f-4e0f-931a-bdb47053a31c) ![Screenshot 2024-12-05 at 9 36 44 PM](https://github.com/user-attachments/assets/eca739e7-2536-44f3-b46c-b0527817bbf1)![Screenshot 2024-12-05 at 9 37 55 PM](https://github.com/user-attachments/assets/68385c35-71f0-4f84-9edd-e3a74f143a90) ![Screenshot 2024-12-10 at 11 25 09 AM](https://github.com/user-attachments/assets/e7a6a4ea-4f0c-4da2-abed-477bc297bc0b)![Screenshot 2024-12-10 at 11 25 14 AM](https://github.com/user-attachments/assets/d93ec08d-e300-4aa8-978b-631348a4be36) ## **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/main/.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/main/.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. commit 1814c2b50093647a18e3d6301c91855c29c4c4f8 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed Dec 11 15:18:30 2024 -0500 chore: update CODEOWNERS for bridge related code (#29029) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29029?quickstart=1) This PR updates `CODEOWNERS` for all Bridge related code. ## **Related issues** n/a ## **Manual testing steps** n/a ## **Screenshots/Recordings** n/a ### **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 3d9db73c562f37273281a0ab8bd474002e8fe700 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Wed Dec 11 15:18:03 2024 -0500 fix: token.icon possibly null, update validators and types (#29065) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29065?quickstart=1) This PR fixes an issue where `token.icon` returned by the Bridge API status endpoint could be `null`, which caused validation to fail. It updates the types and the validation to match what the Bridge API returns. ## **Related issues** Related to https://github.com/MetaMask/metamask-extension/pull/28899 ## **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/main/.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/main/.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. commit 2693e6cd1f9e551e3c857f3a25398463b509591f Author: Brian Bergeron Date: Wed Dec 11 12:13:38 2024 -0800 fix: token detection across multiple networks (#29108) ## **Description** Fixes an issue where even though 'popular networks' was selected in the token filter, it was only showing detected tokens on the current network. There were various places checking for `Object.keys(tokenNetworkFilter).length === Object.keys(allNetworks).length`, which is no longer an accurate way to check the state of the filter, since it's now been narrowed to only include popular networks. The selector `getIsTokenNetworkFilterEqualCurrentNetwork` is used instead. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29108?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Onboard a wallet with tokens across chains 2. When token filter is on 'popular networks', it should show # of detected tokens across chains 3. When token filter is on current network, it should show # of detected tokens on current chain ## **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/main/.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/main/.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. commit e0f6575a6dc80913532f33202b1d3e91b31137b4 Author: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> Date: Wed Dec 11 14:03:17 2024 -0500 feat: add websocket support for c2 detection (#28782) ## **Description** This pull request adds WebSocket support to the MetaMask extension's phishing detection functionality. Scammers have started using WebSocket connections for command-and-control (C2) operations to bypass traditional HTTP-based phishing detection. This PR allows the extension to intercept and block WebSocket handshake requests (`ws://` and `wss://`) in addition to HTTP/HTTPS requests. The key changes include: 1. Adding WebSocket schemes (`ws://*/*` and `wss://*/*`) to the `urls` filter in `background.js`. 2. Updating the `manifest.json` to include WebSocket permissions in the `host_permissions` field. This ensures that malicious WebSocket connections can be detected and blocked. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28782?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3788 ## **Manual testing steps** 1. Navigate to `example.com` 2. Initiate a WebSocket connection to a known safe domain (e.g., `wss://example.com`) and verify it works as expected by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://example.com/")` 3. Attempt a WebSocket connection to a domain flagged as phishing, and verify the connection is blocked and appropriate warnings are displayed by going to the `console` via right clicking and hitting inspect. Then type into the console `new WebSocket("https://walietconnectapi.com/")` ## **Screenshots/Recordings** ### **Before** No support for detecting WebSocket phishing connections. --- ### **After** WebSocket phishing connections are detected and blocked during the handshake phase. ## **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. commit 5de217302dc3b977cba4fca17c7a303790c2924f Author: Matthew Walsh Date: Wed Dec 11 18:44:39 2024 +0000 test: fix flaky confirmation E2E tests (#29104) ## **Description** Fix flaky E2E tests in: - `test/e2e/json-rpc/switchEthereumChain.spec.js` - `test/e2e/tests/confirmations/navigation.spec.ts` (Firefox Only) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29104?quickstart=1) ## **Related issues** Fixes: #29090 ## **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/main/.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/main/.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. commit e9651814518389775c739d0caa46bbec00e1db60 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Wed Dec 11 19:28:10 2024 +0100 chore: bump {profile-sync,notification-services}-controller (#29088) ## **Description** We want to ensure that mobile is using the latest versions of all controllers. - Bump `@metamask/notification-services-controller` from 0.14.0 to 0.15.0 ([view changes](https://github.com/MetaMask/core/blob/main/packages/notification-services-controller/CHANGELOG.md)) - There are no notable changes between these versions. - Bump `@metamask/profile-sync-controller` from 2.0.0 to 3.0.0 ([view changes](https://github.com/MetaMask/core/blob/main/packages/profile-sync-controller/CHANGELOG.md)) - The `UserStorageController` messenger must now allow the actions `NetworkController:getState`, `NetworkController:addNetwork`, `NetworkController:removeNetwork`, and `NetworkController:updateNetwork` - The `UserStorageController` messenger must now allow the event `NetworkController:networkRemoved` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29088?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28856, https://github.com/MetaMask/metamask-extension/issues/29034 ## **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/main/.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/main/.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 commit 5f6e31ae33e7228855ae20f272570e63ed11c285 Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Wed Dec 11 22:11:47 2024 +0400 fix: SonarCloud branch information (#29072) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29072?quickstart=1) In this [slack thread](https://consensys.slack.com/archives/CTQAGKY5V/p1733397151519159?thread_ts=1732116793.123109&cid=CTQAGKY5V) it was reported that SonarCloud is only executed on "main". Following some investigation, we discovered that it is executed on other branches too, but the SonarCloud UI displays the branch always as "main" due to a problem with the wrong branch name being reported by the workflow. We identified that this issue was introduced by the https://github.com/MetaMask/metamask-extension/pull/27700 that updated the SonarCloud workflow to enable execution on forks. Enabling execution on forks required some security measures to avoid arbitrary code execution by malicious actors, which seems to have confused the SonarCloud GitHub Action. In order to solve this problem, the workflow now overrides the analysis parameters, according to the documentation [here](https://docs.sonarsource.com/sonarqube-cloud/advanced-setup/analysis-parameters/). ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3774 ## **Manual testing steps** 1. Run SonarCloud scan (after merging, as the workflow only executes from main), and see PRs and branches correctly displayed on the SonarCloud UI. ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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: Danica Shen commit d758bf9330f378213d04a7f982d12fb52d9de619 Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Wed Dec 11 10:11:30 2024 -0800 chore: change default network avatar style (#28976) ## **Description** This PR changes the default position of the network avatar to the bottomRight of the token. It also changes the default shape from a circle to a rounded square Figma: https://www.figma.com/design/IuOIRmU3wI0IdJIfol0ESu/Cross-Chain-Swaps?node-id=7-24563&node-type=canvas&m=dev [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28976?quickstart=1) ## **Related issues** Fixes: N/A ## **Manual testing steps** 1. Visually inspect AvatarNetwork instances (token list, network picker, activity, etc ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-09 at 11 20 35 AM](https://github.com/user-attachments/assets/46711271-ef5a-4d1d-a9b6-fea93cd3bed4) ![Screenshot 2024-12-09 at 11 20 40 AM](https://github.com/user-attachments/assets/28fd5c27-d91a-49b1-ba60-5e240f1a768e) ### **After** ![Screenshot 2024-12-09 at 11 18 59 AM](https://github.com/user-attachments/assets/67ed09a8-befb-49a0-ad6b-5c4d65ad428c) ![Screenshot 2024-12-09 at 11 18 45 AM](https://github.com/user-attachments/assets/6ae812aa-988b-48e0-befb-702c7fc664ec) ## **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/main/.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/main/.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. commit 17fe0d2f9007adab7b6a06f4dc75fee0d98e4e1c Author: Derek Brans Date: Wed Dec 11 13:04:19 2024 -0500 chore: pass the Extension Version to the Uninstall URL (#28935) ## **Description** Update the uninstall URL to include an application version for all users, even those with MetaMetrics disabled. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28935?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28414 ## **Manual testing steps** I do not know how to test this manually. 1. 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/main/.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/main/.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: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> commit 2bd3db8994097460d535698ec9d9f0731f610610 Author: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Wed Dec 11 21:22:33 2024 +0400 feat: log-merge-group-failure (#28799) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28799?quickstart=1) This PR integrates the new reusable workflow (https://github.com/MetaMask/github-tools/pull/31) that can be used to write data to google sheets when the merge group event fails. Environment variables (= GitHub secrets) are required to configure: - GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_SERVICE_ACCOUNT: The google service account that is used for authentication with the google sheets api. - The service account needs to have access to the google sheet you intend to operate on. To give access to the google sheet, you need to click the "Share" button in the top-right corner. Enter the service account's email (can be found on google cloud console), give "Editor" permissions. After this, the service account will have access to this spreadsheet, allowing read and write operations. - SPREADSHEET_ID: unique identifier that can be found in the url when you open the google sheet - SHEET_NAME: name of a sheet in a spreadsheet that can be found on the bottom Short summary of what happens in the workflow: 1. Google api authentication setup 2. Check if current date exists in the spreadsheet 3. If current date exists, increment number of PRs by 1 4. If current date does not exist, create a new row with the current date, and the number 1 (so that it can be incremented later, if more merge group events fail on the same day). Spreadsheet here: https://docs.google.com/spreadsheets/d/11niHgT_E2YzzXHXQSxX5LNdA6i0GG5aa-OZWvn7_-o4 Dashboard here: https://lookerstudio.google.com/u/1/reporting/e7e8f90e-72aa-4128-ae01-6305bf3393f4/page/p_pz1dszarmd ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3400 ## **Manual testing steps** 1. When the workflow runs, depending on the state of the spreadsheet: - If the current date already exists, the number of PRs removed from the merge queue should be incremented by 1. - If the current date does not exist, a new row should be added with the current date, and the number of PRs should be set to 1. I created a private repository to test it, and there it worked. The actual production usage can only be tested if we merge and see if anything gets removed from the merge queue, in this case the spreadsheet should get updated. ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit fb19fae0fecdf948d778791dd472fef9bb6068f4 Author: Brian Bergeron Date: Wed Dec 11 08:32:46 2024 -0800 chore(cherry-pick): only poll popular networks (#29103) Cherry picks https://github.com/MetaMask/metamask-extension/pull/29071 to 12.9.1 Co-authored-by: Nicholas Gambino commit 0556088f55ff932a5767d0b008191b5a6c9d6849 Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Wed Dec 11 07:18:46 2024 -0800 fix: replace hardcoded slippage + expire bridge quotes (#29028) ## **Description** Changes - Replaces hardcoded slippage with the slippage percentage included in the quote request - Disables tx submission 30s after final quote fetch [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29028?quickstart=1) ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1771 ## **Manual testing steps** 1. Submit a tx 2. Request quotes with sufficient balance 3. 30 seconds after the last fetch, the CTA should be disabled ## **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/main/.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/main/.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. commit 07d4265d1665d94ec580ef65fe8e020641e57c4a Author: Brian Bergeron Date: Wed Dec 11 02:11:23 2024 -0800 feat: only poll popular networks (#29071) ## **Description** To reduce the impact of showing assets across all networks, which can be unbounded, this PR scopes the portfolio view to the 9 popular networks built into metamask. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29071?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29055 ## **Manual testing steps** 1. On popular networks: - The token filter should allow switching between popular networks and current network - The filter should work as described - RPC requests in the background should only hit the popular networks 2. On other networks - The token filter should become disabled and scoped to the current network - RPC requests in the background should only hit the current network ## **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/main/.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/main/.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: Nicholas Gambino commit c2a14215f7954d4ea277e52dd7baf7583ecb180d Merge: 3fc8b20dad 4ae9db0fed Author: Dan J Miller Date: Wed Dec 11 00:05:55 2024 -0330 Merge pull request #29080 from MetaMask/fix/cherry-pick-29068 refactor: [cherry-pick] Init getTokenNetworkFilter selector (#29068) commit 3fc8b20dadcdf3ccf3226acae737e90ea8d5853c Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue Dec 10 19:04:51 2024 -0800 fix: [cherry-pick] fix sticky autodetection banner (#29061) (#29078) cherry-picks https://github.com/MetaMask/metamask-extension/commit/5789e1ceebdb59a2fd02feb3e3dee035988ba2f2 (#29061) ## **Description** fix sticky autodetection banner [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29061?quickstart=1) ## **Related issues** Fixes: #29059 ## **Manual testing steps** 1. Go to autodetected banner 2. click on ignore all 3. you should have allNetworks selected ## **Screenshots/Recordings** ### **Before** ### **After** https://drive.google.com/file/d/11zcmgV1Bdq5wx9MkDlhxr8oDBr9DGB3d/view?usp=sharing ## **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/main/.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/main/.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/29078?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/main/.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/main/.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: Salim TOUBAL Co-authored-by: Brian Bergeron commit 4ae9db0fedf3ba0c9e567fe7565d18f5b3e815bc Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue Dec 10 17:37:44 2024 -0800 fix: Token list should respect hidezerobalance setting (#29058) ## **Description** If a token is not a native asset for a chain, and the balance is zero, it should not be rendered if the `hideZeroBalance` setting is toggled to true. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29058?quickstart=1) ## **Related issues** Fixes: erc20 and native tokens with zero balance rendering on asset list on 12.9 release. ## **Manual testing steps** 1. Add an erc20 token to any chain with zero balance, should display when imported by default 2. Toggle `hideZeroBalance` setting to true in general settings > Respect the "hide zero balance" setting (when true): - Native tokens should always display with zero balance when on the current network filter. - Native tokens should not display with zero balance when on all networks filter - ERC20 tokens with zero balances should respect the setting on both the current and all networks. Respect the "hide zero balance" setting (when false): - Native tokens should always display with zero balance when on the current network filter. - Native tokens should always display with zero balance when on all networks filter - ERC20 tokens always display with zero balance on both the current and all networks filter. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/aaee7e23-ab5c-4216-89e5-dc11485200bc ## **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/main/.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/main/.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: sahar-fehri commit 5f8af0e9ec063b7b9154c7fb73791379acd75ab7 Author: Garrett Bear Date: Tue Dec 10 18:09:12 2024 -0800 fix: Remove Storybook theme flex style (#27783) ## **Description** In this PR, I updated the wrapper of the Storybook to remove the flex layout, as seen in the provided before and after screenshots. The change simplifies the layout, which was previously managed with a flexbox that is no longer necessary. This modification improves layout management for the `FormTextField` component examples. Both light and dark modes are supported, ensuring a consistent appearance across different themes. ## **Related issues** None reported. ## **Manual testing steps** 1. Open the Storybook interface. 2. Navigate to the `FormTextField` component. 3. Observe the layout in both light and dark themes. 4. Verify that the form elements are correctly aligned and behave as expected. ## **Screenshots/Recordings** ### **Before** Screenshot 2024-10-10 at 3 48 41 PM ### **After** Screenshot 2024-10-10 at 3 51 02 PM https://github.com/user-attachments/assets/48c56e71-8dde-4f4b-8a78-15b8f25d116c ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability. - [ ] I’ve included tests if applicable. - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable. - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g., pull and build the branch, run the app, and test the 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. commit 3453c9519b179905ee76b5bdeadf88713f925ba1 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue Dec 10 17:37:44 2024 -0800 fix: Token list should respect hidezerobalance setting (#29058) ## **Description** If a token is not a native asset for a chain, and the balance is zero, it should not be rendered if the `hideZeroBalance` setting is toggled to true. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29058?quickstart=1) ## **Related issues** Fixes: erc20 and native tokens with zero balance rendering on asset list on 12.9 release. ## **Manual testing steps** 1. Add an erc20 token to any chain with zero balance, should display when imported by default 2. Toggle `hideZeroBalance` setting to true in general settings > Respect the "hide zero balance" setting (when true): - Native tokens should always display with zero balance when on the current network filter. - Native tokens should not display with zero balance when on all networks filter - ERC20 tokens with zero balances should respect the setting on both the current and all networks. Respect the "hide zero balance" setting (when false): - Native tokens should always display with zero balance when on the current network filter. - Native tokens should always display with zero balance when on all networks filter - ERC20 tokens always display with zero balance on both the current and all networks filter. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/aaee7e23-ab5c-4216-89e5-dc11485200bc ## **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/main/.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/main/.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: sahar-fehri commit 77179c4fcbbabbd0c9426b21deef802b8908da62 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue Dec 10 15:52:31 2024 -0800 refactor: Init getTokenNetworkFilter selector (#29068) ## **Description** We are falling back to an empty object when tokenNetworkFilter is undefined in several areas around the app. This PR aims to consolidate those within a single selector to make is easier to maintain and fall back on. This only mitigates the issue. Root cause is that the `tokenNetworkFilter` needs to be added via a migration script in order to properly be present, without having to fallback. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29068?quickstart=1) ## **Related issues** Fixes: https://metamask.sentry.io/issues/6125799129/?environment=production&project=273505&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20firstRelease%3A12.9.0&referrer=issue-stream&sort=freq&statsPeriod=7d&stream_index=0 ## **Manual testing steps** 1. Hardcode tokenNetworkFilter to undefined, should not break app. ## **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/main/.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/main/.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: Brian Bergeron commit e7bba6d8947497e90ecc8c8be649a807d329bcc4 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue Dec 10 15:52:31 2024 -0800 refactor: Init getTokenNetworkFilter selector (#29068) ## **Description** We are falling back to an empty object when tokenNetworkFilter is undefined in several areas around the app. This PR aims to consolidate those within a single selector to make is easier to maintain and fall back on. This only mitigates the issue. Root cause is that the `tokenNetworkFilter` needs to be added via a migration script in order to properly be present, without having to fallback. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29068?quickstart=1) ## **Related issues** Fixes: https://metamask.sentry.io/issues/6125799129/?environment=production&project=273505&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20firstRelease%3A12.9.0&referrer=issue-stream&sort=freq&statsPeriod=7d&stream_index=0 ## **Manual testing steps** 1. Hardcode tokenNetworkFilter to undefined, should not break app. ## **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/main/.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/main/.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: Brian Bergeron commit 653177ac7a2838ee6458333715eeac5bad348dcf Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Tue Dec 10 13:47:07 2024 -0800 chore: emit cross-chain swaps metrics + validation error logic (#28713) ## **Description** Changes include - Input/quote validation logic for `isInsufficientBalance`, `isInsufficientGasBalance` and `isEstimatedReturnLow`(no ui yet but these get included in metrics) - Type-safe event emitter for cross-chain swaps - Refactored token exchange rate fetching process so that values can be reused for metrics without additional network calls - Adding new events described in https://docs.google.com/document/d/1hAoqEzlSnPDMfmiYBf8FNykYZ_LLbmZjKyTtjPmeEbs/edit?tab=t.0 - Note that these events that haven't been added bc they depend on tx status changes - ActionFailed - ActionCompleted [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28713?quickstart=1) ## **Related issues** Depends on [this segment-schema PR](https://github.com/Consensys/segment-schema/pull/226) Fixes: https://consensyssoftware.atlassian.net/browse/MMS-1453 ## **Manual testing steps** 1. Open background inspector and filter requests including "batch" 2. Perform an e2e transaction 3. Verify that expected events are published ## **Screenshots/Recordings** ### **Before** ### **After** 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. commit 5789e1ceebdb59a2fd02feb3e3dee035988ba2f2 Author: Salim TOUBAL Date: Tue Dec 10 22:44:43 2024 +0100 fix: fix sticky autodetection banner (#29061) ## **Description** fix sticky autodetection banner [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29061?quickstart=1) ## **Related issues** Fixes: #29059 ## **Manual testing steps** 1. Go to autodetected banner 2. click on ignore all 3. you should have allNetworks selected ## **Screenshots/Recordings** ### **Before** ### **After** https://drive.google.com/file/d/11zcmgV1Bdq5wx9MkDlhxr8oDBr9DGB3d/view?usp=sharing ## **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/main/.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/main/.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: Brian Bergeron commit a608982ca77951b30b092fa487ea449d42b12884 Author: MetaMask Bot Date: Tue Dec 10 20:34:02 2024 +0000 Version v12.9.1 commit 12aea26a728a55ded3479c2c1cc23a338981d07b Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Tue Dec 10 15:29:48 2024 -0500 fix: incorrect cross chain swaps activity item values (#28899) ## **Description** This PR fixes the Bridge activity list item not showing the proper token symbol, token amount, and display currency amount. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28899?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Do a Bridge 2. Go to tx history 3. Observe proper values ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-12-03 at 3 44 55 PM](https://github.com/user-attachments/assets/f43e96f4-be08-48ec-b39f-6ecb9c283863) ### **After** ![Screenshot 2024-12-03 at 3 43 38 PM](https://github.com/user-attachments/assets/51057305-7c8b-4337-9003-02c94a740705) ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit bbb49d27e90b31526e812f94a77b5736a4eb7b39 Author: Bryan Fullam Date: Wed Dec 11 02:44:07 2024 +0700 refactor: decouple extension config from bridge API feature flags (#28983) ## **Description** We are currently unable to scope specific source and destination allow lists to extension. This PR refactors the existing system of using the bridge api allowlists to instead use an extension-scoped feature flag for all extension related configs. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28983?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to bridge 2. Complete successful bridge ## **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/main/.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 - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 3802e3df663419935988552ffbebb2e6ea3cd080 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Tue Dec 10 13:22:09 2024 -0500 fix: cross chain swap tx order (#28939) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28939?quickstart=1) This PR fixes the ordering of `bridgeApproval` and `bridge` type txs. For non-STX, we revert back to using `addTransactionAndWaitForPublish`. We only use `addTransaction` when it is a `bridge` type STX. ## **Related issues** Fixes: ## **Manual testing steps** 1. Add `BRIDGE_USE_DEV_APIS=1` to `.metamaskrc` 1. Do a bridge 2. Go to Activity 3. Observe that ordering is correct ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/a9f77144-9996-4202-868f-dc2d96c65403 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit ac4bdeae45cd7904b0f3dcce5b64067aa57c06ca Author: Matthew Walsh Date: Tue Dec 10 17:30:26 2024 +0000 feat: unified confirmation navigation (#28761) ## **Description** Support a single navigation queue between all types of pending confirmation, including: - Transactions - Signatures - Template Confirmations (e.g. Snap Dialogs, Smart Transaction Status Page) - Get Encryption Public Key - Decrypt - Add Token - Add NFT - Connect Specifically: - Add `useConfirmationNavigation` hook to centralise all confirmation routing. - Use routing hook in: - `ConfirmPageContainerNavigation` - `Home` - `Nav` - `syncConfirmPath` - Replace custom navigation in `ConfirmationPage` with `Nav` component. - Add `confirmationId` property to `Nav` component and remove coupling to `useCurrentConfirmation` hook. - Add `Nav` component to: - `ConfirmAddSuggestedNFT` - `ConfirmAddSuggestedToken` - `ConfirmDecryptMessage` - `ConfirmEncryptionPublicKey` - `PermissionConnectHeader` - Add associated E2E test. - Remove manual header sizing from `ConfirmationPage` component. - Fix template confirmation storybooks. - Add snap dialog storybooks. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28761?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Add unapproved transaction. 2. Add unapproved signature request 3. Install test dialog snap. 4. Create confirmation via test dialog snap. 5. Verify confirmation count is 3 and navigation functions between them. ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/5d59032c-0626-4603-aeac-3661b365a825 ## **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. commit 1c2755c274e209c6e943a1b4e92fd4262b19c996 Author: jvbriones <1674192+jvbriones@users.noreply.github.com> Date: Tue Dec 10 16:54:40 2024 +0100 chore: update bug template to include feature branches (#28878) ## **Description** Github bug template updated to include feature branches as a development stage [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28878?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/main/.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/main/.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. commit 223cf44aa86cc36f241f841e94ff0fa799f2ee32 Author: Priya Date: Tue Dec 10 15:17:07 2024 +0100 test: fix flaky send eth transaction test (#29050) ## **Description** Fixes flaky e2e test to send eth transaction from within the wallet. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29050?quickstart=1) ## **Related issues** Fixes: [#28876](https://github.com/MetaMask/metamask-extension/issues/28876) ## **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/main/.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/main/.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. commit f9e78789663c49464956d9776d61f475794b6afe Author: Pedro Figueiredo Date: Tue Dec 10 13:34:24 2024 +0000 feat: Add new gas_fee_selected property on Transaction * events (#29027) ## **Description** As per the task title. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29027?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3622 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2024-12-09 at 17 35 30 Screenshot 2024-12-10 at 09 58 47 Screenshot 2024-12-10 at 09 58 37 ## **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/main/.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/main/.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. commit 8c8851a21594f00730378d7031daf2a18c600840 Author: Matthew Walsh Date: Tue Dec 10 10:11:14 2024 +0000 feat: upgrade transaction controller to get incoming transactions using accounts API (#28597) ## **Description** Update `@metamask/transaction-controller` to retrieve incoming transactions using the accounts API rather than Etherscan. Add incoming transaction E2E tests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28597?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. commit 5a04816fb01ba34acb034972a045728faf41c4bd Author: OGPoyraz Date: Tue Dec 10 11:05:42 2024 +0100 fix: Copy change on NFT approve confirmation title (#29017) ## **Description** This PR update NFT approve to `Withdrawal request` for consistency between NFT's approve and setApprovalForAll requests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29017?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to test dapp 2. Mint a NFT 3. Approve the NFT 4. See redesigned confirmation for the NFT approve ## **Screenshots/Recordings** ### **Before** Screenshot 2024-12-09 at 11 51 01 ### **After** Screenshot 2024-12-09 at 11 51 35 ## **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/main/.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/main/.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. commit 26aae337f80cd0e0316918adeec9a5e729c65782 Merge: 884d810d46 8d86aeafd7 Author: Dan J Miller Date: Mon Dec 9 22:31:41 2024 -0330 Merge pull request #28805 from MetaMask/Version-v12.9.0 chore: Version v12.9.0 commit 8d86aeafd725f0c04a934385c93b484ed16b01c4 Author: Dan J Miller Date: Mon Dec 9 20:13:54 2024 -0330 chore: Skip flaky smart-transactions.spec.ts until we determine the r… (#29039) Cherry-picks 64a69f709834947c4f459206ade1e2f856ea787464a69f709834947c4f459206ade1e2f856ea7874 (https://github.com/MetaMask/metamask-extension/pull/28943) to v12.9.0, to get the flaky test passing commit 61ee2a6f944b0b9155ca9c3f26df4b7d3ef91a83 Author: Dan J Miller Date: Mon Dec 9 18:56:11 2024 -0330 Lint fix for the v12.9.0 changelog (#29038) commit 528d31c9c7566e69c75d83afb8ea8799dc8b92a6 Author: MetaMask Bot Date: Mon Dec 9 21:20:36 2024 +0000 Update Attributions commit 32b4e8c63b707765737586f1c81b9a5361d92919 Author: Marina Boboc <120041701+benjisclowder@users.noreply.github.com> Date: Mon Dec 9 23:17:37 2024 +0200 chore: V12.9.0 changelog (#28987) ## **Description** RC 12.9.0 Changelog [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28987?quickstart=1) commit a4ca18dfd9e16467277b7602380b4a594c4fec5d Merge: b36fb52350 61f426c0dd Author: Dan J Miller Date: Mon Dec 9 17:26:28 2024 -0330 Merge pull request #29036 from MetaMask/merge-master-v12.8.1-to-v12.9.0 Merge master v12.8.1 to v12.9.0 commit 61f426c0dd6bcff9bcccf665c7bc6fb199a049db Merge: b36fb52350 884d810d46 Author: Dan J Miller Date: Mon Dec 9 17:12:38 2024 -0330 Merge remote-tracking branch 'origin/master' into merge-master-v12.8.1-to-v12.9.0 commit b36fb52350e3ee5957553159561027eae1db6b45 Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Mon Dec 9 20:40:22 2024 +0000 fix (cherry-pick): only display Signing in with for SIWE #28984 (#29025) ## **Description** Cherry-pick of #28984 for release `12.9.0`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29025?quickstart=1) ## **Related issues** Fixes: ## **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/main/.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/main/.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. commit 9b4f20b067fe77c21c964208f6bee93a371b80a5 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Mon Dec 9 13:57:44 2024 -0500 feat: show banner for delayed bridge tx (#28849) ## **Description** This PR adds a banner to tell users to reach out to Support if the Bridge tx is delayed. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28849?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Start a Bridge tx 2. Wait a bit 3. Banner should appear ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/51e288e8-aa98-484c-b851-34eb471471e9 ## **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/main/.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/main/.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. commit 7d9ebf6d3b83bebc3a2c9c4546ecaa1ddfc11246 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Mon Dec 9 13:57:33 2024 -0500 chore: hide cancel button if bridge tx (#28902) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28902?quickstart=1) This PR hides the Cancel button for Bridge transactions. ## **Related issues** Fixes: ## **Manual testing steps** 1. Do a Bridge tx 2. Go to Activity list 3. Observe cancel button will never appear ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/9980d92f-b4c0-4dfd-beb0-5898ffda3209 ### **After** https://github.com/user-attachments/assets/904fc5de-1480-428b-8081-2588a4b1d735 ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 51e67b4e83e162ab7d57ae27d98e1ce85537266a Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Mon Dec 9 13:57:06 2024 -0500 fix: wiping txHistory on both source and dest chains (#29000) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29000?quickstart=1) This PR fixes an issue for Bridge txs where both the source chain AND destination chain txHistory get wiped when the user clears their Activity tab data. ## **Related issues** Fixes: ## **Manual testing steps** 1. Add `BRIDGE_USE_DEV_APIS=1` to `.metamaskrc` 1. Do a bridge from chain 1 to chain 2 2. Switch to chain 2 3. Do a bridge from chain 2 to another chain 4. Go into Settings > Advanced > Click Clear activity 5. Observe that chain 2's tx are gone 6. Switch to chain 1 7. Observe that chain 1's bridge tx has the proper values ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/247a061e-84ac-4757-bede-edeeecff7c4a ### **After** https://github.com/user-attachments/assets/ce872b63-e31c-47ea-8d78-d59555864a6b ## **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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 1da7536e69c8bad121600b2bfbfd938dc35f6bab Author: Howard Braham Date: Mon Dec 9 23:10:44 2024 +0530 chore: eliminate direct `cross-spawn` dependency (#28570) ## **Description** We recently updated `cross-spawn` to solve a yarn audit, but actually we can do one better. This eliminates the direct dependency on `cross-spawn`. It runs on Windows by adding one `shell: true`. We still have some indirect dependencies on `cross-spawn`, so we still need that resolution. @Gudahtt because he last updated `cross-spawn` @davidmurdoch because he's also a Windows user [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28368?quickstart=1) ## **Related issues** Followup to #28522 commit 6ef73b3903f0554f63901c71f67fbc3771b3316d Author: Mathieu Artu Date: Mon Dec 9 18:01:21 2024 +0100 feat: various profile syncing improvements (#29022) ## **Description** This PR improves different things around profile syncing. - It decouples syncing features from any notifications dependency. - It adds a new `MetamaskIdentityProvider` which will be responsible for dispatching syncing features - It changes the implementation of `ProfileSyncDevSettings` so we'll be able to add more settings easily - It adds missing tests [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29022?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Onboard 2. Go to developer settings and click for the account syncing deletion button 3. Go back to the home view 4. Reload the extension and open developer settings 5. Look for a successful network call to `https://user-storage.api.cx.metamask.io/api/v1/userstorage/accounts_v2` More generally, verify that account syncing works (but E2E tests covers this). ## **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/main/.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/main/.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. commit b5dae6cc4ac9a0ae7be9d8d5e75f99f288fe6908 Author: Desi McAdam Date: Mon Dec 9 09:51:45 2024 -0700 chore: Branch off of "New Crowdin translations by Github Action" (#29011) ## **Description** This is a branch off of https://github.com/MetaMask/metamask-extension/pull/26964, as of commit https://github.com/MetaMask/metamask-extension/commit/243bf031364deb6450d43b2cd000f89633574c44 This will make it easier to merge the branch, which regularly has new commits pushed to it This adds new translations from crowdin [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29011?quickstart=1) ## **Related issues** n/a ## **Manual testing steps** 1. Load extension build ## **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/main/.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/main/.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: metamaskbot commit 84e448ef6d3f122e0cf078bc576903811f3ccfa2 Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Mon Dec 9 13:56:36 2024 +0000 fix: only display `Signing in with` for SIWE (#28984) ## **Description** This PR updates the SigningInWith component to enhance the display logic and labelling for signature requests. **Changes:** - For SIWE (Sign-In With Ethereum) requests, the label will now display "Signing in with". - For all other signature requests: - The label will display "Signing with". - The label will only be visible when this field has a corresponding alert. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28984?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28978 ## **Manual testing steps** 1. Go to test dapp 2. Click on any signature request 3. See "Signing in with" row show up ## **Screenshots/Recordings** [setapproveforall.webm](https://github.com/user-attachments/assets/f3124ac4-2a03-448a-9b68-d4016021f39a) ### **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/main/.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/main/.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. commit b078721197f5d392333fcd60bf4f4cbf875fa410 Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Mon Dec 9 13:04:49 2024 +0000 fix: add source when local PPOM fails (#28726) ## **Description** This PR fixes an issue where the source property was not being set correctly when an error occurred during local PPOM validation. Previously, if the local PPOM failed, the error handling mechanism would add an error message but omit the source property, making it unclear whether the error originated from the API or the local PPOM. **Changes introduced:** - Modified error handling for the local PPOM validation to ensure that the source property is correctly set to "local" when an error occurs during local validation. - Ensured that the source property is properly set to "api" in case of an API failure. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28726?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3693 ## **Manual testing steps** 1. Go to the live test dapp 2. Trigger a send transaction with suggested gas values (ie Send Legacy or send EIP1559) 3. Check Segment Obs: we need to block the network calls for `https://security-alerts.api.cx.metamask.io/` and `https://static.cx.metamask.io/api/v1/confirmations/ppom/ppom_version.json` in chrome dev tools > network tab. ## **Screenshots/Recordings** [error-set-local.webm](https://github.com/user-attachments/assets/7a632a0d-826a-4c40-ab6e-7ce4451abdeb) ### **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. commit 0eab55daa63c0a494c838891618c259f20835b1e Author: Prithpal Sooriya Date: Mon Dec 9 12:25:38 2024 +0000 feat: bump @metamask/message-signing-snap to ^0.6.0 (#28877) ## **Description** Bumps message signing snap to `^0.6.0` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28877?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Ensure application still works as intended 1. Complete onboarding 2. Try to enable notifications (as this uses the snap) Try using the preinstalled snap on sites. [See snap docs for testing](https://github.com/MetaMask/message-signing-snap/blob/main/docs/testing.md) - can be tested in the browser. 1. Get eip6963 provider 2. Call connect 3. Call get public key 4. Call sign message ## **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/main/.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/main/.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. commit 44696fd01060952f10f1190df373e103c243f40c Author: Pedro Figueiredo Date: Mon Dec 9 10:43:30 2024 +0000 fix: Support decimal point on advanced gas modal on mac (#28869) ## **Description** The main bug this PR fixes only happens on mac, not linux. It is the following: when a user pressed `.` on the advanced gas modal and a number after it, a zero would appear instead the `.` and any numbers before it. This is because in the `onChange` in `NumericInput`, when the user presses `.`, `e.target.value` is `""`, and the code was defaulting the value that got passed to the on change handler to `0`. To fix the bug, this PR removes that default, and necessarily the `parseInt` that wrapped it. The PR also adds the `0` default to a few places where the newly possible `""` value would otherwise break execution. This PR also prevents the user from setting decimal custom nonce values. Finally, it prevents `,` to be registered when `allowDecimals` is set on ``, most notably for the gas limit input. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28869?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28843 ## **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/main/.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/main/.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. commit 7694809924a990bde1b3737582efd8b6fa48bcc1 Merge: e0a0a7a69e b9bb7b2f04 Author: Dan J Miller Date: Fri Dec 6 22:56:23 2024 -0330 Merge pull request #28858 from MetaMask/merge-master-v12.8.0-to-v12.9.0 chore: Merge master as of v12.8.0 into v12.9.0 commit e0a0a7a69e75b0998c94f385ebc73ba75dd16338 Author: Jyoti Puri Date: Sat Dec 7 05:05:24 2024 +0530 cherry-pick: Update signature controller (#28992) ## **Description** Update signature controller to fix signature decoding issues. PR: 1. https://github.com/MetaMask/core/pull/5028 2. https://github.com/MetaMask/core/pull/5033 Original Extension PR: https://github.com/MetaMask/metamask-extension/pull/28988 ## **Related issues** * Related to: https://github.com/MetaMask/MetaMask-planning/issues/3756 * Related to: https://github.com/MetaMask/MetaMask-planning/issues/3757 ## **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/main/.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/main/.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. commit baf387b41c422b2c879593cb42dd65f254966931 Author: Matthew Walsh Date: Fri Dec 6 19:03:46 2024 +0000 fix (cherry-pick): hide first time interaction alert if internal account (#28990) (#28998) commit 7abff9c7b2bf8e826ab1f2169642e6789bef2280 Author: Matthew Walsh Date: Fri Dec 6 15:34:27 2024 +0000 fix: hide first time interaction alert if internal account (#28990) ## **Description** Hide the first time interaction alert if the transaction `to` is an internal account. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28990?quickstart=1) ## **Related issues** Fixes: #28942 ## **Manual testing steps** See issue. ## **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/main/.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/main/.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. commit 01d276c2175c3f48cbf39cddc69258d60bebfeb7 Author: Pedro Figueiredo Date: Fri Dec 6 14:47:37 2024 +0000 fix: cherry-pick: Add origin row to transfer confirmations (#28989) cherry-picks: https://github.com/MetaMask/metamask-extension/pull/28936 ## **Description** This PR adds an origin row as well as a content divider as per the latest designs (see screenshot below). Additionally, ConfirmInfoSection has been moved to inside the SimulationDetails component, to fix a visual bug that showed additional margin on the UI, even when the SimulationDetails component was not being rendered. Screenshot 2024-12-04 at 17 34 20 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28936?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28928 ## **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/main/.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/main/.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/28989?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/main/.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/main/.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. commit 8d7f003f0bf4e4476d1f31aa279bee630b62aa18 Author: Hassan Malik <41640681+hmalik88@users.noreply.github.com> Date: Fri Dec 6 09:37:25 2024 -0500 feat: Integrate JSX into snap notifications (#27407) ## **Description** Adds an expanded view for snaps notifications, using JSX content returned from the snap to populate the expanded view. Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? Allow for richer snaps notifications 2. What is the improvement/solution? Add expanded view for snaps, allowing a snap to return jsx content in the expanded view. ## **Screenshots/Recordings** ### **After** https://github.com/user-attachments/assets/74c89bb7-7510-4d1c-acb6-a585978ecec8 ## **Manual testing steps** 1. Pull down https://github.com/hmalik88/swiss-knife-snap and run `yarn start` to serve the snap and site. 2. Build this branch using `yarn start:flask` 3. Install the snap from the local host site. 4. Trigger a notification with the "trigger a JSX notification" button and observe the results. ## **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: Maarten Zuidhoorn Co-authored-by: Guillaume Roux Co-authored-by: Frederik Bolding commit 9e70c7372ff47e5fbca228641c4904b956f3d9c1 Author: Jyoti Puri Date: Fri Dec 6 19:28:48 2024 +0530 chore: Update signature controller (#28988) ## **Description** Update signature controller to fix signature decoding issues. 1. https://github.com/MetaMask/core/pull/5028 2. https://github.com/MetaMask/core/pull/5033 ## **Related issues** * Related to: https://github.com/MetaMask/MetaMask-planning/issues/3756 * Related to: https://github.com/MetaMask/MetaMask-planning/issues/3757 ## **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/main/.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/main/.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. commit 884d810d46cb4c94b52ca8b14e1ca7c286b278ba Merge: 7689c6e4bb 4fa2bd1b49 Author: Dan J Miller Date: Fri Dec 6 09:46:01 2024 -0330 Merge pull request #28973 from MetaMask/Version-v12.8.1 Version v12.8.1 RC commit 4fa2bd1b49de1fe483440976bf58b0c6f2157e67 Author: MetaMask Bot Date: Fri Dec 6 12:33:25 2024 +0000 Update Attributions commit 8de03c0c08af54b89f370a1d8f82db10c44f664c Author: Pedro Figueiredo Date: Fri Dec 6 11:34:31 2024 +0000 fix: Add origin row to transfer confirmations (#28936) ## **Description** This PR adds an origin row as well as a content divider as per the latest designs (see screenshot below). Additionally, ConfirmInfoSection has been moved to inside the SimulationDetails component, to fix a visual bug that showed additional margin on the UI, even when the SimulationDetails component was not being rendered. Screenshot 2024-12-04 at 17 34 20 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28936?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28928 ## **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/main/.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/main/.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. commit 1235f689fe2e614cc94d22e14e7aadd6d369d9e8 Merge: 42031dddbb ef35d13f69 Author: Dan J Miller Date: Fri Dec 6 00:44:31 2024 -0330 Merge pull request #28982 from MetaMask/cherry-pick-aa2823f9-v12.8.1 Cherry pick aa2823f9 to v12.8.1 commit ef35d13f6990b8046c19709d03a2ac627a4721c6 Author: Mark Stacey Date: Thu Dec 5 20:56:51 2024 -0330 fix: Fix create metric fragment (#28970) The function `createEventFragment` of the `MetaMetricsController` was broken recently in the migration to BaseControllerV2 (#28113). We ended up trying to mutate a piece of Immer state, resulting in an error. The affected line was updated to use `cloneDeep` prior to mutating, so that we're no longer attempting to mutate a frozen object. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28970?quickstart=1) Fixes #28599 I'm not sure exactly how to reproduce the error using a real build. But the problem is easy to demonstrate in the "should update existing fragment state with new fragment props" unit test. The problem is that we didn't catch this before because Lodash doesn't have strict mode enabled, so in unit tests the attempt to mutate a frozen object will silently fail. In a production build, [strict mode is enabled by LavaMoat](https://github.com/LavaMoat/LavaMoat/blob/7a3896a08b45f667649c46a56f27fe7bf20f4207/packages/lavapack/src/pack.js#L333). You can reproduce the problem by adding `"use strict"` to the top of the file `node_modules/lodash/lodash.js` and running the test with the `{}, ` removed. Then you can test that adding back `{}` as the initial parameter fixes the problem. N/A - [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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] 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. commit 42031dddbb78833b0a6d116969456394ce23e142 Merge: 56a51c1804 2fe56cbc16 Author: Dan J Miller Date: Fri Dec 6 00:36:11 2024 -0330 Merge pull request #28981 from MetaMask/cherry-pick-4fc45f0bf4-v12.8.1 Cherry pick 4fc45f0bf4 to v12.8.1 commit 2fe56cbc168e0c99baade841502813ecc3ea0979 Author: Dan J Miller Date: Fri Dec 6 00:11:25 2024 -0330 chore: Renumber migration 135 to 131.1 (#28979) This PR updates the number on migration 135 to 131.1, so that we can smoothly release it in a v12.8.1 hotfix Migration 135 was just added here https://github.com/MetaMask/metamask-extension/pull/28974 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28979?quickstart=1) Fixes: 1. Go to this page... 2. 3. - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] 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. commit 12e02aaac8399c9421e1d58d00efefc01b6dc10c Author: Dan J Miller Date: Fri Dec 6 00:33:04 2024 -0330 Update changelog for v12.8.1 commit 3343be52b72e752e3568c16121e1dc58a4301ed8 Author: Salim TOUBAL Date: Fri Dec 6 00:48:36 2024 +0100 feat: migrate base mainnet rpc to infura (#28974) This PR introduces a migration to replace the Base network RPC URL (`https://mainnet.base.org`) with the new Infura RPC URL (`https://base-mainnet.infura.io/v3/{infuraProjectId}`) in the MetaMask state. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28895?quickstart=1) Fixes: 1. build the app on main branch and put the file on tmp-chrome folder 2. Add base network 3. build the app on the current branch 4. replace the main build with the new one 5. base url should be updated before Screenshot 2024-12-05 at 20 12 53 - [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/main/.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/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] 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. commit 90695c5e112aa5d9815248f0c9f284b6d4eecdc7 Author: Dan J Miller Date: Fri Dec 6 00:11:25 2024 -0330 chore: Renumber migration 135 to 131.1 (#28979) ## **Description** This PR updates the number on migration 135 to 131.1, so that we can smoothly release it in a v12.8.1 hotfix Migration 135 was just added here https://github.com/MetaMask/metamask-extension/pull/28974 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28979?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/main/.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/main/.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. commit 4112ff3a8b9ea6be7cfdd0739bfa49c429fea5e1 Author: Sean Geng Date: Thu Dec 5 20:56:58 2024 -0500 feat: add B3 logo svg (#27778) ## **Description** Adding logo for the B3 network [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27778?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Go to add network 1. Add B3 network (https://docs.b3.fun) 2. B3 network should have a logo 3. B3 token should have a logo ## **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. commit aa2823f9059dccfb3bb97a5c05d9e30d9393abf5 Author: Mark Stacey Date: Thu Dec 5 20:56:51 2024 -0330 fix: Fix create metric fragment (#28970) ## **Description** The function `createEventFragment` of the `MetaMetricsController` was broken recently in the migration to BaseControllerV2 (#28113). We ended up trying to mutate a piece of Immer state, resulting in an error. The affected line was updated to use `cloneDeep` prior to mutating, so that we're no longer attempting to mutate a frozen object. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28970?quickstart=1) ## **Related issues** Fixes #28599 ## **Manual testing steps** I'm not sure exactly how to reproduce the error using a real build. But the problem is easy to demonstrate in the "should update existing fragment state with new fragment props" unit test. The problem is that we didn't catch this before because Lodash doesn't have strict mode enabled, so in unit tests the attempt to mutate a frozen object will silently fail. In a production build, [strict mode is enabled by LavaMoat](https://github.com/LavaMoat/LavaMoat/blob/7a3896a08b45f667649c46a56f27fe7bf20f4207/packages/lavapack/src/pack.js#L333). You can reproduce the problem by adding `"use strict"` to the top of the file `node_modules/lodash/lodash.js` and running the test with the `{}, ` removed. Then you can test that adding back `{}` as the initial parameter fixes the problem. ## **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/main/.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/main/.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. commit 4fc45f0bf49ce0a79683b53013b0297a1501d4d7 Author: Salim TOUBAL Date: Fri Dec 6 00:48:36 2024 +0100 feat: migrate base mainnet rpc to infura (#28974) ## **Description** This PR introduces a migration to replace the Base network RPC URL (`https://mainnet.base.org`) with the new Infura RPC URL (`https://base-mainnet.infura.io/v3/{infuraProjectId}`) in the MetaMask state. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28895?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. build the app on main branch and put the file on tmp-chrome folder 2. Add base network 3. build the app on the current branch 4. replace the main build with the new one 5. base url should be updated ## **Screenshots/Recordings** ### **Before** before ### **After** Screenshot 2024-12-05 at 20 12 53 ## **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/main/.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/main/.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. commit 456e63f4dc26f0603ff0d4fb3a11de254b642622 Author: Priya Date: Fri Dec 6 04:06:59 2024 +0800 test: fix flaky erc20 send token e2e (#28775) ## **Description** The issue is that sometimes when clicking on the watchAssets button from the test dapp the metamask dialog does not open. The test dapp button is present and enabled but for some reason clicking on it does nothing. However if you wait for a second and then click this seems to solve the issue. Also tried to wait until the page is loaded, element is present, visible and enabled, however, the flakiness persists. Hence added a hardcoded wait. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28775?quickstart=1) ## **Related issues** Fixes: [#28700](https://github.com/MetaMask/metamask-extension/issues/28700) ## **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: Harika <153644847+hjetpoluru@users.noreply.github.com> commit 56a51c18045e106751481aca8e1e53e3134dc0c3 Author: MetaMask Bot Date: Thu Dec 5 20:04:13 2024 +0000 Version v12.8.1 commit 27f3c21f4887fe0528acd9b7bf1b47de22d86cc7 Author: Charly Chevalier Date: Thu Dec 5 21:00:48 2024 +0100 chore: bump `@metamask/account-watcher` to `^4.2.2` (#28915) ## **Description** This new version now uses `devDependencies` rather than `dependencies`, which avoid pulling unnecessary deps into the main tree (check the `yarn.lock`). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28915?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** N/A ## **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/main/.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/main/.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. commit 5cc3f2d180b0acebb934b5bacecabc97898c2ca5 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu Dec 5 19:32:05 2024 +0100 chore: bump `@metamask/ppom-validator` to `0.36.0` (#28958) ## **Description** This PR updates `@metamask/ppom-validator` to version `0.36.0`, which now uses `@metamask/network-controller` version 22.0.0 as a peer dependency. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28958?quickstart=1) ## **Related issues** Fixes: ## **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/main/.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/main/.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. commit 3833c747edc319896703790caaa9a45b1e86096b Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Thu Dec 5 12:46:43 2024 -0500 fix: remove extension from webpack config import (#28111) commit ceeb5b6e6f46c2c1dae99c5bb1d4a57a0db2ffce Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu Dec 5 18:05:15 2024 +0100 chore: bump `@metamask/preferences-controller` to `15.0.1` (#28960) ## **Description** This PR bumps `@metamask/preferences-controller` to `15.0.1` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28960?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28630 ## **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/main/.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/main/.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. commit 58f727fac012417156a5efb6840379609a20d037 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Thu Dec 5 11:28:54 2024 -0500 feat: cross chain swap STX (#28460) ## **Description** This PR improves support for Smart Transactions (STX). It will not show the STX status page if the tx is a Bridge tx. Instead users will be immediately sent to the Activity list. If the user opens the Bridge tx detaills, they should be able to see the proper info. The main change is that in `BridgeStatusController` the `txHistory` is now keyed by `txMeta.id` rather than `txMeta.hash`. This is because for an STX we might not have the hash immediately, but we still want to look things up. We also add a way to call `addTransaction` from the UI side. Previously we used `addTransactionAndWaitForPublish`. However for STX, this will cause the app to wait until a `txHash` is available before returning a `txMeta`, which means we can't even use the `txMeta.id` to key a `BridgeHistoryItem`. If we use `addTransaction` this will allow us to receive a `txMeta` object back immediately and use `txMeta.id`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28460?quickstart=1) ## **Related issues** Depends on https://github.com/MetaMask/core/pull/4918 ## **Manual testing steps** 1. Add `BRIDGE_USE_DEV_APIS=1` to `.metamaskrc` 1. Go to Settings > Advanced, make sure Smart Transactions is on 2. Switch network to Ethereum 3. Go to Bridge 4. Selection source network, source token, dest network, dest token 5. Request a quote 6. Execute a bridge 7. Go to Activity 8. Click on the Bridge tx ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/364b93ec-3b54-4ce6-acd5-5001236c128c ## **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. commit a2e99dc1ddf2cfece9e7e01ca3cc14474ebbb57b Author: Pedro Pablo Aste Kompen Date: Thu Dec 5 13:10:55 2024 -0300 chore: cherry pick merge of #28898 (#28965) ## **Description** cherry pick https://github.com/MetaMask/metamask-extension/issues/28898 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28841?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. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28965?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/main/.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/main/.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: George Weiler Co-authored-by: Dan J Miller commit e8a5d50076d6ecd74eb2b0f2603553da22a4794a Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Thu Dec 5 08:08:33 2024 -0800 fix: cherry-pick fix check for undefined marketData (#28870) (#28950) ## **Description** Cherry pick to 12.9 RC of: https://github.com/MetaMask/metamask-extension/pull/28870 This PR fixes app crash after user removes a network then adds it back and clicks on import token banner [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28870?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28019#issuecomment-2513839152 Fixes: https://github.com/MetaMask/metamask-extension/issues/28882 Fixes: https://github.com/MetaMask/metamask-extension/issues/28864 ## **Manual testing steps** Manual steps are also described in the github [issue](https://github.com/MetaMask/metamask-extension/issues/28019#issuecomment-2513839152). However; I do not think that the Show native token as main balance needs to be ONt o repro the initial issue. Also no need to add new RPC from chainList; Settings: 1. Show balance and token price OFF 2. Token autodetect ON On main view 1. Select an account which has some ERC20 tokens in Polygon 2. Add Polygon default network 3. See tokens are autodetected and you can open the modal -> but don't import the tokens! 4. Switch to another network 5. Delete Polygon network 6. Re-add Polygon default network 7. Click Import tokens --> Wallet should not crash 12. ## **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/main/.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/main/.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/28950?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/main/.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/main/.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: sahar-fehri commit 8cd3ced78943c6cae0bbd55eb5973c739cb2d215 Author: Jyoti Puri Date: Thu Dec 5 21:37:46 2024 +0530 cherry-pick: Adding production URL for signature decoding (#28951) ## **Description** Production URL for signature decoding. Original PR: https://github.com/MetaMask/metamask-extension/pull/28918 Cherry-picked commit: https://github.com/MetaMask/metamask-extension/commit/bf946bc494c71bfaaef89ae3b0c43f8e25490613 ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28892 ## **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/main/.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/main/.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. commit 47c89ecbcc1fb1966515b48ac76cefa94cd29a9f Author: Mark Stacey Date: Thu Dec 5 12:24:43 2024 -0330 chore: Update CODEOWNERS to remove ownership from most (#28941) ## **Description** The CODEOWNERS file has been updated to remove ownership from most files, to give us more time to plan which files the platform should own, and to give teams more time to consolidate their features. Also, ownership of the CODEOWNERS file itself was moved to the extension security team (it was accidentally set to privacy reviewers). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28941?quickstart=1) ## **Related issues** This is an adjustment to recent changes made in #28851 ## **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/main/.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/main/.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. commit 1db72cab8b2929672cc62d0b5387835244002e8d Author: George Weiler Date: Thu Dec 5 08:07:43 2024 -0700 fix: sanitizes the token marketplace URL (#28898) ## **Description** Fixes #28865 . The token marketplace URL included a double slash `//`. This PR sanitizes the URL so the buy page on portfolio can be correctly linked. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28898?quickstart=1) ## **Related issues** https://github.com/MetaMask/metamask-extension/issues/28865 Fixes: ## **Manual testing steps** 1. Click "Buy & Sell" 2. Verify that https://portfolio.metamask.io/buy is opened ## **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/main/.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/main/.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: Dan J Miller commit bffd3002c1ca7fa1f096e7d84916db5ea9c473b8 Author: Pedro Figueiredo Date: Thu Dec 5 14:35:58 2024 +0000 feat: Migrate remaining e2e tests to redesigned confirmations (#28780) ## **Description** Below are the test files that are migrated on this PR. This includes creating duplicate tests that rely on `tempToggleSettingRedesignedTransactionConfirmations`, removing it in those duplicates and making the necessary changes so that those tests pass. - test/e2e/json-rpc/eth_sendTransaction.spec.js - test/e2e/tests/account/add-account.spec.ts - test/e2e/tests/petnames/petnames-transactions.spec.js - test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js - test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js - test/e2e/tests/tokens/custom-token-send-transfer.spec.js - test/e2e/tests/transaction/change-assets.spec.js - test/e2e/tests/transaction/edit-gas-fee.spec.js - test/e2e/tests/transaction/gas-estimates.spec.js - test/e2e/tests/transaction/multiple-transactions.spec.js - test/e2e/tests/transaction/navigate-transactions.spec.js - test/e2e/tests/transaction/send-edit.spec.js - test/e2e/tests/transaction/send-eth.spec.js - test/e2e/tests/transaction/send-hex-address.spec.js On a subsequent ticket that removes the old confirmation flow (https://github.com/MetaMask/MetaMask-planning/issues/3030), the original tests that rely on `tempToggleSettingRedesignedTransactionConfirmations` to pass and to test the old confirmation flows will be deleted. Additionally, the following e2e tests will not be migrated to redesigned confirmations, and will also be deleted on https://github.com/MetaMask/MetaMask-planning/issues/3030. - test/e2e/tests/dapp-interactions/contract-interactions.spec.js - test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js - test/e2e/tests/dapp-interactions/failing-contract.spec.js - test/e2e/tests/network/network-error.spec.js - test/e2e/tests/settings/4byte-directory.spec.js - test/e2e/tests/settings/show-hex-data.spec.js - test/e2e/tests/tokens/custom-token-add-approve.spec.js - test/e2e/tests/tokens/increase-token-allowance.spec.js - test/e2e/tests/transaction/simple-send.spec.ts - test/e2e/tests/tokens/nft/erc721-interaction.spec.js - test/e2e/tests/tokens/nft/erc1155-interaction.spec.js - test/e2e/tests/tokens/nft/send-nft.spec.js [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28780?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3700 ## **Manual testing steps** N/A ## **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. commit 9e90905067f98d7de30bbc30f1c1ee05f50250dc Author: Mathieu Artu Date: Thu Dec 5 15:35:34 2024 +0100 chore: transfer ownership of auth & profile sync e2e to identity (#28946) ## **Description** This PR takes care of all the necessary changes in order to decouple Auth & Profile Sync E2E files from the notifications E2E files. This also underlines the ownership change from @MetaMask/notifications to @MetaMask/identity. The changes made here will help transitioning to cleaner separation of concerns for features leveraging profile sync (i.e Notifications, Account syncing...). ⚠️ This PR does not add missing tests nor introduces any changes. This is only moving files around and separating concerns. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28946?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** No testing steps since this PR does not change the implementation of existing features. ## **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/main/.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/main/.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. commit 0d9bd9d442d63eee0a73a33783e38f6940b8236e Author: Nidhi Kumari Date: Thu Dec 5 13:34:38 2024 +0000 fix: updated analytics preferences to be logged during onboarding (cherrypick-28897) (#28930) During onboarding while clicking on I agree button, the analytics preferences event was not being triggered. This PR is to ensure analytics preferences are logged when user click on `I agree` button in Onboarding Page ## **Related issues** Fixes: [https://github.com/MetaMask/MetaMask-planning/issues/3723](https://github.com/MetaMask/MetaMask-planning/issues/3723) ## **Manual testing steps** 1. Run extension with yarn start 2. Add console.log in trackEvent in ui/contexts/metametrics.js to see the payload 3. Do a fresh install and go on onboading flow 4. Click on I agree button on onboarding screen, check analytics preferences selected are being logged in console ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/35e156a1-ea81-4dd6-a037-87cfcd581773 ## **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/main/.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/main/.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/28930?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/main/.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/main/.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. commit c6564af481577d960f7c20a1a11d9bd8651ede4c Author: George Weiler Date: Thu Dec 5 06:22:06 2024 -0700 chore: adds ramp-dev-team to codeowners (#28933) ## **Description** Adds ramp-dev-team to the CODEOWNERS file so that ramps files require the correct approvals. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28933?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/main/.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/main/.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: Dan J Miller commit 6814e8a64ee39574fda117cdc80741fd34484499 Author: Frederik Bolding Date: Thu Dec 5 13:55:18 2024 +0100 chore: Bump Snaps packages (#28922) ## **Description** Bump Snaps packages and handle any required changes. Summary of Snaps changes: - Add `size` prop to `Text` - Allow `Field` in `Box` - Add JSX `content` property to `snap_notify` (will be used in a follow up PR) - Use WebCrypto API for key derivation - Add `getClientCryptography` hook for specifying custom cryptography functions [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28922?quickstart=1) commit e8f72c3b3b666720d5777ad5b010fd0ba8cd8788 Author: Pedro Figueiredo Date: Thu Dec 5 12:19:46 2024 +0000 feat: Add 'transaction_internal_id' property on all transaction events (#28919) ## **Description** Adds a "transaction_internal_id" property that exists in all transaction events stays the same if the transaction is sped up. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28919?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3596 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Screenshot 2024-12-03 at 18 06 32 Screenshot 2024-12-03 at 18 05 47 Screenshot 2024-12-03 at 18 02 12 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit c375dd5f501eb716c8355fb1852ae78a85a93687 Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Thu Dec 5 11:40:54 2024 +0000 fix: Capture raw 4byte hex (#28773) ## **Description** This PR aims to include in the events the property `transaction_contract_method_4byte` to improve the metrics when we are not able to identify the name of the function with our current sources. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28773?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3468 ## **Manual testing steps** 1. Go to this page test dapp 2. Deploy a contract 3. Click in Mint and check Segment events ## **Screenshots/Recordings** [4byte-event.webm](https://github.com/user-attachments/assets/eb711a3c-d754-489e-87a3-f905b43362bf) ### **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. commit a894ba5f96ebb48889f57472e291904f09222c03 Author: Priya Date: Thu Dec 5 19:38:13 2024 +0800 test: fix flaky ppom test (#28923) ## **Description** Sometimes it takes a little longer for the spinner to go away and for the blockaid banner to show up. Just increasing the dynamic timeout waiting for the spinner to be removed from the page so that in the situations where it takes a little longer the tests don't fail. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28923?quickstart=1) ## **Related issues** Fixes: [#28872](https://github.com/MetaMask/metamask-extension/issues/28872) ## **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/main/.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/main/.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: Howard Braham commit fc5c01fe9c7b987bd54e419798995486d9bccb60 Author: Priya Date: Thu Dec 5 17:41:07 2024 +0800 test: Add integration test for signing and submitting alert and fix b… (#28616) …ug of confirm button not being disabled ## **Description** - Adds integration test to check alert for signing and submitting - Fixes bug 'Confirm button not disabled when alert is displayed' [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28616?quickstart=1) ## **Related issues** Fixes: [#2975](https://github.com/MetaMask/MetaMask-planning/issues/2975) ## **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. commit 176854541f8a321e2a17b3828fbdd8bd2341b30c Author: Davide Brocchetto Date: Thu Dec 5 00:24:45 2024 -0800 test: Fixed e2e swap tests test failures (#28913) ## **Description** This PR addresses e2e Swap tests test failures. The failures are not due to the test code. For some reasons the wallet balance remains zero although the account has been funded on Tenderly. This fix allow the test to skip in case that happens. However I will still need to investigate the root cause. Tests are running fine on local [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28913?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/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.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. commit 79a8f57d2ac22bd36b09bfe63084f6ee8936785d Author: sahar-fehri Date: Thu Dec 5 03:21:11 2024 +0100 fix: fix check for undefined marketData (#28870) ## **Description** This PR fixes app crash after user removes a network then adds it back and clicks on import token banner [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28870?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28019#issuecomment-2513839152 Fixes: https://github.com/MetaMask/metamask-extension/issues/28882 Fixes: https://github.com/MetaMask/metamask-extension/issues/28864 ## **Manual testing steps** Manual steps are also described in the github [issue](https://github.com/MetaMask/metamask-extension/issues/28019#issuecomment-2513839152). However; I do not think that the Show native token as main balance needs to be ONt o repro the initial issue. Also no need to add new RPC from chainList; Settings: 1. Show balance and token price OFF 2. Token autodetect ON On main view 1. Select an account which has some ERC20 tokens in Polygon 2. Add Polygon default network 3. See tokens are autodetected and you can open the modal -> but don't import the tokens! 4. Switch to another network 5. Delete Polygon network 6. Re-add Polygon default network 7. Click Import tokens --> Wallet should not crash 12. ## **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/main/.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/main/.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: Nick Gambino <35090461+gambinish@users.noreply.github.com> commit dbf580a190149cd2a5e66a73ad949028f4b9beca Author: MetaMask Bot <37885440+metamaskbot@users.noreply.github.com> Date: Wed Dec 4 22:02:53 2024 -0300 chore: Update coverage.json (#28092) This PR is automatically opened to update the coverage.json file when test coverage increases. Coverage increased from 71% to 72%. Co-authored-by: MetaMask Bot commit 64a69f709834947c4f459206ade1e2f856ea7874 Author: Dan J Miller Date: Wed Dec 4 21:28:18 2024 -0330 chore: Skip flaky smart-transactions.spec.ts until we determine the root cau… (#28943) …se of the flakiness ## **Description** We are seeing fairly consistent failures of this test: https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/113978/workflows/8fa39e5f-3b57-4124-9af1-30e9bf63fad0/jobs/4272132/tests This PR skips it so that we can unblock merges while we investigate the root cause of the flakiness [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28943?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/main/.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/main/.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. commit bf946bc494c71bfaaef89ae3b0c43f8e25490613 Author: Jyoti Puri Date: Thu Dec 5 04:38:25 2024 +0530 feat: Adding production URL for signature decoding (#28918) ## **Description** Production URL for signature decoding. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28892 ## **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/main/.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/main/.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. commit f7fc1c92c674c89f009205a5c38856990164f16a Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Wed Dec 4 23:13:27 2024 +0100 chore: update controllers as of core release v244 (#28905) ## **Description** This PR bumps the following packages: - `@metamask/ens-controller` to `^15.0.0` - `@metamask/network-controller` to `^22.1.0` - `@metamask/gas-fee-controller` to `^22.0.1` - `@metamask/user-operation-controller` to `^20.0.1` - `@metamask/polling-controller` to `^12.0.1` - `@metamask/selected-network-controller` to `^19.0.0` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28781?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28495, https://github.com/MetaMask/MetaMask-planning/issues/3698 ## **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 - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: MetaMask Bot Co-authored-by: Elliot Winkler commit fc06d3447e97ac3aea654f4d5fb0e29108b5f18b Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Wed Dec 4 23:05:49 2024 +0100 feat: migrate `AppStateController` to inherit from BaseController V2 (#28784) ## **Description** This PR migrate `AppStateController` to inherit from BaseController V2 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28784?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/25916 ## **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 - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit 79de169cd091ec7458166025bf4a68b77ddcda35 Author: Mathieu Artu Date: Wed Dec 4 23:03:44 2024 +0100 chore: change ownership of profile sync from notifications to identity (#28901) ## **Description** This PR takes care of all the necessary changes in order to decouple "Profile Sync" from the notifications feature. This also underlines the ownership change from @MetaMask/notifications to @MetaMask/identity. The changes made here will help transitioning to cleaner separation of concerns for features leveraging profile sync (i.e Notifications, Account syncing...). ⚠️ This PR does not add missing tests nor introduces any changes. This is only moving files around and separating concerns. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28901?quickstart=1) ## **Related issues** Fixes https://github.com/MetaMask/metamask-extension/issues/28900 ## **Manual testing steps** No testing steps since this PR does not change the implementation of existing features. ## **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/main/.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/main/.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: Mark Stacey commit b1e173a6e731dadbff27960104a693d147cc25d5 Author: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Wed Dec 4 22:05:47 2024 +0100 chore: force `@solana/web3.js` version resolution (#28926) ## **Description** Due to this [Advisory](https://github.com/advisories/GHSA-2mhj-xmf4-pr8m), this PR forces the resolution of the affected package version to the current one (unaffected), avoiding unintentional updates to affected versions [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28926?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** N/A ## **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/main/.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/main/.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: Dan J Miller commit 95301f48f819080a7c5124601815cbee7dfdddce Author: Daniel <80175477+dan437@users.noreply.github.com> Date: Wed Dec 4 19:13:12 2024 +0100 chore: Update a mock for tests (#28934) ## **Description** The mock has been updated to be less specific, so that the mocked network response is used in more situations. This is meant to address a frequently failing flaky test (example: https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/113905/workflows/1140e350-cf29-45ff-916e-6fa0f3befc67/jobs/4268755/parallel-runs/16) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28934?quickstart=1) ## **Related issues** No issue yet ## **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/main/.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/main/.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. commit 95a62e834402618f3cc8d68dca97d6c94c3bbc16 Author: Jyoti Puri Date: Wed Dec 4 20:01:28 2024 +0530 cherry-pick: Fix decoding data display for ERC-1155 tokens (#28924) ## **Description** Signature decoding data was not being displayed for ERC-1155 tokens, this PR fixes it. Original PR: https://github.com/MetaMask/metamask-extension/pull/28921 Cherry-picked link: https://github.com/MetaMask/metamask-extension/commit/fc8e51e9479890a0c25caca246ce9f2515d51a42 ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28903 ## **Manual testing steps** Detailed [here](https://www.notion.so/metamask-consensys/Signature-Decoding-v12-9-QA-151f86d67d68802baddfebf3e44aea5e?pvs=4#151f86d67d6880f5a69aff17d227329d) ## **Screenshots/Recordings** Screenshot 2024-12-04 at 4 01 53 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit fc8e51e9479890a0c25caca246ce9f2515d51a42 Author: Jyoti Puri Date: Wed Dec 4 17:01:59 2024 +0530 fix: decoding data display for ERC-1155 tokens (#28921) ## **Description** Signatuare decoding data was not being displayed for ERC-1155 tokens, this PR fixes it. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28903 ## **Manual testing steps** Detailed [here](https://www.notion.so/metamask-consensys/Signature-Decoding-v12-9-QA-151f86d67d68802baddfebf3e44aea5e?pvs=4#151f86d67d6880f5a69aff17d227329d) ## **Screenshots/Recordings** Screenshot 2024-12-04 at 4 01 53 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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/main/.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. commit f8ae136673c1f1c37f91e4a412e2fbb77a9d35e1 Author: Nidhi Kumari Date: Wed Dec 4 09:40:16 2024 +0000 fix: updated analytics preferences to be logged during onboarding (#28897) During onboarding while clicking on I agree button, the analytics preferences event was not being triggered. This PR is to ensure analytics preferences are logged when user click on `I agree` button in Onboarding Page ## **Related issues** Fixes: [https://github.com/MetaMask/MetaMask-planning/issues/3723](https://github.com/MetaMask/MetaMask-planning/issues/3723) ## **Manual testing steps** 1. Run extension with yarn start 2. Add console.log in trackEvent in ui/contexts/metametrics.js to see the payload 3. Do a fresh install and go on onboading flow 4. Click on I agree button on onboarding screen, check analytics preferences selected are being logged in console ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/35e156a1-ea81-4dd6-a037-87cfcd581773 ## **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/main/.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/main/.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. commit 4534f9a55a11c92daca4d750e889614f68718a33 Author: Mathieu Artu Date: Tue Dec 3 19:38:26 2024 +0100 fix: check if a user is signed in before attempting to sign them out (#28874) ## **Description** This PR adds a `isUserSignedIn` check before attempting to sign him out. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28874?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3709 ## **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/main/.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/main/.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. commit 589d7e4d1b45723183b825ba958d482fad53a23b Author: Danica Shen Date: Tue Dec 3 16:09:34 2024 +0000 feat(27256): implement remote feature flag feature (#28684) ## **Description** - Consume feature-flag-controller built in https://github.com/MetaMask/metamask-extension/issues/27254. - And When Basic Functionality toggle is OFF, feature flag request should not be made, not call getFeatureFlags, which can be aligned with https://github.com/MetaMask/mobile-planning/issues/1975 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28684?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27256 ## **Manual testing steps** 1. Load the extension 2. Option 1: input `chrome.storage.local.get(console.log)` in dev tools console, and you'll find in `data` => `RemoteFeatureFlagController` => contains 2 dataset, `cacheTimestamp` and `remoteFeatureFlags` with value 3. Option 2: go to settings => advanced, and click `download stage logs` button, you'll find `remoteFeatureFlags` with cached value in your state. 4. Option 3: Settings => about page, if you open console in dev tools, there's a log after `Feature flag fetched successfully`, which will be removed after we implement 1st feature flag in production ## **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: Mark Stacey Co-authored-by: MetaMask Bot Co-authored-by: Dan J Miller commit af68fe550a1bf9f8a991c7f35e7cca3e71e11d8d Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Tue Dec 3 11:36:54 2024 +0100 test: [POM] improve homepage class implementation (#28797) ## **Description** The current homepage class contains methods for asset, NFT, and activity lists, resulting in a long file that can make it challenging to locate specific methods. To improve organization and maintainability, I have refactored the homepage into four separate class files: - homepage - asset_list - nft_list - activity_list Changes: Refactoring: Split the methods into respective class files to streamline the structure and make the codebase more modular. Method update: Removed the `waitUntilAssetListHasItems` method, as it duplicates the functionality of the existing `check_tokenListItem` method. I added the `skip-e2e-quality-gate`label, as this PR involves many import line changes in test specs. However, there are no functional changes for tests and methods in this PR, i have only splitted original functions in different files. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** Fixes: ## **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: Harika <153644847+hjetpoluru@users.noreply.github.com> commit 3ed6c7592dab30527a3654e9326645dcc7a18807 Author: seaona <54408225+seaona@users.noreply.github.com> Date: Tue Dec 3 08:40:36 2024 +0100 chore: cleanup on some names and scripts in ci (#28844) ## **Description** After the default branch has been renamed from develop to main, there were some names that could improve on some ci scripts/files. This Pr updates them It also updates how the git diff was run, to use yarn as a better practice (pointed by Mark on another PR for the rerun-from-failed) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28844?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. All jobs should continue to work ## **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/main/.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/main/.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. commit b9bb7b2f0407ded68a94be45580369484efb3278 Merge: b744b09a2c 7689c6e4bb Author: Dan J Miller Date: Tue Dec 3 03:34:47 2024 -0330 Merge remote-tracking branch 'origin/master' into merge-master-v12.8.0-to-v12.9.0 commit 7689c6e4bbab2ad8df278f3b383ba4f018714d61 Merge: 040cb8d738 d909bde8ea Author: Dan J Miller Date: Tue Dec 3 03:20:21 2024 -0330 Merge pull request #28503 from MetaMask/Version-v12.8.0 chore: Version v12.8.0 commit d909bde8ead2447f4746e1b435ad71179df4ec88 Author: Brian Bergeron Date: Mon Dec 2 16:12:59 2024 -0800 fix(cherry-pick): use PORTFOLIO_VIEW flag to determine token list polling (#28585) Cherry picks https://github.com/MetaMask/metamask-extension/pull/28579 to 12.8.0 so chains aren't polled unnecessarily Co-authored-by: Dan J Miller commit 7ab130c56a456913d4ae003a2fbb98f3c6729c0a Author: Desi McAdam Date: Mon Dec 2 16:52:27 2024 -0700 chore: Improvements to codeowners (#28851) - Extension Devs is no longer in use so removing - Sometime back the security team asked that we have a group who would be responsible for being mindful of security concerns as it relates to things like our CI pipeline. We needed a quick fix for that and so the library admins group was selected but it should really be a group that is best suited for that responsibility so we have created a new group reflecting that here and have added them as code owners for those areas. - Kumavis showing with the lock icon regularly was confusing for folks so we have removed them as a specific code owner ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28851?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/main/.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/main/.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. commit 16271818cb0c7461de4d7a82e522d41c3e4b30e5 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Mon Dec 2 18:47:08 2024 -0500 ci: pin image versions used in CI so it remains deterministic (#28779) We shouldn't use image versions in CI that can update all willy-nilly. This will result in non-deterministic test runs. `ubuntu-2404:2024.05.1` is currently the same as `ubuntu-2404:2024.05.1` (see https://discuss.circleci.com/t/new-ubuntu-24-04-linux-machine-executor-image/51018#linux-1) commit 19d5637e22eab3e4f87284704a3362bc044425b3 Author: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Mon Dec 2 17:08:41 2024 -0500 feat: cross chain swaps - tx status - UI (#28657) ## **Description** This PR is a collection of all the UI related code from https://github.com/MetaMask/metamask-extension/pull/27740 (no UI changes). It has been split up in order to make it easier to review. The main addition is the Bridge Transaction Details and its supporting code. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28657?quickstart=1) ## **Related issues** Related to #27740, #28636 ## **Manual testing steps** Add `BRIDGE_USE_DEV_APIS=1` to `.metamaskrc` to enable Bridge Refer to to #27740 ## **Screenshots/Recordings** Refer to to #27740 ## **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. commit 8e074a60b4b97d0d31b3259048dc333792029716 Author: Brian Bergeron Date: Mon Dec 2 13:26:37 2024 -0800 fix(cherry-pick): use PORTFOLIO_VIEW flag to determine chain polling (#28578) Cherry picks https://github.com/MetaMask/metamask-extension/pull/28504 to 12.8.0 so chains aren't polled unnecessarily Co-authored-by: Dan J Miller commit 0304023863503c2cd4b81720f2a8f0c520a99eb4 Author: Harika <153644847+hjetpoluru@users.noreply.github.com> Date: Mon Dec 2 14:21:55 2024 -0500 chore: Changelog v12.8.0 (#28692) ## **Description** This PR is to document changelog with v12.8.0 PR's [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28692?quickstart=1) commit c5df9dca33b81e64af216b9da52a761a36a9edb7 Author: Davide Brocchetto Date: Mon Dec 2 10:59:49 2024 -0800 test: Allow token balance to populate after swap (#28744) ## **Description** After importing the test account which contains the funds to execute the swaps it takes a few seconds before the balance would show up in the wallet. So I added steps to the script to pause and wait for that balance to show up before executing the tests. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28744?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 - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [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. commit 93e480c15a03b8335b393455b4ea37218abc4a39 Author: Jyoti Puri Date: Mon Dec 2 20:32:07 2024 +0530 fix: optimize helper methods for signature e2e (#28810) ## **Description** Signature e2e are slower after this PR is merged: https://github.com/MetaMask/metamask-extension/pull/28423/. This PR optimises those signature methods. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28809 ## **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. commit ca64247b33b7ac093c10b3f4dff75b0b5cd8c170 Author: Mark Stacey Date: Mon Dec 2 11:10:14 2024 -0330 chore: Reduce MMI trigger machine size (#28689) ## **Description** The `check-mmi-trigger` job was using a medium machine, but the work performed during this job is trivial and doesn't require a medium machine. It has been reduced to small. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28689?quickstart=1) ## **Related issues** No related issue, just a minor CircleCI credit usage reduction. ## **Manual testing steps** See that the trigger MMI job still passes. ## **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. commit e3eab5cade2925f919655bde5499d3fe13e21f47 Author: OGPoyraz Date: Mon Dec 2 15:24:55 2024 +0100 fix: Replace `AvatarAccount` with `Identicon` (#28645) ## **Description** This PR fixes icon issue on confirmations mentioned on https://github.com/MetaMask/metamask-extension/issues/28609 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28645?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28609 ## **Manual testing steps** 1. Go to settings - select Blockies icons 2. Trigger a signature or contract interaction 3. See the addresses there are displayed in Blockies 4. Go to settings - select JazzIcons 5. Trigger a signature or contract interaction 6. See the addresses there are displayed in JazzIcons ## **Screenshots/Recordings** ### **Before** ![before](https://github.com/user-attachments/assets/41727b32-f7c6-411c-9bf4-45b041a059fd) ### **After** ![after](https://github.com/user-attachments/assets/9774f4ec-92ab-4481-81bd-5a70c0f3a90b) ## **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. commit b744b09a2c0164b8f446c6fa7b2ee8b21601ba16 Author: Salim TOUBAL Date: Mon Dec 2 15:22:16 2024 +0100 fix: fix asset-list e2e test (#28822) (#28841) ## **Description** cherry pick #28822 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28841?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. commit b6bf219d1a5c883ccf69705f211aacc543e8fd48 Author: Mark Stacey Date: Mon Dec 2 10:30:46 2024 -0330 chore: Rename `develop` to `main` (#28821) ## **Description** Rename references to the main branch. It will be called `main` rather than `develop`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28821?quickstart=1) ## **Related issues** Relates to https://github.com/MetaMask/MetaMask-planning/issues/3677/ ## **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. --------- Co-authored-by: Dan J Miller commit 3ad954a2f6e0def316270b1ded679f02f25a9867 Author: seaona <54408225+seaona@users.noreply.github.com> Date: Mon Dec 2 14:36:33 2024 +0100 chore: accept regex expression for rerun-from-failed trigger from circle ci UI, so we can add multiple triggers following the name convention (#28804) ## **Description** In circle ci UI, there is no way to schedule a trigger for different times in a day. Instead you can only scheduled a trigger to run once a day (or multiple times within that scheduled hour). Given that we want to trigger the rerun from failed job, different times within a day, we are now accepting a regex in the trigger name, so we can add as many triggers as we want in the UI, following that pattern (see screenshot below). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28804?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. We can only test this from the UI side, once the PR is merged - I already created different triggers on circle ci ## **Screenshots/Recordings** ![Screenshot from 2024-11-29 11-10-44](https://github.com/user-attachments/assets/0c71c6f9-8673-42d2-a5fd-6d7790be95ac) ## **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. commit 5ab288b8587df64bd7a9c0ede2d0d085dfd713ae Author: Salim TOUBAL Date: Mon Dec 2 10:38:42 2024 +0100 fix: fix asset-list e2e test (#28822) ## **Description** fix e2e test for the unified list [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28822?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. run `PORTFOLIO_VIEW=1 yarn build:test` 2. Run `PORTFOLIO_VIEW=1 yarn test:e2e:single --browser=chrome test/e2e/tests/multichain/asset-list.spec.ts --debug --leave-running` ## **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. commit 7143c9643a11034a2a8b7f5f644f961be71b5e10 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Fri Nov 29 18:03:37 2024 +0100 chore: remove unused `usedNetworks` state property from `AppStateController` (#28813) ## **Description** Building on the work done to remove the network modal in [PR](https://github.com/MetaMask/metamask-extension/pull/28765), this PR finalizes the process by completely removing the unused `usedNetworks` state property from `AppStateController`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28813?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** ## **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. commit 7d252e9ca78e40c7e41034667850f07da8c70ca0 Author: Matthew Walsh Date: Fri Nov 29 16:25:29 2024 +0000 refactor: remove global network from transaction controller (#28449) ## **Description** Upgrade `@metamask/transaction-controller` to remove all usages of the global network. Specifically: - Remove deleted constructor options. - Add `getGlobalChainId` and `getGlobalNetworkClientId` private methods in `MetamaskController`. - Remove `TRANSACTION_MULTICHAIN` environment variable. - Add `networkClientId` to test data. - Update calls to: - `addTransaction` - `estimateGasBuffered` - `getNonceLock` - `getTransactions` - `startIncomingTransactionPolling` - `updateIncomingTransactions` - `wipeTransactions` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28449?quickstart=1) ## **Related issues** Fixes: [#3499](https://github.com/MetaMask/MetaMask-planning/issues/3499) ## **Manual testing steps** Full regression of all transaction flows. ## **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. commit 717745208357ea656d65d2ae25b351236d1e5892 Author: Frederik Bolding Date: Fri Nov 29 14:57:39 2024 +0100 [cherry-pick] fix: Correct preferences controller usage for `isOnPhishingList` hook (#28806) ## **Description** This is a cherry-pick to the RC for the following commit: https://github.com/MetaMask/metamask-extension/commit/db4386f6e25c8a96bd2510588c9ba462a51d9ca1 In [this commit](https://github.com/MetaMask/metamask-extension/commit/cedabc62e45601c77871689425320c54d717275e) the preferences controller was converted to `BaseControllerV2`, however the `isOnPhishingList` hook was not corrected to reference the state properly. The hook will currently always throw which means that link validation fails for Snaps notifications, making them unable to display. This PR corrects that mistake. Note: This is an edge-case of the Snaps API that doesn't have good E2E coverage yet. We should prioritize that. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28803?quickstart=1) ## **Manual testing steps** The following Snap should work correctly and display a notification: ``` export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request, }) => { switch (request.method) { case 'hello': return snap.request({ method: 'snap_notify', params: { type: 'inApp', message: 'Hello! [metamask.io](https://metamask.io)', }, }); default: throw new Error('Method not found.'); } }; ``` commit 5e52a7d6d05db865fefdb7e7fdab82b090089911 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri Nov 29 12:14:33 2024 +0100 test: [POM] fix change language flaky tests and migrate tests to Page Object Model (#28777) ## **Description** - Fix change language flaky tests. There are multiple reasons for the flakiness, including taking actions during the loading spinner and excessive misuse of refresh page - Migrate change language e2e tests to Page Object Model - Created base pages for advanced settings page and general settings page - For some special language-related locators, as they are only used in this specific test, I didn't migrate them to POM methods. [![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/27904 https://github.com/MetaMask/metamask-extension/issues/28698 https://github.com/MetaMask/metamask-extension/issues/27390 ## **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. commit 8c7e8b154d91bd721499ba46b2aea93dcf492430 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri Nov 29 12:00:14 2024 +0100 chore: Fix changelog title v12.9.0 (#28807) ## **Description** Updating the title in the changelog for v12.9.0 entries to help pass the CI [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28807?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. commit db4386f6e25c8a96bd2510588c9ba462a51d9ca1 Author: Frederik Bolding Date: Fri Nov 29 11:31:07 2024 +0100 fix: Correct preferences controller usage for `isOnPhishingList` hook (#28803) ## **Description** In [this commit](https://github.com/MetaMask/metamask-extension/commit/cedabc62e45601c77871689425320c54d717275e) the preferences controller was converted to `BaseControllerV2`, however the `isOnPhishingList` hook was not corrected to reference the state properly. The hook will currently always throw which means that link validation fails for Snaps notifications, making them unable to display. This PR corrects that mistake. Note: This is an edge-case of the Snaps API that doesn't have good E2E coverage yet. We should prioritize that. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28803?quickstart=1) ## **Manual testing steps** The following Snap should work correctly and display a notification: ``` export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request, }) => { switch (request.method) { case 'hello': return snap.request({ method: 'snap_notify', params: { type: 'inApp', message: 'Hello! [metamask.io](https://metamask.io)', }, }); default: throw new Error('Method not found.'); } }; ``` commit a48abf3978ad5e5328b8827633bf54321db176cc Author: MetaMask Bot Date: Fri Nov 29 10:20:31 2024 +0000 Version v12.9.0 commit cbb57a13a57a5e364efa7f121199a08c9fdbf77a Author: seaona <54408225+seaona@users.noreply.github.com> Date: Fri Nov 29 10:18:23 2024 +0100 fix: SIWE e2e test timing out and breaking ci (#28801) ## **Description** SIWE tests are timing out, making our ci broken, as they take very long to execute (there are several instances of waiting for event payload which take 20 seconds each, making reach the total limit of 80 seconds). This updates the timeout limits for that spec to unblock ci (based on a similar fix @chloeYue did for another long test). The Confirmations team can investigate further if that's expected ![Screenshot from 2024-11-29 09-21-45](https://github.com/user-attachments/assets/fd87f087-e291-49f6-b2fe-3acd8108bd2f) ![Screenshot from 2024-11-29 09-06-04](https://github.com/user-attachments/assets/da118cb2-d55f-4c92-9b60-430118ede4fb) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28801?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. commit 099a07b4c44473a4e8a1cc188c009fda15b5588a Author: Jony Bursztyn Date: Fri Nov 29 00:25:54 2024 +0000 fix: has_marketing_consent flag on metametrics (#28795) ## **Description** Sends the correct value for the `has_marketing_consent` flag on trackEvent. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28795?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3699 ## **Manual testing steps** 1. Go to Security settings 2. Enable the marketing consent 3. Check that the trackEvent on metametrics is passing a `true` value 4. Check same condition when triggering it off ## **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. commit a9026774328c37da7b7c31e94899955eb09b1aca Author: Mark Stacey Date: Thu Nov 28 15:48:52 2024 -0330 chore: Bump `@metamask/eth-token-tracker` from v8 to v9 (#28754) ## **Description** The `@metamask/eth-token-tracker` package has been bumped from v8 to v9. The only breaking change is dropping support for older Node.js versions. This gets rid of some older copies of dependencies, reducing bundle size. Changelog: https://github.com/MetaMask/eth-token-tracker/blob/main/CHANGELOG.md#900 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28754?quickstart=1) ## **Related issues** N/A ## **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. --------- Co-authored-by: MetaMask Bot commit efbdf3ad04da26e526a31f7703ea4752c47df23e Author: Mark Stacey Date: Thu Nov 28 14:57:41 2024 -0330 test: Fix `getEventPayloads` e2e test helper (#28796) ## **Description** The test helper `getEventPayloads` is meant to wait until all mock endpoints provided have resolved (i.e. until none are pending). However, it currently only waits until the _last_ mock endpoint in the array has been fulfilled. The pending status of the others is ignored. This results in race conditions, because the status of non-last mock endpoints is indeterminate when this returns. The `hasRequest` parameter has been renamed to `waitWhilePending` to make the expected behavior more clear, and JSDocs have been added for each parameter. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28796?quickstart=1) ## **Related issues** None. This was done to assist with debugging e2e test failures. In particular it was hoped to fix (or shed further light on) some failures we're seeing on `develop` right now. ## **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. commit a2e0b01c5b9c2cc9a6d1bc560e96e68a315ac209 Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu Nov 28 17:53:53 2024 +0100 chore: bump `@metamask/preferences-controller` to `^14.0.0` (#28778) ## **Description** This PR bumps `@metamask/preferences-controller` to `^14.0.0` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28778?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28491 ## **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 - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit f1543c94b0d0765742fa46970f2d5f3e715ebcd0 Merge: 9b067c82e9 c0ecb688f9 Author: Dan J Miller Date: Thu Nov 28 13:41:24 2024 -0330 Merge pull request #28794 from MetaMask/master-sync chore: Master sync after 12.7.2 commit c0ecb688f9f9a4dfbc948076b56e333fc89b14f1 Merge: 2b5b77f7bb 94c6aa9f17 Author: Chloe Gao Date: Thu Nov 28 17:46:03 2024 +0100 Merge branch 'master-sync' of https://github.com/MetaMask/metamask-extension into master-sync commit 2b5b77f7bb4a84806ebe2bc561b368d2adb9e832 Merge: 040cb8d738 9b067c82e9 Author: Chloe Gao Date: Thu Nov 28 17:45:36 2024 +0100 Merge origin/develop into master-sync commit 0ad999033f5e8472e2f90f48790ef50e9775f01a Merge: 2d8473200c 5453b96a55 Author: Dan J Miller Date: Thu Nov 28 13:06:51 2024 -0330 Merge pull request #28771 from MetaMask/merge-master-12.7.2-12.8.0 Merge master at v12.7.2 into 12.8.0 commit 9b067c82e96d78e93e716b146810471ebe3dbcad Author: Jyoti Puri Date: Thu Nov 28 21:37:21 2024 +0530 feat: adding e2e for signature decoding api and enable it in extension (#28423) ## **Description** adding e2e for signature decoding api and enable it in extension ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3625, https://github.com/MetaMask/MetaMask-planning/issues/3692 ## **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. commit f27dcc8adaf38d517cc672ea33d3893e6db52403 Author: Daniel <80175477+dan437@users.noreply.github.com> Date: Thu Nov 28 14:47:51 2024 +0100 feat: Support returning a txHash asap for smart transactions (#28770) ## **Description** - Updates the STX controller to v15.0.0 - Renames `returnTxHashAsap` to `extensionReturnTxHashAsap` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28770?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. A feature flag for returning a txHash asap has to be set to true. 2. It can be tested e.g. via a dapp transaction. A dapp should receive a txHash asap right after submitting a smart transaction ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: MetaMask Bot commit 827ae999b082d0280dfaca7c755a8413c7b0f67a Author: Alejandro Garcia Anglada Date: Thu Nov 28 14:08:07 2024 +0100 feat: multichain send action adds solana (#28738) ## **Description** Added support for Solana on the Send button action. When the user click on Send from the BTC snap, it will open the BTC send flow. When the user click on Send from the Solana snap, it will open the Solana send flow. Screenshot 2024-11-26 at 18 05 24 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28738?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** As of right now, manually testing is a bit complex, it needs to run the snap manually and the extension, since we 1st need to publish a new release to npm with more up to date work. The snap version we have in npm is outdated and won't support this flow. That said, if you want to go ahead and run locally the steps are the following: 1. Clone the [ Solana Snap monorepo](https://github.com/MetaMask/snap-solana-wallet) and run it locally with `yarn` and then `yarn start` 2. In the extension, at this branch, apply the following changes and run the extension as flask: ``` At builds.yml add the solana feature to the flask build: features: - build-flask - keyring-snaps + - solana At shared/lib/accounts/solana-wallet-snap.ts point the snap ID to the snap localhost: -export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +//export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +export const SOLANA_WALLET_SNAP_ID: SnapId = "local:http://localhost:8080/"; ``` 3. Manually install the snap via the snap dapp at http://localhost:3000 4. Enable the Solana account via Settings > Experimental > Enable Solana account 5. Create a Solana account from the account-list menu and see the account overview of it ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [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. commit 8865e9a5b240603bd291e626366e010694eb8455 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Thu Nov 28 01:40:39 2024 -0800 chore: Cleanup PortfolioView (#28785) ## **Description** Final cleanup before PortfolioView RC. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28785?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: Salim TOUBAL commit 5430c8998a68a3d43f52a357976a0fcd7f066df0 Author: Priya Date: Thu Nov 28 13:56:06 2024 +0700 test: migrate signature redesign tests to page object model (#28538) ## **Description** Migrates the existing signature redesign e2e tests to use page object model. Adds the relevant pages and updates the tests using the newly created pages. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28538?quickstart=1) ## **Related issues** Fixes: [#28540](https://github.com/MetaMask/metamask-extension/issues/28540) ## **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. commit b963edc86bf3aa52f9835154db31131896042b25 Author: Mark Stacey Date: Wed Nov 27 20:23:52 2024 -0330 chore: Bump `@metamask/message-manager` to v11 (#28758) ## **Description** The `@metamask/message-manager` package has been updated from v10 to v11. The only breaking change is the removal of exports previously used by the signature controller which are no longer used. Changelog: https://github.com/MetaMask/core/blob/main/packages/message-manager/CHANGELOG.md#1100 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28758?quickstart=1) ## **Related issues** N/A ## **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. --------- Co-authored-by: MetaMask Bot commit 208245a6133edd499536db1253270d0515d1903d Author: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu Nov 28 00:04:20 2024 +0100 feat: migrate `AppMedataController` to inherit from BaseController V2 (#28783) ## **Description** This PR migrate `AppMedataController` to inherit from BaseController V2 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28783?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28467 ## **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 - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit 781267ab282abce01c8cb44fcb388bc3a85fcb12 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed Nov 27 15:00:17 2024 -0800 chore: Add error handling for `setCorrectChain` (#28740) ## **Description** Adds error handling to `setCurrentNetwork` and additional check to make sure that the user is never redirected to an incorrect network that is incompatible with the token they are trying to send/swap. This is an edge case, and should be revisited post-launch for a more elegant/user friendly approach. We are now checking to make sure that the network and token chainIds match before redirecting. Very rarely, this will result in the user needing to click the send/swap button twice before they get redirected to the send/swap flow. In our opinion, this was preferable to potentially redirecting the user to an incorrect network, where they could transact with a token incompatible with the network they are on. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28740?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** ## **Screenshots/Recordings** ## **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. commit 3fa28bf08aae23eca22618bfeccdf0b39f91179d Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed Nov 27 14:38:37 2024 -0800 fix: Add optional chaining to currencyRates check for stability (#28753) ## **Description** https://github.com/MetaMask/metamask-extension/issues/28750 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28753?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28750 ## **Manual testing steps** Testing steps in issue. Behavior is inconsistent, hard to replicate consistently. ## **Screenshots/Recordings** ## **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. commit 6aeb77791ab67ae3f72868608fc2337fcce64e2d Author: Mark Stacey Date: Wed Nov 27 18:35:20 2024 -0330 chore: Bump `@metamask/providers` to v18 (#28757) ## **Description** The `@metamask/providers` package has been updated from v14 to v18. The breaking changes do not require any changes to the extension. They include: * Removal of certain provider features, which were later restored * Updates to JSON-RPC packages that used to impact error serialization in a breaking way, but no longer do (as of rpc-errors v7.0.1) * `webextension-polyfill` added as a peer dependency * Sub-path exports no longer work due to addition of exports and ESM build. Altogether this should result in no functional changes, aside from eliminating some redundant older copies of libraries (reducing bundle size). This update was difficult because of the need for a subpath export in `inpage.js`. We want to import just the `initializeInpageProvider` module without the rest of the package, which means we need to use a subpath export (because browserify doesn't support tree shaking). But this was challenging because the export maps added in v17 prevent the use of any subpath exports not defined in the export map. There was no entry that worked correctly across webpack and browserify. This was resolved by a new entry added in https://github.com/MetaMask/providers/pull/391 (as part of v18.2.0) Changelog: https://github.com/MetaMask/providers/blob/main/CHANGELOG.md#1820 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28757?quickstart=1) ## **Related issues** N/A ## **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. commit be331756eae66fcdfafe75d2897cddd8ba07c254 Author: Mark Stacey Date: Wed Nov 27 18:18:48 2024 -0330 chore: Bump `@metamask/eth-json-rpc-middleware` to v15.0.0 (#28756) ## **Description** Bump `@metamask/eth-json-rpc-middlware` from v14.0.2 to v15.0.0. This eliminates some older copies of libraries from our dependency tree, and aligns the `json-rpc-engine` and `rpc-errors` versions with the rest of our middleware stack. This release also drops support for `eth_sign`, but that has been removed from the extension already. Changelog: https://github.com/MetaMask/eth-json-rpc-middleware/blob/main/CHANGELOG.md#1500 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28756?quickstart=1) ## **Related issues** N/A ## **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. --------- Co-authored-by: MetaMask Bot commit 080d005b219b7e10425f37005ad9ee67d1554119 Author: mar <72634565+mindofmar@users.noreply.github.com> Date: Wed Nov 27 14:30:24 2024 -0600 fix: Phishing page metrics (#28364) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28364?quickstart=1) ## **Related issues** Fixes: https://github.com/orgs/MetaMask/projects/103/views/1?pane=issue&itemId=86413189 ## **Manual testing steps** See Issue attached in Related Issues. ## **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: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com> commit c0574f4bcf8c51b3c04f48f2bbe51465ed1e8f14 Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed Nov 27 12:04:03 2024 -0800 feat: Turn on `PortfolioView` (#28661) ## **Description** Sets `PORTFOLIO_VIEW` feature flag to `true`. Rather than fully removing this, we are defaulting to true in case we need to roll the feature back behind the feature flag. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28661?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: Salim TOUBAL commit d4575394108fab7fe70adc83e676f39bdba181c1 Author: sahar-fehri Date: Wed Nov 27 20:40:21 2024 +0100 fix: remove network modal (#28765) ## **Description** PR to remove network modal ![image](https://github.com/user-attachments/assets/86c355d1-0cbe-4fdc-a47c-1ae1d00c48fc) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28765?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to home page and click Network selector and go to 'Additional networks' section 2. Click on "Add" and then click on "approve" 3. You should not see another network modal "You're now using.." ## **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. commit 86e616b2c69284146a5c7aa98b411634ca9fed1f Author: Mark Stacey Date: Wed Nov 27 14:26:11 2024 -0330 chore: Update `@metamask/polling-controller` to v11 (#28759) ## **Description** The `@metamask/polling-controller` package has been updated from v10 to v11. This update included several breaking changes to the polling controller mixins, impacting the `BridgeController` and `BridgeStatusController`. They have been updated to account for the changes. There should be no functional changes. Changelog: https://github.com/MetaMask/core/blob/main/packages/polling-controller/CHANGELOG.md#1100 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28759?quickstart=1) ## **Related issues** N/A ## **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. --------- Co-authored-by: MetaMask Bot commit 7a9793f5701f507764a524c13b6fc6b99f36f652 Author: Jongsun Suh Date: Wed Nov 27 12:19:12 2024 -0500 perf: Prevent Sentry from auto-generating spans for requests to Sentry (#28613) ## Motivation The Sentry browser tracing integration intercepts all outgoing fetch requests, and measures their duration in automatically-generated `http.client` spans. Currently, our Sentry configuration does not exclude POST requests to Sentry DSN endpoints from this process. Unfortunately, these requests appear to have been taking up not just a non-negligible, but an outright dominating footprint in our Sentry performance quota and budget. Based on usage data over the past 90 days in our `metamask` Sentry project, we can make the following observations (the following data only covers requests sent to `sentry.io`, and doesn't include requests sent to hostnames under that domain e.g. `*.ingest.us.sentry.io`): - In both the `/home.html` and `/popup.html` traces, the auto-generated spans for sentry POST requests collectively rank in 3rd place by the "Time Spent" metric, only coming in behind the `ui.long-task` and `pageload` spans, and actually being ahead of `navigation` as well as all other legitimate requests to resource API endpoints. Screenshot 2024-11-21 at 8 16 04 AM > https://metamask.sentry.io/performance/summary/spans/?project=273505&query=&statsPeriod=90d&transaction=%2Fpopup.html Screenshot 2024-11-21 at 8 17 20 AM > https://metamask.sentry.io/performance/summary/spans/?project=273505&query=&statsPeriod=90d&transaction=%2Fhome.html - While the average duration of the Sentry spans is around 0.5s, there are instances where they last for much longer (30s - 1m). - An argument could be made that we should leave the Sentry spans to measure these edge cases. However, I think it would be a safe assumption that drastic delays like these will be accompanied by and captured through similar delays in other outgoing network requests. Unless we have reason to target delayed communications that only affect Sentry, filtering out these spans wouldn't result in meaningful information being lost. Screenshot 2024-11-21 at 8 18 37 AM > https://metamask.sentry.io/performance/summary/spans/http.client:8ee04019c21406cc/?project=273505&query=&sort=-span.duration&statsPeriod=90d&transaction=%2Fpopup.html Screenshot 2024-11-21 at 8 18 43 AM > https://metamask.sentry.io/performance/summary/spans/http.client:8ee04019c21406cc/?project=273505&query=&sort=-span.duration&statsPeriod=90d&transaction=%2Fhome.html ## **Description** To fix this, we need to filter out communications with Sentry itself from the monitoring data that is logged to the Sentry dashboard. This commit achieves this by configuring Sentry's browser tracing integration with its `shouldCreateSpanForRequest` option. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28613?quickstart=1) ## **Related issues** - Closes https://github.com/MetaMask/MetaMask-planning/issues/3636 ## **Manual testing steps** 1. Initialize the MetaMask extension with Sentry enabled. 2. [Optional] Take actions that are measured by custom spans (e.g. toggle between account overview tabs, load the account list). This isn't necessary, as communications with Sentry will already have taken place in the first step. 3. Verify in the Sentry dashboard that no recent `http.client` spans for 'sentry.io' have been logged. ## **Screenshots/Recordings** ### **Before** Note the three `http.client` spans for 'sentry.io': Screenshot 2024-11-21 at 8 37 55 AM ### **After** No auto-generated `http.client` spans show up in the Sentry dashboard for POST requests to the Sentry DSN endpoint, even though the requests are still going out. `http.client` spans for other URLs are still being created and measured. Screenshot 2024-11-21 at 7 49 39 AM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit f249b8ce8ea0ffa8fd71b343b1c881ba3e98cff6 Author: Hassan Malik <41640681+hmalik88@users.noreply.github.com> Date: Wed Nov 27 12:00:08 2024 -0500 feat: Integrate Snap notification services (#27975) ## **Description** Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? Snap notifications were moved into the `NotificationServicesController` to reduce code duplication. 2. What is the improvement/solution? The `NotificationController` and associated code was deleted, as well as code that was written to process snap notifications. Parts of the codebase that expect snap notifications from the `NotificationController` source were also updated. See https://github.com/MetaMask/core/pull/4809 for changes made to the `NotificationServicesController` ## **Screenshots/Recordings** ### **After** https://github.com/user-attachments/assets/e84f5209-20fa-4b1d-ace7-2eec08bb2329 ## **Manual testing steps** 1. Pull down https://github.com/hmalik88/swiss-knife-snap and run `yarn start` to serve the snap and site. 2. Build this branch using `yarn start:flask` 3. Install the snap from the local host site. 4. Trigger a notification with the "trigger a within limit notification" button and observe the results. ## **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. commit 068c456890caa990e5af47cd836838afbee33f74 Author: David Walsh Date: Wed Nov 27 10:17:08 2024 -0600 feat: PortfolioView: Add feature flag check for polling intervals (#28501) ## **Description** Adds a remote endpoint check to see if the remote API wants us to update the number of seconds between polling intervals for balances. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28501?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Open the extension 2. See the network request being made in the Networks devtools panel 3. Change conditional in response to `if (true) {` and hardcode a `pollInterval` value 4. See `this.tokenBalancesController.setIntervalLength` successfully called. ## **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. commit a2651c025a78c5c84eb395728d371ac9127301e3 Author: sahar-fehri Date: Wed Nov 27 16:59:45 2024 +0100 fix: add dispatch detect Nfts on network switch (#28769) ## **Description** PR to fix detecting NFTs when a user switches networks while on the NFT tab [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28769?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/bea181c6-b252-4c0c-bc16-dd48abaeae13 ### **After** https://github.com/user-attachments/assets/4c674e18-09c7-4b9f-81d3-fc42e59f2bc2 ## **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: salimtb commit 478537374b88070b68d08a6c8d9a124a2f4c4003 Author: Jyoti Puri Date: Wed Nov 27 21:11:05 2024 +0530 feat: on UI side filtering put typed sign V4 requests for which decoding data is displayed (#28762) ## **Description** Filter our typed sign requests for which decoding api results are displayed. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3682 ## **Manual testing steps** 1. Enable signature decoding locally 2. Submit request for permit signatures on test dapp of supported seaport signatures 3. It should work as expected ## **Screenshots/Recordings** Screenshot 2024-11-27 at 3 14 40 PM Screenshot 2024-11-27 at 3 14 31 PM ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit 78a218f7e02e6662911e7d814f93bede21fb6fb4 Author: Mark Stacey Date: Wed Nov 27 12:07:49 2024 -0330 chore: Bump `@metamask/eth-json-rpc-middleware` to v14.0.2 (#28755) ## **Description** Bump `@metamask/eth-json-rpc-middleware` from v14.0.1 (+ patch) to v14.0.2. The v14.0.2 release contains the same change as was added in the patch being removed. This bump has no functional changes. Changelog: https://github.com/MetaMask/eth-json-rpc-middleware/blob/main/CHANGELOG.md#1402 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28755?quickstart=1) ## **Related issues** Context: * https://github.com/MetaMask/metamask-extension/pull/27021 * https://github.com/MetaMask/eth-json-rpc-middleware/pull/334 ## **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. commit 70b017ab3ee83658fdaa748a8c768202113838d9 Author: Charly Chevalier Date: Wed Nov 27 16:34:40 2024 +0100 fix(wallet-overview): prevent send button clicked event to be sent twice (#28772) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28772?quickstart=1) ## **Related issues** Fixes: - https://github.com/MetaMask/metamask-extension/pull/28593/files#r1860667737 ## **Manual testing steps** N/A (being covered by unit tests) ## **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. commit 85baff382ffa17b9ccbcfa07eaf24a6f4bf412c7 Author: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Wed Nov 27 10:43:50 2024 -0500 refactor: move `getCurrentChainId` from `selectors/selectors.js` to `shared/modules/selectors/networks.ts` (#27647) Converts the selector `getCurrentChainId` from functions from JS to TS and moves it to the `shared` folder. Also updates some functions to match the actual expect return values and fixes some types. Why only this function? I'm trying to solve circular dependency issues. `getCurrentChainId` is so widely used in the codebase, it makes it very complicated to untangle. I've moved it to `shared/modules/selectors/networks.ts` --------- Co-authored-by: Howard Braham commit 5453b96a555fc86b00addcefd2007aed39826756 Author: Dan J Miller Date: Wed Nov 27 11:37:56 2024 -0330 Lint changelog commit 034222acfa074c11140def93a6739fe246502069 Merge: 2d8473200c 040cb8d738 Author: Dan J Miller Date: Wed Nov 27 10:50:32 2024 -0330 Merge remote-tracking branch 'origin/master' into merge-master-12.7.2-12.8.0 commit afe6dc0007c91ce79b495c916de50d35411a37b4 Author: Jyoti Puri Date: Wed Nov 27 19:38:48 2024 +0530 feat: adding metrics for signature decoding (#28719) ## **Description** Adding metrics for signature decoding. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3644 ## **Manual testing steps** 1. Enable signature deccoding locally 2. Open permit page 3. Check that metrics are recorded for signature decoding information ## **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. commit ddb4c97da0b35f295a5682be3f84a52cf5ccc316 Author: Salim TOUBAL Date: Wed Nov 27 14:00:08 2024 +0100 fix: fix transaction list message on token detail page (#28764) fixes #28766 ## **Description** If the token chosen in the token details does not correspond to the current network, the message displayed in the activity section should be updated accordingly. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28764?quickstart=1) ## **Related issues** Fixes: #28766 ## **Manual testing steps** 1. run `PORTFOLIO_VIEW=true yarn start` 2. choose any token who is not part of current network and go to token details 3. go to activity section and check the message ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/e0d379b8-dd37-4e87-a845-ff35a08deb76 ## **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 - [ ] 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. commit afd7e543935f36c98dc3de705f78a8a1187af754 Author: Mark Stacey Date: Wed Nov 27 09:06:39 2024 -0330 chore: Bump `@metamask/permission-log-controller` to v3.0.1 (#28747) ## **Description** Update `@metamask/permission-log-controller` from v2.0.1 to v3.0.1. No breaking changes that impact the extension. Changelog: https://github.com/MetaMask/core/blob/main/packages/permission-log-controller/CHANGELOG.md#301 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28747?quickstart=1) ## **Related issues** Relates to https://github.com/MetaMask/MetaMask-planning/issues/3568 ## **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. --------- Co-authored-by: MetaMask Bot commit 867c7f8ff2d202b1a9e58ffaea2683c398b0025b Author: Mark Stacey Date: Wed Nov 27 09:06:27 2024 -0330 chore: Bump `@metamask/ens-controller` from v13 to v14 (#28746) ## **Description** Bump `@metamask/ens-controller` to v14. There are two minor breaking changes that impact the constructor and messenger. Changelog: https://github.com/MetaMask/core/blob/main/packages/ens-controller/CHANGELOG.md#1400 This update resolves a peer dependency warning about the ENS controller's dependence upon the network controller (it was expecting v20, but we had v21). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28746?quickstart=1) ## **Related issues** Relates to https://github.com/MetaMask/MetaMask-planning/issues/3568 ## **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. --------- Co-authored-by: MetaMask Bot commit ab394d0a2710226da2547b0116091318f1e13cea Author: Priya Date: Wed Nov 27 19:22:28 2024 +0700 test: add e2e for transaction decoding (#28204) ## **Description** Adds test for checking transaction decoding for contract interaction from 4bytes, sourcify and uniswap Also adds tests to verify when all of the above fails then it falls back to the raw hexdata Adds a data-testId to make e2e easier [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28204?quickstart=1) ## **Related issues** Fixes: [#2877](https://github.com/MetaMask/MetaMask-planning/issues/2877) ## **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. commit 96fafbf2a13e8b91eed2eea608089df8f268dded Author: Priya Date: Wed Nov 27 19:22:18 2024 +0700 test: add integration tests for different types of Permit (#27446) ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27446?quickstart=1) ## **Related issues** Fixes: [#26134](https://github.com/MetaMask/metamask-extension/issues/26134) ## **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. commit b6c750d7601fc90727b7c3585626f6afe1fcda44 Author: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Wed Nov 27 13:08:52 2024 +0100 test: [POM] Migrate add token e2e tests to TS and Page Object Model (#28658) ## **Description** - Fix flaky test add hide token, the reason of flakiness is a race condition that we assert number of listed tokens before all tokens are displayed - Migrate add token e2e tests to TS and Page Object Model [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28681 https://github.com/MetaMask/metamask-extension/issues/28664 ## **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. commit f711459a0857c16b32f5382216085c69c6d99dfe Author: legobeat <109787230+legobeat@users.noreply.github.com> Date: Wed Nov 27 15:32:01 2024 +0900 chore: node.js 20.18 (#28058) ## **Description** Bump Node.js from `20.17.0` to `20.18.0` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28058?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 - [ ] 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. commit 27763f5585c272dc10be6bd0e0b8399456df3d43 Author: Mark Stacey Date: Tue Nov 26 19:40:29 2024 -0330 chore: Update `@metamask/gas-fee-controller` and peer deps (#28745) ## **Description** Update the `@metamask/gas-fee-controller` package to v21 to satisfy the peer dependency on `@metamask/network-controller@^21.0.0`, and update the `@metamask/user-operation-controller` to satisfy its peer dependency upon `@metamask/gas-fee-controller`. Note that an older version of `@metamask/gas-fee-controller` (v18) remains in the dependency tree, but only because it's imported by `@metamask/smart-transaction-controller` for type reasons. It has no runtime impact on the application, so the associated peer dependency warnings from this older release can be ignored. This will be eliminated soon, in an upcoming PR. The updated `@metamask/user-operation-controller` still does not have its peer dependencies satisfied, but the problems are pre-existing. The `@metamask/keyring-controller` and `@metamask/transaction-controller` packages are head of where this package expects them to be. This is not made worse by this PR though, and will be addressed in a future PR. Changelogs: - `@metamask/gas-fee-controller@21`: https://github.com/MetaMask/core/blob/main/packages/gas-fee-controller/CHANGELOG.md#2100 - One breaking change with an impact: >The inherited AbstractPollingController method startPollingByNetworkClientId has been renamed to startPolling (https://github.com/MetaMask/core/pull/4752) - `@metamask/user-operation-controller@16`: https://github.com/MetaMask/core/blob/main/packages/user-operation-controller/CHANGELOG.md#1600 - No breaking changes with an impact. It appeared at first that the `startPollingByNetworkClientId` was breaking, but the user operation controller had an override for that method so it was preserved ([see here](https://github.com/MetaMask/core/pull/4752/files#diff-335af05ece636eb593b348e369dff139dfbfea49ad4e9ba3bb47b4909aab9aefR304)) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28745?quickstart=1) ## **Related issues** Related to https://github.com/MetaMask/MetaMask-planning/issues/3568 ## **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. --------- Co-authored-by: MetaMask Bot commit 3f574c49cc976e4520cf5f35d9a3dc6d51ebd3e7 Author: micaelae <100321200+micaelae@users.noreply.github.com> Date: Tue Nov 26 13:44:31 2024 -0800 fix: content dialog styling is being applied to all dialogs (#28739) ## **Description** Problem: A scss change for preventing modal scrolling in the bridge experience was added and got unintentionally applied to all modals. Solution: Nest the styling within the `quotes-modal` className [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28739?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28722 ## **Manual testing steps** 1. Visually inspect Swap token picker 2. Visually inspect tx "Speed up" and "Cancel" layout ## **Screenshots/Recordings** ### **Before** See bug report screenshots ### **After** ![Screenshot 2024-11-26 at 9 48 13 AM](https://github.com/user-attachments/assets/f5bd07d5-f062-4489-8c11-5e64f88eed75) ## **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. commit 86d6c05c58af26e62826f625bd3c21ee89b5edce Author: jiexi Date: Tue Nov 26 12:46:15 2024 -0800 feat: Bump `@metamask/permission-controller` to `^11.0.0` (#28743) ## **Description** Bumps `@metamask/permission-controller` to `^11.0.0` and adopt breaking changes [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28743?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** No changes in behavior. ## **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 commit 4e1d3132511c65fe0bbc7540a68143b6fbdb21d8 Author: Jony Bursztyn Date: Tue Nov 26 20:37:28 2024 +0000 feat: add e2e tests for multichain (#28708) ## **Description** This PR adds E2E tests for multichain [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28708?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. commit 193da7660527930131cd012294455606f31e958e Author: David Walsh Date: Tue Nov 26 14:35:05 2024 -0600 fix: Add metric trait for token network filter preference (#28336) ## **Description** Adds a user trait for network filter preference for PortfolioView™ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28336?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Open debugger 2. Put a breakpoint in app/scripts/controllers/metametrics.ts's _buildUserTraitsObject function 3. See value being passed ## **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: Nick Gambino <35090461+gambinish@users.noreply.github.com> Co-authored-by: Nicholas Gambino commit c272b254f05195167b0349004643e883743f2c6d Author: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue Nov 26 10:12:21 2024 -0800 fix: Provide selector that enables cross-chain polling, regardless of network filter state (#28662) ## **Description** We cannot rely on the same selector for all cases, as not all UI is tightly coupled to the tokenNetworkFilter, else we will not be able to compute aggregated balances across chains, when filtered by current network. Since polling for balances is UI based, we can use a different selector on the network-filter, which should execute polling loops only when the dropdown is toggled open. With the current behavior, the aggregated balance will only display when "All Networks" filter is selected, and when the "Current Network" is selected, it will aggregate balances only for that chain. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28662?quickstart=1) ## **Related issues** Fixes: Current chain aggregated balance showing up in cross chain aggregated balance when current network is filterd. ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit 2a06244aeddad0b55c06ed4858599d5abefd5ce3 Author: Daniel <80175477+dan437@users.noreply.github.com> Date: Tue Nov 26 19:00:22 2024 +0100 chore: Remove unnecessary event prop (#28546) ## **Description** Removes an unnecessary event prop `smart_transaction_duplicated`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28546?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. This event prop won't be available anymore in some events after submitting a smart transaction. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. commit 040cb8d7380187adc0862df3a87e676c55737678 Merge: a53a6dd636 c4e1d93991 Author: Dan J Miller Date: Tue Nov 26 09:57:57 2024 -0330 Merge pull request #28705 from MetaMask/Version-v12.7.2 Version v12.7.2 RC commit c4e1d939911a56a117b96f42ccb8f82fa9de8b1b Author: Dan J Miller Date: Mon Nov 25 22:47:17 2024 -0330 Update changelog for v12.7.2 (#28712) commit 8724b631ae18f96c679ab49eac82aefb93fce25c Merge: c40c1a5c22 4e41eee565 Author: Dan J Miller Date: Mon Nov 25 22:44:50 2024 -0330 Merge pull request #28711 from MetaMask/cherry-pick-62430fb-12.7.2 Cherry pick 62430fb (#28573) to v12.7.2 commit 4e41eee5655a92d9cb480fd7d58875e1360f98e0 Author: Gauthier Petetin Date: Wed Nov 20 16:44:38 2024 -0300 fix(sentry sampling): divide by 2 our sentry trace sample rate to avoid exceeding our quota (#28573) ## **Description** - Divide by 2 our sentry trace sample rate to avoid exceeding our quota ## **Related issues** Fixes: None ## **Manual testing steps** - None ## **Screenshots/Recordings** - None ## **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: Harika <153644847+hjetpoluru@users.noreply.github.com> commit c40c1a5c2204efaf61fbffb62287cfec278212a6 Merge: ccc35ccd80 02ff1abfcf Author: Dan J Miller Date: Mon Nov 25 22:30:57 2024 -0330 Merge pull request #28694 from MetaMask/fix/lattice-sign-typed-error fix: lattice sign typed signature verification error commit 02ff1abfcfcdeaa37d6c4611ca7c37f9d90088cc Author: Dan J Miller Date: Mon Nov 25 21:20:39 2024 -0330 Revert "fix(deps): gridplus-sdk@2.5.1->~2.6.0 (#27973)" This reverts commit 134d9a660d593d4add38fcfa63f4b88da9bab806. commit 6777ad725bc485c7ac5507569e5528bf8634058d Author: Dan J Miller Date: Mon Nov 25 21:07:37 2024 -0330 Revert "chore: Bump gridplus-sdk to 2.7.1 (#28008)" This reverts commit fa895e502ae0e4aed5f1725756ca98b48c6763a6. commit ccc35ccd8047170bceb75be0017c0efcc63ed6f5 Author: MetaMask Bot Date: Mon Nov 25 20:42:10 2024 +0000 Version v12.7.2 commit 94c6aa9f17d7bfeca7f1513a3aa22abd15e61b07 Merge: a53a6dd636 b6613df068 Author: HJetpoluru Date: Fri Nov 22 07:06:26 2024 -0500 Merge origin/develop into master-sync commit a53a6dd63631954297b17b374ce600dc511ae356 Merge: 7bc06fc47b d0e84caded Author: Dan J Miller Date: Thu Nov 21 17:58:26 2024 -0330 Merge pull request #28498 from MetaMask/Version-v12.7.1 Version v12.7.1 RC commit 2d8473200ce510fde6ad041cdbb166c4093dfa43 Author: Mark Stacey Date: Thu Nov 21 16:28:09 2024 -0330 [cherry pick] chore: Reduce E2E test jobs run on PRs (#28525) (#28621) This is a cherry-pick of #28525 for v12.8.0. This won't actually impact the release candidate branch or release, but this is being cherry-picked to reduce credit usage from cherry-pick PRs targeting this release. E2e test runs from cherry-pick PRs aren't a major source of credit usage, but these have been making it difficult for us to gauge how many branches are still running these jobs "illegitimately" due to being out-of-date. The hope is that after merging this, we can more easily detect which branches need to be updated, and be able to evaluate the success of this strategy in reducing credit usage. Original description: ## **Description** The number of E2E test jobs run on PRs has been reduced to save on CircleCI credits. We still run the "chrome MV3" test job, but the Firefox and "chrome MV2/webpack build" E2E test jobs are now only run on `develop`, `master`, and RC branches. This should result in huge CircleCI credit savings. These jobs were chosen because it's uncommon for test failures or flakiness to manifest in these jobs without also appearing in the Chrome MV3 E2E test job, and this job represents the mmajority of our userbase (the Chrome MV2/webpack build is only used for development). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28525?quickstart=1) ## **Related issues** This is intended to reduce credit usage. There is no linked issue. ## **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). - [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. commit d0e84cadedfae678eb09b0d8ed30b0f32eb53e97 Author: Dan J Miller Date: Thu Nov 21 12:02:46 2024 -0330 Update CHANGELOG.md for v12.7.1 (#28618) commit 4c3f46cafce54d9d1d16957db0fc3c5c2bf4d133 Author: sahar-fehri Date: Thu Nov 21 15:15:10 2024 +0100 fix: fix display amount when price checker off (#28612) ## **Description** I eventually did not cherry pick https://github.com/MetaMask/metamask-extension/pull/28569 because it tried to add code related to Portfolio View which is not yet in this version, so i added the fix manually. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28612?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to Settings=> Security and privacy and disable price checker setting 2. Go back to home page and you should see correct balance in crypto ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/55931f93-962f-4e30-bc47-56755147c95d ### **After** https://github.com/user-attachments/assets/48b3efd2-46f5-43a6-9d10-a4765879cd83 ## **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. commit 5c48902ab2e713ec550dfa72a3954ae433cbb544 Author: Dan J Miller Date: Thu Nov 21 09:24:45 2024 -0330 Cherry pick d5c0aaa to 12.7.1 (#28583) This PR cherry picks https://github.com/MetaMask/metamask-extension/pull/28580 (d5c0aaa) to 12.7.1. This is needed to get the build passing Original PR description: ## **Description** Storybook CI jobs are failing due to the `playwright install` step timing out due to an AWS issue. We may be able to work around this issue by reducing the number of browsers we download. We only use chromium, so this PR limits it to just chromium [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28580?quickstart=1) Co-authored-by: David Murdoch <187813+davidmurdoch@users.noreply.github.com> commit eaac1a5b7164df43dea21c0017a07f27f43d79c2 Merge: d1b0f87983 2264779fef Author: Dan J Miller Date: Wed Nov 20 15:34:00 2024 -0330 Merge pull request #28550 from MetaMask/sync-12.7.1-restore-12.7.0 Sync v12.7.1 with master and restore 12.7.0 commit 2264779fef85ad5518f57335f60b51a2fe068352 Author: Dan J Miller Date: Tue Nov 19 16:49:12 2024 -0330 Update yarn.lock commit 9b488ebfe46dad785ec0867a7292bc05e53168c3 Author: Dan J Miller Date: Tue Nov 19 16:29:15 2024 -0330 Revert "Revert "Merge pull request #28235 from MetaMask/Version-v12.7.0"" This reverts commit 3fb593d70a1b0883af5f2fd39bf2eb54d8c01e7e. commit 49c4512f69dbde899474dd8d7fb19ed01d4505c6 Merge: d1b0f87983 7bc06fc47b Author: Dan J Miller Date: Tue Nov 19 16:25:13 2024 -0330 Merge remote-tracking branch 'origin/master' into Version-v12.7.1 commit 7bc06fc47b11d2459ce4f3bb0759e3a3c14a2417 Merge: 556109657b 2167145996 Author: Mark Stacey Date: Tue Nov 19 11:30:30 2024 -0330 Merge pull request #28507 from MetaMask/Version-v12.6.2 Version v12.6.2 RC commit 2167145996b7677e74badd206ea9f8d8e453a7a8 Author: Mark Stacey Date: Mon Nov 18 21:30:39 2024 -0330 fix: Make QR scanner more strict (#28521) (#28531) This is a cherry-pick of #28521 for v12.6.2. Original description: ## **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. commit 7fbd4fcfb29ecccee5b53544676f8900c373a6ee Author: Mark Stacey Date: Mon Nov 18 18:16:45 2024 -0330 [cherry-pick] Update `cross spawn` (#28530) This is a cherry-pick of #28522 for v12.6.2. Original description: ## **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. commit 91b289b0fdba10ff4a510573100c2a404ab54b01 Author: Dan J Miller Date: Mon Nov 18 16:32:21 2024 -0330 chore: Update changelog for v12.6.2 (#28526) Updates tje changelog for v12.6.2 commit fd14de48b40f86713dfb15f62be770b975ac6fec Author: MetaMask Bot Date: Sat Nov 16 03:28:42 2024 +0000 Version v12.6.2 commit 3fb593d70a1b0883af5f2fd39bf2eb54d8c01e7e Author: Dan J Miller Date: Fri Nov 15 23:50:37 2024 -0330 Revert "Merge pull request #28235 from MetaMask/Version-v12.7.0" This reverts commit 556109657b1fbe4cff686c73f7097305f5fcb63c, reversing changes made to aa3274428b8710628fe6f787b5639f8cfd5ea0f8. commit ec359ed9ce1e4cffdccd580f22e524ed4ae2d5dd Author: Harika <153644847+hjetpoluru@users.noreply.github.com> Date: Fri Nov 15 18:48:19 2024 -0500 chore: Fix changelog title v12.8.0 (#28505) ## **Description** Updating the title in the changelog for v12.8.0 entries to help pass the CI [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28505?quickstart=1) commit 5fa1028ab6ef6052c248b241b5108eab9755e406 Author: MetaMask Bot Date: Fri Nov 15 23:30:05 2024 +0000 Update Attributions commit e78d479956a91633399e7eeaa7fce2e4857e006c Author: MetaMask Bot Date: Fri Nov 15 21:25:28 2024 +0000 Version v12.8.0 commit d1b0f87983ab2ed16389acf08315e3ff57ca0da2 Merge: d3846fb888 e3ad6540cf Author: Dan J Miller Date: Fri Nov 15 17:06:46 2024 -0330 Merge pull request #28500 from MetaMask/cherry-pick-fix-race-condition-ppom-validation fix (cherry-pick): Fix race condition validating ERC20 transfer (blockaid) (#28487) commit e3ad6540cf10b0a2014e5a80ed3da6ef036eaa67 Author: Dan J Miller Date: Fri Nov 15 14:58:52 2024 -0330 Update changelog for v12.7.1 commit 7eaeff731cb864073894d60ca9d8aa90fb1e7521 Author: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri Nov 15 16:48:14 2024 +0000 fix: Fix race condition validating ERC20 transfer (blockaid) (#28487) ## **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. commit d3846fb8881de9ece63af056e40b14e56fc67c1c Author: MetaMask Bot Date: Fri Nov 15 17:59:38 2024 +0000 Version v12.7.1 --- .circleci/config.yml | 581 +- .circleci/scripts/bundle-stats-commit.sh | 4 +- .circleci/scripts/check-working-tree.sh | 11 - .circleci/scripts/check_mmi_trigger.sh | 2 +- ...-develop.ts => git-diff-default-branch.ts} | 21 +- .../scripts/release-create-gh-release.sh | 4 - .../scripts/rerun-ci-workflow-from-failed.ts | 13 +- .../scripts/test-run-e2e-timeout-minutes.ts | 23 +- .circleci/scripts/trigger-beta-build.sh | 25 - .../scripts/validate-source-maps-beta.sh | 22 - .devcontainer/download-builds.ts | 2 +- .github/CODEOWNERS | 36 +- .github/CONTRIBUTING.md | 6 +- .github/ISSUE_TEMPLATE/bug-report.yml | 3 +- .github/guidelines/LABELING_GUIDELINES.md | 3 +- .github/pull-request-template.md | 4 +- .../scripts/check-pr-has-required-labels.ts | 2 +- .../scripts/check-template-and-add-labels.ts | 28 +- .../workflows/add-mmi-reviewer-and-notify.yml | 4 +- .github/workflows/add-release-label.yml | 2 +- .github/workflows/build-beta.yml | 78 + .github/workflows/check-attributions.yml | 7 - .github/workflows/check-pr-labels.yml | 2 +- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/codespaces.yml | 2 +- .github/workflows/crowdin-action.yml | 2 +- .github/workflows/main.yml | 112 +- .github/workflows/publish-prerelease.yml | 76 + .github/workflows/runway.yml | 71 + .github/workflows/security-code-scanner.yml | 4 +- .github/workflows/sonarcloud.yml | 24 +- .github/workflows/test-deps-audit.yml | 18 + .github/workflows/test-deps-depcheck.yml | 18 + .github/workflows/test-lint-changelog.yml | 27 + .github/workflows/test-lint-lockfile.yml | 21 + .github/workflows/test-lint-shellcheck.yml | 15 + .github/workflows/test-lint.yml | 21 + .github/workflows/test-yarn-dedupe.yml | 18 + .github/workflows/update-coverage.yml | 2 +- .../validate-conventional-commits.yml | 2 +- .../validate-lavamoat-allow-scripts.yml | 25 + .../validate-lavamoat-policy-build.yml | 27 + .../validate-lavamoat-policy-webapp.yml | 30 + .../wait-for-circleci-workflow-status.yml | 36 + .gitignore | 3 + .nvmrc | 2 +- .prettierignore | 1 - .storybook/index.css | 18 + .storybook/preview.js | 2 +- .storybook/test-data.js | 47 +- .vscode/cspell.json | 4 +- ...ec-json-pointer-npm-0.1.2-3d06119887.patch | 13 + ...erence-resolver-npm-1.2.6-4e1497c16d.patch | 13 + ...-assets-controllers-patch-d114308c1b.patch | 116 + ...-assets-controllers-patch-d6ed5f8213.patch | 61 + ...rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch | 13 - ...ork-controller-npm-21.0.0-559aa8e395.patch | 33 - ...ork-controller-npm-22.1.1-09b6510f1e.patch | 34 + ...k-nonce-tracker-npm-5.0.0-d81478218e.patch | 30 - ...ppom-validator-npm-0.32.0-f677deea54.patch | 15 - .../eth-query-npm-2.1.2-7c6adc825f.patch | 27 - .../lavamoat-core-npm-16.2.2-e361ff1f8a.patch | 48 + .../patches/luxon-npm-3.2.1-56f8d97395.patch | 40 - .yarnrc.yml | 8 + CHANGELOG.md | 229 +- README.md | 4 +- app/_locales/am/messages.json | 6 - app/_locales/ar/messages.json | 6 - app/_locales/bg/messages.json | 6 - app/_locales/bn/messages.json | 6 - app/_locales/ca/messages.json | 6 - app/_locales/cs/messages.json | 6 - app/_locales/da/messages.json | 6 - app/_locales/de/messages.json | 195 +- app/_locales/el/messages.json | 195 +- app/_locales/en/messages.json | 339 +- app/_locales/en_GB/messages.json | 34 - app/_locales/es/messages.json | 195 +- app/_locales/es_419/messages.json | 10 - app/_locales/et/messages.json | 6 - app/_locales/fa/messages.json | 6 - app/_locales/fi/messages.json | 6 - app/_locales/fil/messages.json | 6 - app/_locales/fr/messages.json | 195 +- app/_locales/he/messages.json | 6 - app/_locales/hi/messages.json | 195 +- app/_locales/hn/messages.json | 6 - app/_locales/hr/messages.json | 6 - app/_locales/ht/messages.json | 6 - app/_locales/hu/messages.json | 6 - app/_locales/id/messages.json | 195 +- app/_locales/it/messages.json | 12 - app/_locales/ja/messages.json | 195 +- app/_locales/kn/messages.json | 6 - app/_locales/ko/messages.json | 195 +- app/_locales/lt/messages.json | 6 - app/_locales/lv/messages.json | 6 - app/_locales/ms/messages.json | 6 - app/_locales/nl/messages.json | 6 - app/_locales/no/messages.json | 6 - app/_locales/ph/messages.json | 10 - app/_locales/pl/messages.json | 6 - app/_locales/pt/messages.json | 196 +- app/_locales/pt_BR/messages.json | 10 - app/_locales/ro/messages.json | 6 - app/_locales/ru/messages.json | 195 +- app/_locales/sk/messages.json | 6 - app/_locales/sl/messages.json | 6 - app/_locales/sr/messages.json | 6 - app/_locales/sv/messages.json | 6 - app/_locales/sw/messages.json | 6 - app/_locales/ta/messages.json | 6 - app/_locales/th/messages.json | 6 - app/_locales/tl/messages.json | 195 +- app/_locales/tr/messages.json | 195 +- app/_locales/uk/messages.json | 6 - app/_locales/vi/messages.json | 195 +- app/_locales/zh_CN/messages.json | 195 +- app/_locales/zh_TW/messages.json | 10 - app/images/arbitrum.svg | 51 +- app/images/avax-token.svg | 7 +- app/images/b3.svg | 1 + app/images/base.svg | 5 +- app/images/bitcoin-logo.svg | 17 +- app/images/bnb.svg | 8 +- app/images/eth_logo.svg | 24 +- app/images/hollow-circle.svg | 3 + app/images/info-fox.svg | 1 - app/images/ink-sepolia.svg | 22 + app/images/ink.svg | 11 + app/images/kaia.svg | 9 + app/images/klaytn.svg | 45 - app/images/linea-logo-mainnet.svg | 10 +- app/images/lisk.svg | 5 + app/images/lisk_sepolia.svg | 5 + app/images/no-nfts.svg | 59 +- app/images/optimism.svg | 16 +- app/images/pol-token.svg | 23 +- app/images/slide-bridge-icon.svg | 17 + app/images/slide-card-icon.svg | 16 + app/images/slide-fund-icon.svg | 20 + app/images/slide-sell-icon.svg | 11 + app/images/soneium.svg | 5 + app/images/zk-sync.svg | 17 +- app/manifest/v2/_base.json | 2 + app/manifest/v2/brave.json | 2 +- app/manifest/v2/chrome.json | 2 +- app/manifest/v3/_base.json | 4 +- app/manifest/v3/chrome.json | 4 +- app/scripts/background.js | 164 +- app/scripts/constants/sentry-state.ts | 41 +- .../account-tracker-controller.test.ts | 27 +- .../controllers/account-tracker-controller.ts | 33 +- .../controllers/alert-controller.test.ts | 1 + app/scripts/controllers/app-metadata.test.ts | 197 +- app/scripts/controllers/app-metadata.ts | 153 +- .../controllers/app-state-controller.test.ts | 965 +- .../controllers/app-state-controller.ts | 581 +- .../bridge-status-controller.test.ts.snap | 14 +- .../bridge-status-controller.test.ts | 298 +- .../bridge-status/bridge-status-controller.ts | 216 +- .../controllers/bridge-status/mocks.ts | 327 + .../controllers/bridge-status/types.ts | 11 +- .../controllers/bridge-status/utils.ts | 43 +- .../bridge-status/validators.test.ts | 42 + .../controllers/bridge-status/validators.ts | 7 +- .../bridge/bridge-controller.test.ts | 145 +- .../controllers/bridge/bridge-controller.ts | 143 +- app/scripts/controllers/bridge/constants.ts | 32 +- app/scripts/controllers/bridge/types.ts | 58 +- .../metametrics-controller.test.ts | 52 +- .../controllers/metametrics-controller.ts | 14 +- .../controllers/mmi-controller.test.ts | 2 +- app/scripts/controllers/mmi-controller.ts | 4 +- .../controllers/permissions/background-api.js | 243 +- .../permissions/background-api.test.js | 1058 +- .../permissions/caveat-mutators.js | 71 - .../permissions/caveat-mutators.test.js | 67 - app/scripts/controllers/permissions/index.js | 1 - .../controllers/permissions/selectors.js | 27 +- .../controllers/permissions/selectors.test.js | 117 +- .../controllers/permissions/specifications.js | 293 +- .../permissions/specifications.test.js | 549 +- .../preferences-controller.test.ts | 19 +- .../controllers/preferences-controller.ts | 52 +- app/scripts/fixtures/with-preferences.js | 2 - app/scripts/inpage.js | 2 +- .../lib/AccountIdentitiesPetnamesBridge.ts | 2 +- .../lib/accounts/BalancesController.test.ts | 10 +- .../lib/accounts/BalancesController.ts | 8 +- app/scripts/lib/approval/utils.test.ts | 103 + app/scripts/lib/approval/utils.ts | 56 + app/scripts/lib/backup.test.js | 1 - .../lib/createMainFrameOriginMiddleware.ts | 24 + .../lib/createRPCMethodTrackingMiddleware.js | 4 - .../createRPCMethodTrackingMiddleware.test.js | 76 +- app/scripts/lib/ens-ipfs/resolver.js | 44 +- app/scripts/lib/manifestFlags.ts | 48 +- .../ledger-offscreen-bridge.ts | 14 +- app/scripts/lib/ppom/ppom-util.test.ts | 52 +- app/scripts/lib/ppom/ppom-util.ts | 50 +- .../createMethodMiddleware.js | 31 +- .../createMethodMiddleware.test.js | 86 +- .../handlers/add-ethereum-chain.js | 14 +- .../handlers/add-ethereum-chain.test.js | 793 +- .../handlers/eth-accounts.test.ts | 51 + .../handlers/eth-accounts.ts | 12 +- .../handlers/ethereum-chain-utils.js | 82 +- .../handlers/ethereum-chain-utils.test.ts | 358 + .../rpc-method-middleware/handlers/index.ts | 10 +- .../handlers/request-accounts.js | 123 +- .../handlers/request-accounts.test.ts | 208 + .../handlers/switch-ethereum-chain.js | 14 +- .../handlers/switch-ethereum-chain.test.js | 341 +- .../handlers/wallet-getPermissions.test.ts | 357 + .../handlers/wallet-getPermissions.ts | 106 + .../wallet-requestPermissions.test.ts | 651 + .../handlers/wallet-requestPermissions.ts | 187 + .../handlers/wallet-revokePermissions.test.ts | 150 + .../handlers/wallet-revokePermissions.ts | 85 + app/scripts/lib/setupSentry.js | 11 +- .../lib/snap-keyring/snap-keyring.test.ts | 4 +- .../lib/transaction/decode/proxy.test.ts | 30 +- app/scripts/lib/transaction/decode/proxy.ts | 19 +- .../lib/transaction/decode/util.test.ts | 12 +- app/scripts/lib/transaction/decode/util.ts | 8 +- app/scripts/lib/transaction/metrics.test.ts | 67 +- app/scripts/lib/transaction/metrics.ts | 71 +- .../transaction/smart-transactions-mocks.ts | 35 + .../transaction/smart-transactions.test.ts | 11 +- .../lib/transaction/smart-transactions.ts | 50 +- app/scripts/lib/transaction/util.test.ts | 41 +- app/scripts/lib/transaction/util.ts | 10 +- .../tx-verification-middleware.ts | 4 +- app/scripts/lib/util.ts | 3 +- app/scripts/metamask-controller.js | 1654 ++- app/scripts/metamask-controller.test.js | 1772 ++- app/scripts/migrations/105.test.ts | 10 +- app/scripts/migrations/105.ts | 17 +- app/scripts/migrations/119.ts | 2 +- app/scripts/migrations/131.1.test.ts | 254 + app/scripts/migrations/131.1.ts | 128 + app/scripts/migrations/133.1.test.ts | 224 + app/scripts/migrations/133.1.ts | 187 + app/scripts/migrations/133.2.test.ts | 185 + app/scripts/migrations/133.2.ts | 53 + app/scripts/migrations/133.test.ts | 46 + app/scripts/migrations/133.ts | 43 + app/scripts/migrations/134.test.ts | 62 + app/scripts/migrations/134.ts | 41 + app/scripts/migrations/135.test.ts | 187 + app/scripts/migrations/135.ts | 84 + app/scripts/migrations/136.test.ts | 56 + app/scripts/migrations/136.ts | 37 + app/scripts/migrations/137.test.ts | 66 + app/scripts/migrations/137.ts | 75 + app/scripts/migrations/138.test.ts | 79 + app/scripts/migrations/138.ts | 47 + app/scripts/migrations/139.test.ts | 1253 ++ app/scripts/migrations/139.ts | 430 + app/scripts/migrations/140.test.ts | 305 + app/scripts/migrations/140.ts | 89 + app/scripts/migrations/141.test.ts | 79 + app/scripts/migrations/141.ts | 47 + app/scripts/migrations/index.js | 12 + app/scripts/ui.js | 10 +- attribution.txt | 27 - builds.yml | 30 +- coverage.json | 2 +- development/build/README.md | 4 +- development/build/task.js | 3 +- development/build/utils.js | 2 +- .../charts/flamegraph/chart/index.html | 167 - .../flamegraph/lib/d3-flamegraph-tooltip.js | 3117 ----- .../charts/flamegraph/lib/d3-flamegraph.css | 46 - .../charts/flamegraph/lib/d3-flamegraph.js | 5719 -------- development/charts/table/index.html | 67 - development/charts/table/jquery.min.js | 18 - development/fitness-functions/rules/index.ts | 2 +- development/highlights/index.js | 6 +- development/lib/get-manifest-flag.ts | 101 + development/lib/run-command.js | 2 +- development/master-sync.js | 14 +- development/metamaskbot-build-announce.js | 193 +- development/webpack/build.ts | 2 +- .../utils/plugins/ManifestPlugin/index.ts | 35 +- docs/QA_MIGRATIONS_GUIDE.md | 2 +- .../README.md | 8 +- .../confirmation-page-structure/README.md | 14 +- .../confirmation-pages-routing/README.md | 10 +- .../confirmation-state-management/README.md | 16 +- .../signature-request/README.md | 46 +- docs/lavamoat-policy-review-process.md | 29 + docs/publishing.md | 8 +- docs/testing.md | 1 - jest.integration.config.js | 3 +- lavamoat/browserify/beta/policy.json | 7364 +++++----- lavamoat/browserify/flask/policy.json | 7364 +++++----- lavamoat/browserify/main/policy.json | 7364 +++++----- lavamoat/browserify/mmi/policy.json | 7458 +++++----- lavamoat/build-system/policy.json | 11195 ++++++++-------- offscreen/scripts/ledger.ts | 2 +- package.json | 163 +- privacy-snapshot.json | 9 + shared/constants/alerts.ts | 2 + shared/constants/app-state.ts | 10 + shared/constants/bridge.ts | 38 +- shared/constants/metametrics.ts | 30 +- shared/constants/multichain/accounts.ts | 12 + shared/constants/network.test.ts | 4 +- shared/constants/network.ts | 126 +- shared/constants/signatures.ts | 1 + shared/constants/smartTransactions.test.ts | 3 +- shared/constants/smartTransactions.ts | 2 + shared/constants/snaps/permissions.ts | 11 +- shared/constants/swaps.ts | 6 + shared/lib/accounts/snaps.ts | 23 + shared/lib/confirmation.utils.test.ts | 65 +- shared/lib/confirmation.utils.ts | 24 +- shared/lib/four-byte.test.ts | 12 +- shared/lib/index.d.ts | 1 - shared/lib/multichain.ts | 3 +- shared/lib/token-util.ts | 5 +- shared/modules/bridge-utils/balance.test.ts | 3 +- shared/modules/bridge-utils/balance.ts | 8 +- .../modules/bridge-utils}/bridge.util.test.ts | 116 +- .../modules/bridge-utils}/bridge.util.ts | 65 +- shared/modules/bridge-utils/quote.ts | 36 + .../modules/bridge-utils}/validators.ts | 36 +- shared/modules/contract-utils.test.js | 22 +- shared/modules/contract-utils.ts | 13 +- shared/modules/gas.utils.js | 17 +- shared/modules/gas.utils.test.js | 4 +- shared/modules/metametrics.test.ts | 3 - shared/modules/metametrics.ts | 3 - shared/modules/selectors/feature-flags.ts | 10 +- shared/modules/selectors/index.test.ts | 4 +- shared/modules/selectors/networks.ts | 37 +- .../modules/selectors/smart-transactions.ts | 29 +- shared/modules/selectors/util.js | 4 +- shared/modules/transaction.utils.test.js | 66 +- shared/modules/transaction.utils.ts | 18 +- shared/types/bridge-status.ts | 65 +- shared/types/bridge.ts | 198 + test/data/bridge/dummy-quotes.ts | 20 + test/data/bridge/mock-token-data.ts | 102 + .../confirmations/contract-interaction.ts | 1 + test/data/confirmations/helper.ts | 10 - .../confirmations/set-approval-for-all.ts | 6 +- test/data/confirmations/token-approve.ts | 5 +- test/data/confirmations/token-transfer.ts | 8 +- test/data/confirmations/typed_sign.ts | 53 + test/data/mock-accounts.ts | 9 +- test/data/mock-send-state.json | 13 +- test/data/mock-state.json | 79 +- test/e2e/constants.ts | 22 +- test/e2e/default-fixture.js | 29 +- test/e2e/fixture-builder.js | 174 +- test/e2e/flask/README.md | 2 +- .../flask/btc/btc-account-overview.spec.ts | 20 +- .../e2e/flask/btc/btc-dapp-connection.spec.ts | 19 +- .../btc/btc-experimental-settings.spec.ts | 54 +- test/e2e/flask/btc/btc-send.spec.ts | 166 +- test/e2e/flask/btc/common-btc.ts | 47 +- test/e2e/flask/btc/create-btc-account.spec.ts | 56 +- test/e2e/flask/create-watch-account.spec.ts | 417 +- test/e2e/flask/solana/check-balance.spec.ts | 61 + test/e2e/flask/solana/common-solana.ts | 300 + .../solana/create-solana-account.spec.ts | 65 + test/e2e/flask/solana/send-flow.spec.ts | 447 + .../flask/solana/solana-eth-networks.spec.ts | 24 + .../solana/switching-network-accounts.spec.ts | 24 + test/e2e/flask/user-operations.spec.ts | 10 +- test/e2e/helpers.js | 189 +- .../userStorageMockttpController.test.ts | 0 .../userStorageMockttpController.ts | 0 test/e2e/json-rpc/eth_accounts.spec.ts | 2 +- test/e2e/json-rpc/eth_sendTransaction.spec.js | 7 +- test/e2e/json-rpc/switchEthereumChain.spec.js | 1049 +- .../wallet_requestPermissions.spec.js | 59 - .../wallet_requestPermissions.spec.ts | 54 + .../json-rpc/wallet_revokePermissions.spec.js | 58 - .../json-rpc/wallet_revokePermissions.spec.ts | 183 + test/e2e/mock-e2e.js | 49 +- test/e2e/mv3-perf-stats/bundle-size.js | 2 +- test/e2e/mv3-perf-stats/index.js | 2 - test/e2e/mv3-perf-stats/init-load-stats.js | 118 - test/e2e/mv3-stats.js | 115 - test/e2e/page-objects/common.ts | 5 +- test/e2e/page-objects/flows/login.flow.ts | 2 +- .../flows/send-transaction.flow.ts | 38 +- test/e2e/page-objects/flows/sign.flow.ts | 3 +- test/e2e/page-objects/flows/transaction.ts | 2 +- .../page-objects/flows/watch-account.flow.ts | 22 + .../page-objects/pages/account-list-page.ts | 388 +- .../redesign/accountDetailsModal.ts | 42 + .../redesign/add-token-confirmations.ts | 49 + .../confirmations/redesign/confirmation.ts | 69 + .../redesign/permit-confirmation.ts | 142 + .../redesign/personal-sign-confirmation.ts | 34 + .../redesign/sign-typed-data-confirmation.ts | 92 + .../redesign/transaction-confirmation.ts | 171 + .../pages/dialog/account-details-modal.ts | 106 + .../page-objects/pages/dialog/add-tokens.ts | 50 + .../pages/dialog/confirm-alert.ts | 36 + .../pages/dialog/create-contract.ts | 39 + .../pages/dialog/select-network.ts | 1 + .../connect-hardware-wallet-page.ts | 54 + .../select-trezor-account-page.ts | 94 + test/e2e/page-objects/pages/header-navbar.ts | 49 +- .../page-objects/pages/home/activity-list.ts | 173 + .../e2e/page-objects/pages/home/asset-list.ts | 393 + .../pages/home/bitcoin-homepage.ts | 110 + test/e2e/page-objects/pages/home/homepage.ts | 262 + test/e2e/page-objects/pages/home/nft-list.ts | 89 + .../pages/home/non-evm-homepage.ts | 69 + test/e2e/page-objects/pages/homepage.ts | 367 - .../pages/permission/permission-list-page.ts | 50 + .../pages/permission/site-permission-page.ts | 125 + .../pages/send/bitcoin-review-tx-page.ts | 42 + .../pages/send/bitcoin-send-page.ts | 82 + .../pages/send/send-token-page.ts | 60 + .../pages/send/solana-confirm-tx-page.ts | 90 + .../pages/send/solana-send-page.ts | 98 + .../pages/send/solana-tx-result-page.ts | 82 + .../pages/settings/advanced-settings.ts | 32 + .../pages/settings/experimental-settings.ts | 35 +- .../pages/settings/general-settings.ts | 61 + .../pages/settings/settings-page.ts | 29 + .../pages/snap-simple-keyring-page.ts | 10 +- test/e2e/page-objects/pages/test-dapp.ts | 409 +- test/e2e/page-objects/pages/test-snaps.ts | 56 + .../page-objects/pages/token-overview-page.ts | 54 + .../global/specs/protect-intrinsics.spec.ts | 1 - .../pageObjects/network-controller-page.ts | 22 +- .../shared/pageObjects/signup-page.ts | 4 + .../shared/pageObjects/wallet-page.ts | 15 +- .../playwright/swap/pageObjects/swap-page.ts | 11 +- test/e2e/playwright/swap/specs/swap.spec.ts | 66 +- test/e2e/playwright/swap/tenderly-network.ts | 18 +- test/e2e/restore/MetaMaskUserData.json | 1 - test/e2e/run-all.js | 2 +- test/e2e/seeder/ganache.ts | 7 + test/e2e/set-manifest-flags.ts | 86 +- test/e2e/snaps/enums.js | 2 +- test/e2e/snaps/test-snap-cronjob.spec.js | 2 + test/e2e/snaps/test-snap-ethprovider.spec.js | 34 +- test/e2e/snaps/test-snap-managestate.spec.js | 149 +- test/e2e/snaps/test-snap-metrics.spec.js | 4 +- test/e2e/snaps/test-snap-revoke-perm.spec.js | 2 +- test/e2e/snaps/test-snap-siginsights.spec.js | 84 +- .../e2e/snaps/test-snap-txinsights-v2.spec.js | 174 - test/e2e/snaps/test-snap-txinsights.spec.js | 322 +- .../tests/account/account-custom-name.spec.ts | 14 +- test/e2e/tests/account/add-account.spec.ts | 28 +- .../e2e/tests/account/forgot-password.spec.ts | 2 +- test/e2e/tests/account/import-flow.spec.ts | 11 +- .../account/incremental-security.spec.js | 2 +- test/e2e/tests/account/lock-account.spec.ts | 2 +- .../tests/account/migrate-old-vault.spec.ts | 2 +- .../snap-account-contract-interaction.spec.ts | 9 +- ...account-signatures-and-disconnects.spec.ts | 27 +- .../account/snap-account-signatures.spec.ts | 13 +- .../account/snap-account-transfers.spec.ts | 436 +- .../bridge/bridge-button-routing.spec.ts | 8 +- test/e2e/tests/bridge/bridge-test-utils.ts | 6 +- test/e2e/tests/bridge/constants.ts | 11 +- test/e2e/tests/carousel/carousel.spec.ts | 115 + .../alerts/insufficient-funds.spec.ts | 1 - .../alerts/queued-confirmations.spec.ts | 14 +- test/e2e/tests/confirmations/helpers.ts | 60 +- .../tests/confirmations/navigation.spec.ts | 50 +- .../signatures/malicious-signatures.spec.ts | 82 +- .../signatures/nft-permit.spec.ts | 125 +- .../confirmations/signatures/permit.spec.ts | 149 +- .../signatures/personal-sign.spec.ts | 57 +- .../signatures/sign-typed-data-v3.spec.ts | 79 +- .../signatures/sign-typed-data-v4.spec.ts | 83 +- .../signatures/sign-typed-data.spec.ts | 52 +- .../signatures/signature-helpers.ts | 168 +- .../confirmations/signatures/siwe.spec.ts | 52 +- .../contract-deployment-redesign.spec.ts | 2 - .../contract-interaction-redesign.spec.ts | 9 - ...1155-set-approval-for-all-redesign.spec.ts | 1 + .../erc20-approve-redesign.spec.ts | 27 +- .../erc20-token-send-redesign.spec.ts | 4 +- .../erc721-approve-redesign.spec.ts | 4 +- .../increase-token-allowance-redesign.spec.ts | 50 +- .../transactions/metrics.spec.ts | 1 - .../transactions/native-send-redesign.spec.ts | 2 +- .../nft-token-send-redesign.spec.ts | 14 +- .../revoke-allowance-redesign.spec.ts | 9 +- .../confirmations/transactions/shared.ts | 67 +- .../transaction-decoding-redesign.spec.ts | 245 + .../connections/connect-with-metamask.spec.js | 79 - .../connections/edit-account-flow.spec.js | 101 - .../edit-account-permissions.spec.ts | 74 + .../connections/edit-networks-flow.spec.js | 77 - .../edit-networks-permissions.spec.ts | 49 + .../review-permissions-page.spec.js | 8 - .../review-switch-permission-page.spec.js | 2 +- .../dapp-interactions/block-explorer.spec.js | 2 +- .../contract-interactions.spec.js | 97 - .../dapp-interactions.spec.js | 5 - .../dapp-interactions/dapp-tx-edit.spec.js | 81 - .../failing-contract.spec.js | 152 - .../dapp-interactions/permissions.spec.js | 4 - .../revoke-permissions.spec.js | 45 +- .../signin-with-ethereum.spec.js | 64 +- .../hardware-wallets/lattice-connect.spec.ts | 38 +- .../hardware-wallets/trezor-account.spec.js | 156 - .../hardware-wallets/trezor-account.spec.ts | 117 + .../hardware-wallets/trezor-send.spec.ts | 27 +- .../hardware-wallets/trezor-sign.spec.ts | 40 +- .../account-syncing/helpers.ts | 0 .../importing-private-key-account.spec.ts | 32 +- .../account-syncing/mockData.ts | 0 .../account-syncing/new-user-sync.spec.ts | 32 +- .../onboarding-with-opt-out.spec.ts | 44 +- .../sync-after-adding-account.spec.ts | 56 +- .../sync-after-modifying-account-name.spec.ts | 39 +- .../sync-after-onboarding.spec.ts | 19 +- .../sync-with-account-balances.spec.ts | 36 +- test/e2e/tests/identity/constants.ts | 9 + test/e2e/tests/identity/mocks.ts | 136 + test/e2e/tests/metrics/app-opened.spec.ts | 104 + test/e2e/tests/metrics/dapp-viewed.spec.js | 4 - .../metrics/delete-metametrics-data.spec.ts | 44 +- test/e2e/tests/metrics/errors.spec.js | 10 + .../tests/metrics/signature-approved.spec.js | 19 +- ...rs-after-init-opt-in-background-state.json | 54 +- .../errors-after-init-opt-in-ui-state.json | 106 +- ...s-before-init-opt-in-background-state.json | 28 +- .../errors-before-init-opt-in-ui-state.json | 45 +- test/e2e/tests/metrics/swaps.spec.js | 1 + .../multichain/aggregated-balances.spec.ts | 137 + .../multichain/all-permissions-page.spec.js | 98 - .../multichain/all-permissions-page.spec.ts | 76 + test/e2e/tests/multichain/asset-list.spec.ts | 205 + .../multichain/asset-picker-send.spec.ts | 2 +- .../tests/multichain/permission-page.spec.js | 88 - .../tests/multichain/permission-page.spec.ts | 33 + .../tests/network/custom-rpc-history.spec.js | 1 + test/e2e/tests/network/multi-rpc.spec.ts | 2 +- test/e2e/tests/network/network-error.spec.js | 83 - test/e2e/tests/network/switch-network.spec.ts | 2 +- test/e2e/tests/network/update-network.spec.ts | 2 +- test/e2e/tests/notifications/mocks.ts | 92 +- test/e2e/tests/onboarding/onboarding.spec.ts | 2 +- test/e2e/tests/petnames/petnames-helpers.js | 6 +- .../petnames/petnames-signatures.spec.js | 44 +- .../petnames/petnames-transactions.spec.js | 14 +- test/e2e/tests/phishing-controller/mocks.js | 19 +- .../phishing-detection.spec.js | 77 +- .../tests/portfolio/portfolio-site.spec.js | 50 - .../tests/portfolio/portfolio-site.spec.ts | 50 + ...lockaid-alert-contract-interaction.spec.js | 2 - .../ppom-blockaid-alert-simple-send.spec.js | 5 +- ...blockaid-alert-trade-order-farming.spec.js | 4 +- .../tests/privacy-mode/privacy-mode.spec.js | 106 - .../tests/privacy-mode/privacy-mode.spec.ts | 74 + .../privacy/account-tracker-api-usage.spec.ts | 2 +- .../tests/privacy/basic-functionality.spec.js | 176 - .../tests/privacy/basic-functionality.spec.ts | 120 + .../onboarding-infura-call-privacy.spec.ts | 2 +- ...nboarding-token-price-call-privacy.spec.ts | 2 +- test/e2e/tests/privacy/polling.spec.ts | 2 +- .../tests/remote-feature-flag/mock-data.ts | 8 + .../remote-feature-flag.spec.ts | 48 + .../batch-txs-per-dapp-diff-network.spec.js | 301 +- .../batch-txs-per-dapp-extra-tx.spec.js | 467 +- .../batch-txs-per-dapp-same-network.spec.js | 391 +- .../request-queuing/chainid-check.spec.js | 369 +- .../dapp1-send-dapp2-signTypedData.spec.js | 429 +- .../dapp1-subscribe-network-switch.spec.js | 95 - .../dapp1-subscribe-network-switch.spec.ts | 97 + ...-switch-dapp2-eth-request-accounts.spec.js | 325 +- .../dapp1-switch-dapp2-send.spec.js | 779 +- .../request-queuing/enable-queuing.spec.js | 47 - ...multi-dapp-sendTx-revokePermission.spec.js | 351 +- .../multiple-networks-dapps-txs.spec.js | 342 +- .../sendTx-revokePermissions.spec.js | 58 - .../sendTx-revokePermissions.spec.ts | 75 + .../sendTx-switchChain-sendTx.spec.js | 2 +- .../request-queuing/switch-network.spec.js | 217 +- .../switchChain-sendTx.spec.js | 2 +- .../switchChain-watchAsset.spec.js | 2 +- test/e2e/tests/request-queuing/ui.spec.js | 1577 +-- .../watchAsset-switchChain-watchAsset.spec.js | 2 +- .../metamask-responsive-ui.spec.js | 9 +- .../tests/settings/4byte-directory.spec.js | 100 - .../tests/settings/change-language.spec.ts | 153 +- .../settings-security-reveal-srp.spec.ts | 2 +- test/e2e/tests/settings/show-hex-data.spec.js | 97 - .../e2e/tests/signature/personal-sign.spec.js | 61 +- .../tests/signature/signature-request.spec.js | 167 +- .../mock-requests-for-swap-test.ts | 3 - .../smart-transactions.spec.ts | 2 +- test/e2e/tests/swaps/shared.ts | 20 +- test/e2e/tests/swaps/swap-eth.spec.ts | 41 +- .../tests/swaps/swaps-notifications.spec.ts | 2 + test/e2e/tests/tokens/add-hide-token.spec.js | 270 - test/e2e/tests/tokens/add-hide-token.spec.ts | 50 + .../tests/tokens/add-multiple-tokens.spec.js | 114 - .../tests/tokens/add-multiple-tokens.spec.ts | 70 + .../tests/tokens/add-token-using-search.ts | 69 + .../tokens/custom-token-add-approve.spec.js | 458 - .../tokens/custom-token-send-transfer.spec.js | 77 +- ...t-tokens.spec.js => import-tokens.spec.ts} | 57 +- .../tokens/increase-token-allowance.spec.js | 329 - .../tests/tokens/nft/auto-detect-nft.spec.ts | 10 +- .../tokens/nft/erc1155-interaction.spec.js | 331 - .../tokens/nft/erc721-interaction.spec.js | 561 - .../tests/tokens/nft/import-erc1155.spec.js | 7 +- test/e2e/tests/tokens/nft/import-nft.spec.ts | 33 +- .../tests/tokens/nft/remove-erc1155.spec.js | 2 +- test/e2e/tests/tokens/nft/send-nft.spec.js | 187 - .../tokens/send-erc20-to-contract.spec.js | 53 - .../tokens/send-erc20-to-contract.spec.ts | 52 + test/e2e/tests/tokens/token-details.spec.ts | 8 +- test/e2e/tests/tokens/token-list.spec.ts | 245 +- test/e2e/tests/tokens/token-sort.spec.ts | 108 +- .../tokens/watch-asset-call-add-token.ts | 109 + .../tests/transaction/change-assets.spec.js | 64 +- .../tests/transaction/edit-gas-fee.spec.js | 44 +- test/e2e/tests/transaction/ens.spec.ts | 2 +- .../tests/transaction/gas-estimates.spec.js | 62 +- .../transaction/incoming-transactions.spec.ts | 199 + .../transaction/multiple-transactions.spec.js | 22 +- .../transaction/navigate-transactions.spec.js | 53 +- test/e2e/tests/transaction/send-edit.spec.js | 62 +- test/e2e/tests/transaction/send-eth.spec.js | 77 +- .../transaction/send-hex-address.spec.js | 15 +- .../e2e/tests/transaction/simple-send.spec.ts | 47 - .../stuck-approved-transaction.spec.ts | 11 +- test/e2e/vault-decryption-chrome.spec.ts | 2 +- test/e2e/webdriver/driver.js | 76 +- .../signatures/permit-batch.test.tsx | 190 + .../signatures/permit-seaport.test.tsx | 209 + .../signatures/permit-single.test.tsx | 164 + .../signatures/permit-tradeOrder.test.tsx | 144 + .../confirmations/signatures/permit.test.tsx | 68 +- .../signatures/personalSign.test.tsx | 75 +- .../signatures/signature-helpers.ts | 98 + .../transactions/alerts.test.tsx | 95 +- .../transactions/contract-deployment.test.tsx | 16 +- .../contract-interaction.test.tsx | 16 +- .../transactions/erc20-approve.test.tsx | 16 +- .../transactions/erc721-approve.test.tsx | 21 +- .../transactions/increase-allowance.test.tsx | 22 +- .../set-approval-for-all.test.tsx | 18 +- .../transactions/transactionDataHelpers.tsx | 1 + .../data/integration-init-state.json | 30 +- .../data/onboarding-completion-route.json | 28 +- test/integration/data/transaction-helpers.tsx | 1 + .../data/notification-state.ts | 1 + .../notifications-toggle.test.tsx | 4 +- .../onboarding/wallet-created.test.tsx | 33 +- test/jest/mock-store.js | 57 +- test/jest/mocks.ts | 33 +- test/lib/render-helpers.js | 9 +- .../upgrade-testing/upgrade-testing.md | 2 +- test/stub/provider.js | 6 + types/eth-query.d.ts | 50 - types/global.d.ts | 8 +- .../alert-system/alert-modal/alert-modal.tsx | 15 +- .../confirm-alert-modal.tsx | 2 +- .../general-alert/general-alert.tsx | 4 +- .../unconnected-account-alert.test.js | 18 +- ui/components/app/app-components.scss | 3 +- .../asset-list-control-bar.test.tsx | 62 + .../asset-list-control-bar.tsx | 48 +- .../app/assets/asset-list/asset-list.test.tsx | 12 + .../app/assets/asset-list/asset-list.tsx | 146 +- .../native-token/use-native-token-balance.ts | 9 +- .../network-filter/network-filter.tsx | 68 +- .../sort-control/sort-control.test.tsx | 3 +- .../asset-list/sort-control/sort-control.tsx | 3 +- .../nft-default-image.test.js.snap | 6 +- ...default-image.js => nft-default-image.tsx} | 31 +- .../__snapshots__/nft-details.test.js.snap | 60 +- .../__snapshots__/nft-full-image.test.js.snap | 51 +- .../app/assets/nfts/nft-details/index.scss | 2 +- .../nfts/nft-details/nft-details.test.js | 31 +- .../assets/nfts/nft-details/nft-details.tsx | 141 +- .../app/assets/nfts/nft-grid/index.scss | 19 + .../app/assets/nfts/nft-grid/nft-grid.tsx | 73 + .../assets/nfts/nft-options/nft-options.js | 68 - .../nfts/nft-options/nft-options.test.js | 18 +- .../assets/nfts/nft-options/nft-options.tsx | 89 + .../{index.js => index.ts} | 0 ... => nfts-detection-notice-import-nfts.tsx} | 8 +- .../{index.js => index.ts} | 0 ....js => nfts-detection-notice-nfts-tab.tsx} | 0 .../collection-image.component.test.tsx | 86 - .../nfts-items/collection-image.component.tsx | 53 - .../app/assets/nfts/nfts-items/index.js | 1 - .../app/assets/nfts/nfts-items/index.scss | 58 - .../app/assets/nfts/nfts-items/nfts-items.js | 397 - .../nfts/nfts-items/nfts-items.stories.tsx | 186 - .../assets/nfts/nfts-items/nfts-items.test.js | 101 - .../app/assets/nfts/nfts-tab/index.scss | 17 - .../nfts/nfts-tab/{index.js => index.ts} | 0 .../app/assets/nfts/nfts-tab/nfts-tab.test.js | 35 - .../nfts-tab/{nfts-tab.js => nfts-tab.tsx} | 248 +- .../__snapshots__/token-cell.test.tsx.snap | 72 +- .../app/assets/token-cell/token-cell.test.tsx | 2 +- .../app/assets/token-cell/token-cell.tsx | 2 +- .../app/assets/token-list/token-list.tsx | 58 +- .../basic-configuration-modal.tsx | 2 +- .../row/__snapshots__/address.test.tsx.snap | 66 +- .../info/row/__snapshots__/row.test.tsx.snap | 2 +- .../app/confirm/info/row/address.tsx | 15 +- .../info/row/alert-row/alert-row.test.tsx | 18 + .../confirm/info/row/alert-row/alert-row.tsx | 7 + ui/components/app/confirm/info/row/row.tsx | 3 +- .../app/confirm/info/row/section.tsx | 5 +- ui/components/app/confirm/info/row/text.tsx | 26 +- ui/components/app/confirm/info/row/url.tsx | 2 +- .../app/confirm/info/row/value-double.tsx | 18 +- .../connected-sites-list.component.js | 2 +- ui/components/app/contact-list/utils.ts | 2 +- .../app/currency-input/currency-input.js | 12 +- .../hooks/useTokenExchangeRate.tsx | 6 +- .../detected-token-details.js | 1 + ...tected-token-ignored-popover.test.tsx.snap | 153 + .../detected-token-ignored-popover.js | 1 + .../detected-token-ignored-popover.test.tsx | 37 + .../detected-token-selection-popover.js | 30 +- .../detected-token-values.js | 2 +- .../app/detected-token/detected-token.js | 59 +- .../app/home-notification/index.scss | 5 +- .../incoming-transaction-toggle.test.js.snap | 18 +- .../safe-component-list.js | 130 +- .../confirm-turn-off-profile-syncing.test.tsx | 4 +- .../customize-nonce.component.js | 4 +- .../hide-token-confirmation-modal.js | 6 +- .../turn-on-metamask-notifications.tsx | 2 +- .../multi-rpc-edit-modal.test.tsx.snap | 2 +- .../network-list-item.test.tsx.snap | 2 +- .../app/name/__snapshots__/name.test.tsx.snap | 2 +- ui/components/app/name/index.scss | 1 + .../__snapshots__/name-details.test.tsx.snap | 2 +- ui/components/app/name/name.tsx | 2 +- .../permission-cell-options.js | 26 +- .../permission-cell/permission-cell-status.js | 2 +- .../app/permission-cell/permission-cell.js | 6 +- .../permission-connect-header.js | 99 +- .../permission-connect-header.test.js | 5 + ...ission-page-container-content.component.js | 11 +- .../permission-page-container.component.js | 5 +- .../permissions-connect-permission-list.js | 26 +- .../selected-account-component.test.js | 1 + .../insight-warnings/insight-warnings.js | 1 + .../keyring-snap-removal-warning.tsx | 2 +- .../snap-authorship-pill.tsx | 1 - .../snap-home-page/snap-home-renderer.js | 44 +- .../app/snaps/snap-icon/snap-icon.tsx | 1 - .../app/snaps/snap-insight/snap-insight.js | 2 + .../snap-metadata-modal.js | 7 +- .../app/snaps/snap-settings-page/index.ts | 1 + .../snap-settings-renderer.tsx | 81 + .../app/snaps/snap-ui-banner/index.ts | 1 + .../snap-ui-banner/snap-ui-banner.stories.tsx | 33 + .../snaps/snap-ui-banner/snap-ui-banner.tsx | 19 + .../snaps/snap-ui-button/snap-ui-button.tsx | 21 +- .../app/snaps/snap-ui-card/snap-ui-card.tsx | 7 +- .../snap-ui-checkbox/snap-ui-checkbox.tsx | 5 +- .../snap-ui-dropdown/snap-ui-dropdown.tsx | 5 +- .../snap-ui-file-input/snap-ui-file-input.tsx | 16 +- .../snap-ui-footer-button.tsx | 15 +- .../app/snaps/snap-ui-input/snap-ui-input.tsx | 13 +- .../app/snaps/snap-ui-link/snap-ui-link.js | 44 +- .../snap-ui-markdown/snap-ui-markdown.js | 65 +- .../snap-ui-radio-group.tsx | 8 +- .../snap-ui-renderer/components/avatar.ts | 1 + .../snap-ui-renderer/components/banner.ts | 20 + .../snap-ui-renderer/components/button.ts | 4 + .../snap-ui-renderer/components/container.ts | 10 +- .../snap-ui-renderer/components/file-input.ts | 13 +- .../snap-ui-renderer/components/index.ts | 2 + .../snap-ui-renderer/components/section.ts | 10 +- .../snap-ui-renderer/components/spinner.ts | 2 +- .../snaps/snap-ui-renderer/components/text.ts | 59 +- .../snap-ui-renderer/components/types.ts | 1 + .../snap-ui-renderer/components/value.ts | 36 +- .../app/snaps/snap-ui-renderer/index.scss | 18 +- .../snap-ui-renderer/snap-ui-renderer.js | 21 +- .../app/snaps/snap-ui-renderer/utils.ts | 16 + .../app/snaps/snap-ui-selector/index.scss | 2 + .../snap-ui-selector/snap-ui-selector.tsx | 13 +- ui/components/app/tab-bar/index.scss | 5 + ui/components/app/toast-master/selectors.ts | 3 +- .../transaction-breakdown-utils.ts | 140 + .../transaction-breakdown.container.js | 124 +- .../app/transaction-icon/transaction-icon.js | 2 + .../transaction-list-item-details/index.scss | 7 +- ...transaction-list-item-details.component.js | 53 +- ...action-list-item-details.component.test.js | 66 +- .../smart-transaction-list-item.component.js | 3 +- .../transaction-list-item.component.js | 96 +- .../transaction-list-item.component.test.js | 14 + ui/components/app/transaction-list/index.scss | 4 - .../transaction-list.component.js | 85 +- .../transaction-list/transaction-list.test.js | 73 +- .../transaction-status-label.test.js.snap | 10 +- .../transaction-status-label.js | 20 +- .../transaction-status-label.test.js | 370 +- ...referenced-currency-display.component.d.ts | 2 +- ...-percentage-overview-cross-chains.test.tsx | 7 +- ...gated-percentage-overview-cross-chains.tsx | 2 +- .../aggregated-percentage-overview.test.tsx | 12 +- .../aggregated-percentage-overview.tsx | 4 +- .../app/wallet-overview/coin-buttons.tsx | 184 +- .../app/wallet-overview/coin-overview.tsx | 102 +- .../app/wallet-overview/eth-overview.js | 2 +- .../app/wallet-overview/eth-overview.test.js | 30 +- .../wallet-overview/non-evm-overview.test.tsx | 7 +- .../app/wallet-overview/non-evm-overview.tsx | 22 +- .../avatar-network.test.tsx.snap | 2 +- .../avatar-network/avatar-network.tsx | 5 +- .../__snapshots__/badge-wrapper.test.tsx.snap | 2 +- .../badge-wrapper/badge-wrapper.scss | 4 +- .../badge-wrapper/badge-wrapper.test.tsx | 9 +- .../badge-wrapper/badge-wrapper.tsx | 4 +- .../banner-base/banner-base.types.ts | 2 +- .../component-library/box/box.stories.tsx | 5 + .../component-library-components.scss | 1 + .../picker-network.test.tsx.snap | 132 +- .../picker-network/picker-network.stories.tsx | 32 + .../picker-network/picker-network.test.tsx | 45 + .../picker-network/picker-network.tsx | 25 +- .../picker-network/picker-network.types.ts | 5 + .../component-library/skeleton/README.mdx | 94 + .../__snapshots__/skeleton.test.tsx.snap | 9 + .../component-library/skeleton/index.ts | 6 + .../component-library/skeleton/skeleton.scss | 22 + .../skeleton/skeleton.stories.tsx | 134 + .../skeleton/skeleton.test.tsx | 22 + .../component-library/skeleton/skeleton.tsx | 44 + .../skeleton/skeleton.types.ts | 35 + .../account-details-display.js | 2 +- .../account-list-item-menu.js | 4 +- .../account-list-item.test.js.snap | 41 +- .../account-list-item/account-list-item.js | 32 +- .../account-list-item.stories.js | 2 +- .../account-list-item.test.js | 109 +- .../account-list-menu.test.tsx | 93 +- .../account-list-menu/account-list-menu.tsx | 44 +- .../account-list-menu/hidden-account-list.js | 12 +- .../account-overview-eth.test.tsx | 3 + .../account-overview-layout.tsx | 133 +- .../account-overview-non-evm.test.tsx | 3 + .../multichain/account-overview/constants.ts | 34 + .../activity-list-item.test.js.snap | 10 +- .../activity-list-item/activity-list-item.js | 4 +- .../__snapshots__/app-header.test.js.snap | 30 +- .../app-header-unlocked-content.tsx | 2 +- .../asset-balance/asset-balance-text.test.tsx | 155 +- .../asset-balance/asset-balance-text.tsx | 24 +- .../asset-picker-amount.tsx | 7 +- .../asset-picker-modal/Asset.test.tsx | 10 +- .../asset-picker-modal/Asset.tsx | 41 +- .../asset-picker-modal/AssetList.test.tsx | 17 + .../asset-picker-modal/AssetList.tsx | 76 +- .../asset-picker-modal-network.test.tsx.snap | 123 +- .../asset-picker-modal-network.test.tsx | 17 +- .../asset-picker-modal-network.tsx | 220 +- .../asset-picker-modal-nft-tab.tsx | 220 +- .../asset-picker-modal-search.tsx | 15 +- .../asset-picker-modal.test.tsx | 72 +- .../asset-picker-modal/asset-picker-modal.tsx | 245 +- .../asset-picker-modal/index.scss | 12 +- .../asset-picker-modal/types.ts | 27 +- .../__snapshots__/asset-picker.test.tsx.snap | 12 +- .../asset-picker/asset-picker.stories.tsx | 142 +- .../asset-picker/asset-picker.tsx | 173 +- .../asset-picker/index.scss | 5 - .../__snapshots__/avatar-group.test.tsx.snap | 2 +- .../avatar-group/avatar-group.stories.tsx | 26 + .../multichain/avatar-group/avatar-group.tsx | 35 +- .../avatar-group/avatar-group.types.tsx | 9 +- .../badge-status/badge-status.stories.tsx | 2 +- .../multichain/badge-status/badge-status.tsx | 2 +- .../multichain/carousel/carousel.stories.tsx | 66 + .../multichain/carousel/carousel.test.tsx | 264 + .../multichain/carousel/carousel.tsx | 213 + .../multichain/carousel/carousel.types.ts | 9 + .../multichain/carousel/constants.ts | 18 + ui/components/multichain/carousel/helpers.ts | 30 + ui/components/multichain/carousel/index.scss | 71 + ui/components/multichain/carousel/index.ts | 1 + .../connect-accounts-modal.test.tsx.snap | 2 +- .../connect-account-modal.types.ts | 2 +- .../connect-accounts-modal-list.test.tsx | 3 +- .../connect-accounts-modal.tsx | 3 +- .../connected-accounts-menu.test.tsx | 22 +- .../connected-accounts-menu.types.ts | 2 +- .../connected-site-menu.test.js.snap | 4 +- .../connected-site-menu.js | 4 +- .../connected-status/connected-status.tsx | 7 +- .../create-account/create-account.tsx | 2 +- .../create-eth-account/create-eth-account.js | 2 +- .../create-named-snap-account.tsx | 2 +- .../detected-token-banner.js | 19 +- .../edit-accounts-modal.tsx | 5 +- .../edit-networks-modal.js | 5 +- .../multichain/global-menu/global-menu.js | 2 +- .../import-nfts-modal/import-nfts-modal.js | 2 +- .../import-tokens-modal-confirm.js | 2 +- .../import-tokens-modal.js | 10 +- ui/components/multichain/index.js | 1 + .../menu-items/view-explorer-menu-item.tsx | 2 +- .../multichain/multichain-components.scss | 1 + .../network-list-item.test.js.snap | 23 +- .../multichain/network-list-item/index.scss | 10 +- .../network-list-item/network-list-item.tsx | 71 +- .../network-list-menu.test.js.snap | 106 +- .../network-list-menu.test.js | 4 +- .../network-list-menu/network-list-menu.tsx | 20 +- .../select-rpc-url-modal.test.tsx.snap | 2 +- ui/components/multichain/nft-item/index.scss | 36 +- .../nft-item/{index.js => index.ts} | 0 ui/components/multichain/nft-item/nft-item.js | 142 - .../multichain/nft-item/nft-item.tsx | 156 + ...tion-detail-block-explorer-button.test.tsx | 10 + .../notification-detail-button.test.tsx | 10 + .../notification-detail-button.tsx | 69 +- .../__snapshots__/connections.test.tsx.snap | 2 +- .../components/connections.types.tsx | 2 +- .../pages/connections/connections.test.tsx | 36 +- .../pages/connections/connections.tsx | 29 +- .../permissions-page.test.js.snap | 2 +- .../permissions-page/permissions-page.js | 27 +- .../permissions-page/permissions-page.test.js | 18 +- .../review-permission.types.tsx | 2 +- .../review-permissions-page.tsx | 22 +- .../site-cell-tooltip.test.js.snap | 4 +- .../site-cell/site-cell.test.tsx | 81 + .../site-cell/site-cell.tsx | 48 +- .../send/__snapshots__/send.test.js.snap | 2 +- .../network-picker.test.tsx.snap | 8 +- .../__snapshots__/your-accounts.test.tsx.snap | 14 +- .../send/components/account-picker.test.tsx | 18 +- .../quote-card/hooks/useEthFeeData.test.tsx | 4 +- .../quote-card/hooks/useEthFeeData.tsx | 4 +- .../hooks/useTranslatedNetworkName.test.tsx | 2 +- .../hooks/useTranslatedNetworkName.tsx | 2 +- .../send/components/recipient-content.tsx | 2 +- .../pages/send/components/your-accounts.tsx | 21 +- .../multichain/pages/send/send.test.js | 22 +- .../permission-details-modal.test.tsx | 27 +- ui/components/multichain/toast/toast.test.tsx | 2 +- .../token-list-item.test.tsx.snap | 414 +- .../multichain/token-list-item/index.scss | 4 - .../percentage-and-amount-change.test.tsx | 9 +- .../percentage-and-amount-change.tsx | 4 +- .../token-list-item.stories.js | 30 + .../token-list-item/token-list-item.test.tsx | 15 +- .../token-list-item/token-list-item.tsx | 263 +- ui/components/ui/box/README.mdx | 26 +- .../button-group-component.test.js.snap | 35 +- .../button-group-component.test.js | 16 +- .../ui/button-group/button-group.component.js | 7 +- ui/components/ui/button-group/index.scss | 2 +- ui/components/ui/menu/menu-item.js | 2 +- ui/components/ui/menu/menu.scss | 2 +- ui/components/ui/new-network-info/index.js | 1 - ui/components/ui/new-network-info/index.scss | 59 - .../ui/new-network-info/new-network-info.js | 269 - .../new-network-info.stories.js | 10 - .../new-network-info/new-network-info.test.js | 223 - .../numeric-input/numeric-input.component.js | 4 +- .../ui/origin-pill/origin-pill.test.tsx | 27 + ui/components/ui/origin-pill/origin-pill.tsx | 57 + ui/components/ui/ui-components.scss | 1 - .../ui/unit-input/unit-input.component.js | 2 +- ui/contexts/identity/index.test.tsx | 101 + ui/contexts/identity/index.tsx | 23 + .../metamask-notifications.tsx | 9 +- ui/css/design-system/_colors.scss | 1 + ui/ducks/app/app.test.js | 69 + ui/ducks/app/app.ts | 72 + ui/ducks/bridge-status/actions.ts | 31 + ui/ducks/bridge-status/selectors.ts | 68 + ui/ducks/bridge/actions.ts | 29 +- ui/ducks/bridge/bridge.test.ts | 70 +- ui/ducks/bridge/bridge.ts | 49 +- ui/ducks/bridge/selectors.test.ts | 1096 +- ui/ducks/bridge/selectors.ts | 463 +- ui/ducks/bridge/utils.ts | 122 +- ui/ducks/confirm-alerts/confirm-alerts.ts | 32 +- .../confirm-transaction.duck.test.js | 15 - ui/ducks/domains.js | 2 +- ui/ducks/metamask/metamask.js | 67 +- ui/ducks/metamask/metamask.test.js | 78 +- ui/ducks/ramps/ramps.test.ts | 10 +- ui/ducks/ramps/ramps.ts | 3 +- ui/ducks/send/helpers.js | 20 +- ui/ducks/send/helpers.test.js | 9 +- ui/ducks/send/send.js | 10 +- ui/ducks/send/send.test.js | 59 - ui/ducks/swaps/swaps.js | 28 +- ui/helpers/constants/design-system.ts | 8 + ui/helpers/constants/routes.ts | 5 + ui/helpers/constants/settings.js | 18 +- ui/helpers/utils/accounts.js | 12 +- ui/helpers/utils/accounts.test.js | 13 +- ui/helpers/utils/permission.js | 8 + ui/helpers/utils/permissions.test.ts | 7 +- ui/helpers/utils/permissions.ts | 3 +- ui/helpers/utils/settings-search.test.js | 2 +- ui/helpers/utils/snaps.js | 14 - ui/helpers/utils/snaps.ts | 24 + ui/helpers/utils/token-util.js | 38 +- ui/helpers/utils/transactions.util.js | 2 +- ui/helpers/utils/util.js | 8 +- .../useMultichainWalletSnapClient.test.ts | 16 +- .../accounts/useMultichainWalletSnapClient.ts | 2 +- .../useTokensWithFiltering.test.ts.snap | 59 + ui/hooks/bridge/events/types.ts | 47 + .../bridge/events/useConvertedUsdAmounts.ts | 76 + ui/hooks/bridge/events/useQuoteProperties.ts | 25 + .../events/useRequestMetadataProperties.ts | 38 + .../bridge/events/useRequestProperties.ts | 54 + ui/hooks/bridge/events/useTradeProperties.ts | 24 + ui/hooks/bridge/useBridgeChainInfo.ts | 112 + ui/hooks/bridge/useBridgeExchangeRates.ts | 97 + ui/hooks/bridge/useBridgeTxHistoryData.ts | 65 + ui/hooks/bridge/useBridging.test.ts | 17 +- ui/hooks/bridge/useBridging.ts | 44 +- ui/hooks/bridge/useCountdownTimer.test.ts | 18 +- ui/hooks/bridge/useCountdownTimer.ts | 15 +- .../bridge/useCrossChainSwapsEventTracker.ts | 114 + ui/hooks/bridge/useIsTxSubmittable.ts | 49 + ui/hooks/bridge/useLatestBalance.test.ts | 16 +- ui/hooks/bridge/useLatestBalance.ts | 25 +- ui/hooks/bridge/useQuoteFetchEvents.ts | 151 + .../bridge/useTokensWithFiltering.test.ts | 59 + ui/hooks/bridge/useTokensWithFiltering.ts | 206 + ui/hooks/identity/useAuthentication/index.ts | 2 + .../useAuthentication/useAutoSignIn.test.tsx | 101 + .../useAuthentication/useAutoSignIn.ts | 51 + .../useAuthentication/useSignIn.test.tsx | 121 + .../identity/useAuthentication/useSignIn.ts | 54 + .../useCreateSession.test.ts | 0 .../useCreateSession.ts | 22 +- .../useProfileSyncing/accountSyncing.test.tsx | 179 + .../useProfileSyncing/accountSyncing.ts | 93 + .../useProfileSyncing/index.ts | 3 +- .../useProfileSyncing/profileSyncing.test.tsx | 71 + .../useProfileSyncing/profileSyncing.ts | 53 +- .../useCounter.test.tsx | 11 +- .../metamask-notifications/useCounter.tsx | 18 +- .../useNotifications.test.tsx | 7 +- .../useNotifications.ts | 9 +- .../useProfileSyncing/accountSyncing.test.tsx | 70 - .../useProfileSyncing/accountSyncing.ts | 66 - .../useProfileSyncing/profileSyncing.test.tsx | 140 - .../useSwitchNotifications.test.tsx | 256 +- .../useSwitchNotifications.ts | 59 +- ui/hooks/ramps/useRamps/useRamps.test.tsx | 14 + ui/hooks/ramps/useRamps/useRamps.ts | 36 +- ui/hooks/snaps/useSnapSettings.ts | 55 + ...eAccountTotalCrossChainFiatBalance.test.ts | 9 +- .../useAccountTotalCrossChainFiatBalance.ts | 4 +- ui/hooks/useAccountTotalFiatBalance.js | 4 +- ui/hooks/useCurrencyDisplay.js | 1 + ui/hooks/useCurrentAsset.js | 2 +- ui/hooks/useEthFiatAmount.js | 7 +- ui/hooks/useFiatFormatter.test.ts | 4 +- ui/hooks/useFiatFormatter.ts | 2 +- ui/hooks/useGetAssetImageUrl.test.ts | 2 +- ui/hooks/useGetAssetImageUrl.ts | 5 +- .../useGetFormattedTokensPerChain.test.ts | 8 +- ui/hooks/useGetFormattedTokensPerChain.ts | 3 +- .../useMetametrics.test.tsx | 20 +- .../useMetametrics.ts | 24 +- ...MultichainAccountTotalFiatBalance.test.tsx | 3 +- .../useMultichainAccountTotalFiatBalance.ts | 3 +- ui/hooks/useMultichainBalances.test.ts | 121 + ui/hooks/useMultichainBalances.ts | 154 + ui/hooks/useMultichainSelector.test.ts | 2 +- ui/hooks/useMultichainSelector.ts | 2 +- ui/hooks/useNfts.ts | 71 + ui/hooks/useNftsCollections.js | 3 +- ui/hooks/useNotificationTimeouts.ts | 25 + ui/hooks/useSwappedTokenValue.js | 2 +- ui/hooks/useTokenFiatAmount.js | 29 +- ui/hooks/useTokensToSearch.js | 10 +- ui/hooks/useTokensWithFiltering.test.ts | 153 - ui/hooks/useTokensWithFiltering.ts | 194 - ui/hooks/useTransactionDisplayData.js | 35 +- ui/hooks/useTransactionInsights.js | 2 +- ui/index.js | 8 +- ui/pages/asset/asset.tsx | 4 +- .../__snapshots__/asset-page.test.tsx.snap | 322 +- ui/pages/asset/components/asset-page.test.tsx | 28 +- ui/pages/asset/components/asset-page.tsx | 55 +- ui/pages/asset/components/token-asset.tsx | 1 - ui/pages/asset/components/token-buttons.tsx | 30 +- .../bridge/__snapshots__/index.test.tsx.snap | 207 +- .../awaiting-signatures-cancel-button.tsx | 25 + .../awaiting-signatures.tsx | 154 + .../bridge/awaiting-signatures/index.scss | 31 + ui/pages/bridge/awaiting-signatures/index.ts | 1 + ui/pages/bridge/hooks/useAddToken.ts | 12 +- .../bridge/hooks/useBridgeTokenDisplayData.ts | 39 + ui/pages/bridge/hooks/useHandleApprovalTx.ts | 37 +- ui/pages/bridge/hooks/useHandleBridgeTx.ts | 37 +- ui/pages/bridge/hooks/useHandleTx.ts | 45 +- .../hooks/useSubmitBridgeTransaction.test.tsx | 86 +- .../hooks/useSubmitBridgeTransaction.ts | 150 +- ui/pages/bridge/index.scss | 62 +- ui/pages/bridge/index.test.tsx | 25 +- ui/pages/bridge/index.tsx | 152 +- ui/pages/bridge/layout/tooltip.tsx | 95 +- .../bridge-cta-button.test.tsx.snap | 48 +- .../prepare-bridge-page.test.tsx.snap | 597 +- .../bridge/prepare/bridge-cta-button.test.tsx | 176 +- ui/pages/bridge/prepare/bridge-cta-button.tsx | 194 +- .../bridge/prepare/bridge-input-group.tsx | 329 +- ...dge-transaction-settings-modal.stories.tsx | 51 + .../bridge-transaction-settings-modal.tsx | 243 + .../prepare/bridge-tx-declined-message.tsx | 37 + .../components/bridge-asset-picker-button.tsx | 93 + ui/pages/bridge/prepare/index.scss | 202 +- .../prepare/prepare-bridge-page.stories.tsx | 251 + .../prepare/prepare-bridge-page.test.tsx | 124 +- .../bridge/prepare/prepare-bridge-page.tsx | 583 +- .../bridge-quote-card.test.tsx.snap | 440 +- .../bridge-quotes-modal.test.tsx.snap | 24 +- .../bridge/quotes/bridge-quote-card.test.tsx | 79 +- ui/pages/bridge/quotes/bridge-quote-card.tsx | 366 +- .../quotes/bridge-quotes-modal.stories.tsx | 2 +- .../quotes/bridge-quotes-modal.test.tsx | 35 +- .../bridge/quotes/bridge-quotes-modal.tsx | 249 +- ui/pages/bridge/quotes/index.scss | 67 +- ui/pages/bridge/quotes/quote-info-row.tsx | 51 - .../bridge-activity-item-tx-segments.tsx | 83 + .../bridge-explorer-links.tsx | 118 + .../bridge-step-description.tsx | 202 + .../bridge-step-list.stories.tsx | 1781 +++ .../transaction-details/bridge-step-list.tsx | 99 + .../transaction-details/hollow-circle.tsx | 39 + .../bridge/transaction-details/index.scss | 54 + .../transaction-details/pulsing-circle.tsx | 51 + .../transaction-details/segment.stories.tsx | 46 + .../bridge/transaction-details/segment.tsx | 32 + .../step-progress-bar-item.tsx | 82 + .../transaction-detail-row.tsx | 44 + .../transaction-details.test.tsx | 50 + .../transaction-details.tsx | 474 + ui/pages/bridge/types.ts | 146 - ui/pages/bridge/utils/quote.test.ts | 109 +- ui/pages/bridge/utils/quote.ts | 173 +- .../confirm-add-suggested-nft.test.js.snap | 4 +- .../confirm-add-suggested-nft.js | 5 +- .../confirm-add-suggested-token.js | 3 + .../confirm-decrypt-message.component.js | 32 +- .../confirm-decrypt-message.component.test.js | 10 +- .../confirm-decrypt-message.scss | 1 + ...confirm-encryption-public-key.component.js | 26 +- ...confirm-encryption-public-key.container.js | 10 +- .../advanced-gas-fee-gas-limit.js | 2 +- .../advanced-gas-fee-gas-limit.test.js | 4 +- .../confirm-gas-display.test.js | 1 + .../confirm-detail-row.component.test.js.snap | 4 +- ...ge-container-header.component.test.js.snap | 8 +- ...irm-page-container-navigation.component.js | 77 +- .../blockaid-loading-indicator.test.tsx | 1 - .../components/confirm/footer/footer.test.tsx | 57 +- .../components/confirm/footer/footer.tsx | 21 +- .../header/__snapshots__/header.test.tsx.snap | 4 +- .../components/confirm/header/header-info.tsx | 17 +- .../info/__snapshots__/info.test.tsx.snap | 624 +- .../__snapshots__/approve.test.tsx.snap | 142 +- .../approve-details.test.tsx.snap | 336 +- .../approve-details/approve-details.test.tsx | 22 +- .../approve-details/approve-details.tsx | 22 +- .../approve-static-simulation.tsx | 4 +- .../confirm/info/approve/approve.test.tsx | 23 +- .../confirm/info/approve/approve.tsx | 4 +- .../edit-spending-cap-modal.tsx | 2 +- .../use-approve-token-simulation.test.ts | 173 +- .../hooks/use-approve-token-simulation.ts | 43 +- .../__snapshots__/spending-cap.test.tsx.snap | 14 +- .../approve/spending-cap/spending-cap.tsx | 5 +- .../base-transaction-info.test.tsx.snap | 104 +- .../base-transaction-info.tsx | 14 +- .../info/hooks/use-token-values.test.ts | 98 +- .../confirm/info/hooks/use-token-values.ts | 70 +- .../hooks/useDecodedTransactionData.test.ts | 26 + .../info/hooks/useDecodedTransactionData.ts | 12 +- .../info/hooks/useFeeCalculations.test.ts | 31 + .../confirm/info/hooks/useFeeCalculations.ts | 9 +- .../info/hooks/useSendingValueMetric.test.ts | 104 + .../info/hooks/useSendingValueMetric.ts | 26 + .../hooks/useTokenTransactionData.test.ts | 94 + .../info/hooks/useTokenTransactionData.ts | 14 + .../info/hooks/useTransferRecipient.test.ts | 74 + .../info/hooks/useTransferRecipient.ts | 20 + .../components/confirm/info/info.test.tsx | 17 + .../native-transfer.test.tsx.snap | 261 +- .../info/native-transfer/native-transfer.tsx | 17 +- .../nft-token-transfer.test.tsx.snap | 303 +- .../nft-token-transfer/nft-token-transfer.tsx | 17 +- .../__snapshots__/personal-sign.test.tsx.snap | 158 +- .../info/personal-sign/personal-sign.test.tsx | 35 +- .../info/personal-sign/personal-sign.tsx | 15 +- .../__snapshots__/siwe-sign.test.tsx.snap | 8 +- .../set-approval-for-all-info.test.tsx.snap | 117 +- .../set-approval-for-all-info.test.tsx | 5 - .../set-approval-for-all-info.tsx | 17 +- .../advanced-details.test.tsx.snap | 22 + .../advanced-details/advanced-details.tsx | 6 +- .../confirm/info/shared/constants.ts | 5 + .../edit-gas-fees-row.test.tsx.snap | 26 +- .../edit-gas-fees-row/edit-gas-fees-row.tsx | 8 +- .../gas-fees-details.test.tsx.snap | 24 +- .../gas-fees-section.test.tsx.snap | 24 +- .../native-send-heading.tsx | 20 +- .../nft-send-heading.test.tsx.snap | 44 +- .../__snapshots__/send-heading.test.tsx.snap | 58 +- .../shared/send-heading/send-heading.test.tsx | 6 + .../info/shared/send-heading/send-heading.tsx | 8 +- .../sign-in-with-row.test.tsx | 23 +- .../sign-in-with-row/sign-in-with-row.tsx | 5 +- .../static-simulation/static-simulation.tsx | 22 +- .../transaction-data.test.tsx.snap | 89 +- .../transaction-data/transaction-data.tsx | 2 +- .../transaction-details.test.tsx.snap | 80 +- .../transaction-details.test.tsx | 116 +- .../transaction-details.tsx | 15 +- .../token-details-section.test.tsx.snap | 58 +- .../token-transfer.test.tsx.snap | 317 +- .../transaction-flow-section.test.tsx.snap | 12 +- .../token-transfer/token-details-section.tsx | 38 +- .../token-transfer/token-transfer.test.tsx | 8 +- .../info/token-transfer/token-transfer.tsx | 17 +- .../transaction-flow-section.test.tsx | 45 +- .../transaction-flow-section.tsx | 28 +- .../__snapshots__/typed-sign-v1.test.tsx.snap | 81 +- .../info/typed-sign-v1/typed-sign-v1.test.tsx | 11 +- .../info/typed-sign-v1/typed-sign-v1.tsx | 2 +- .../__snapshots__/typed-sign.test.tsx.snap | 578 +- .../permit-simulation.test.tsx.snap | 345 - .../decoded-simulation.test.tsx.snap | 115 - .../decoded-simulation.test.tsx | 98 - .../decoded-simulation/decoded-simulation.tsx | 125 - .../default-simulation/index.ts | 1 - .../permit-simulation.test.tsx | 91 - .../permit-simulation/permit-simulation.tsx | 22 - .../decoded-simulation.test.tsx | 291 + .../decoded-simulation/decoded-simulation.tsx | 203 + .../decoded-simulation/index.ts | 0 .../typed-sign-v4-simulation/index.ts | 1 + .../native-value-display.test.tsx | 0 .../native-value-display.tsx | 4 +- .../permit-simulation.test.tsx.snap} | 4 +- .../permit-simulation/index.ts | 0 .../permit-simulation.test.tsx} | 8 +- .../permit-simulation/permit-simulation.tsx} | 29 +- .../typed-sign-v4-simulation.test.tsx | 174 + .../typed-sign-v4-simulation.tsx | 33 + .../__snapshots__/value-display.test.tsx.snap | 0 .../value-display/value-display.test.tsx | 26 +- .../value-display/value-display.tsx | 95 +- .../info/typed-sign/typed-sign.test.tsx | 15 +- .../confirm/info/typed-sign/typed-sign.tsx | 19 +- .../components/confirm/info/utils.test.ts | 149 +- .../components/confirm/info/utils.ts | 109 +- .../nav/__snapshots__/nav.test.tsx.snap | 5 +- .../components/confirm/nav/index.tsx | 2 +- .../components/confirm/nav/nav.stories.tsx | 116 +- .../components/confirm/nav/nav.test.tsx | 11 +- .../components/confirm/nav/nav.tsx | 94 +- .../network-change-toast-legacy.test.tsx | 1 - .../row/__snapshots__/dataTree.test.tsx.snap | 53 +- .../components/confirm/row/dataTree.tsx | 5 + .../typedSignDataV1.test.tsx.snap | 2 + .../__snapshots__/typedSignData.test.tsx.snap | 44 +- .../__snapshots__/snaps-section.test.tsx.snap | 16 +- .../snaps/snaps-section/snap-insight.tsx | 3 +- .../title/hooks/useCurrentSpendingCap.ts | 2 +- .../components/confirm/title/title.test.tsx | 2 +- .../components/confirm/title/title.tsx | 49 +- .../confirmations/components/confirm/utils.ts | 14 + .../edit-gas-display.component.js | 2 +- .../gas-timing/gas-timing.component.js | 16 +- .../signature-request-header.js | 2 +- .../signature-request-original.test.js.snap | 4 +- .../signature-request-siwe.test.js.snap | 4 +- .../signature-request-siwe.test.js | 4 + .../signature-request.test.js.snap | 8 +- ...ture-request-header.component.test.js.snap | 16 +- .../simulation-details/simulation-details.tsx | 80 +- .../useBalanceChanges.test.ts | 6 +- .../simulation-details/useBalanceChanges.ts | 6 +- .../smart-transactions-banner-alert/index.ts | 1 + .../smart-transactions-banner-alert.test.tsx | 258 + .../smart-transactions-banner-alert.tsx | 127 + .../SnapAccountSuccessMessage.tsx | 2 +- .../transaction-alerts/transaction-alerts.js | 2 + .../transaction-alerts.test.js | 73 +- .../confirm-approve/confirm-approve.js | 8 +- .../confirm-send-ether.test.js.snap | 18 +- .../confirm-send-ether.test.js | 5 +- .../confirm-send-token/confirm-send-token.js | 2 +- .../__snapshots__/index.test.js.snap | 4 +- .../confirm-token-transaction-base.js | 2 +- .../confirm-transaction-base.test.js.snap | 4 +- .../confirm-transaction-base.component.js | 12 +- .../confirm-transaction-base.container.js | 3 + .../confirm-transaction-base.test.js | 154 +- .../confirm-transaction.transaction.test.js | 2 +- .../__snapshots__/confirm.test.tsx.snap | 1747 +-- .../confirmations/confirm/confirm.test.tsx | 72 +- ui/pages/confirmations/confirm/confirm.tsx | 6 +- .../confirmation/confirmation.js | 290 +- .../stories/snap-dialog.stories.tsx | 118 + .../confirmation/stories/util.js | 10 +- .../add-ethereum-chain.test.js.snap | 22 +- .../create-named-snap-account.test.js.snap | 57 +- .../create-snap-account.test.js.snap | 2 +- .../__snapshots__/error.test.js.snap | 2 +- .../remove-snap-account.test.js.snap | 4 +- .../snap-account-redirect.test.js.snap | 2 +- .../__snapshots__/success.test.js.snap | 2 +- .../switch-ethereum-chain.test.js.snap | 12 +- .../templates/add-ethereum-chain.js | 8 + .../templates/add-ethereum-chain.test.js | 1 + .../templates/remove-snap-account.test.js | 3 + .../PendingTransactionAlertMessage.tsx | 32 + .../useFirstTimeInteractionAlert.test.ts | 120 +- .../useFirstTimeInteractionAlert.ts | 16 +- .../usePendingTransactionAlerts.test.ts | 17 +- .../usePendingTransactionAlerts.ts | 12 +- .../transactions/useResimulationAlert.test.ts | 1 + .../useSigningOrSubmittingAlerts.test.ts | 6 +- .../useSigningOrSubmittingAlerts.ts | 4 +- .../hooks/syncConfirmPath.test.ts | 18 +- .../confirmations/hooks/syncConfirmPath.ts | 19 +- ui/pages/confirmations/hooks/test-utils.js | 2 +- .../hooks/useConfirmationNavigation.test.ts | 296 + .../hooks/useConfirmationNavigation.ts | 146 + .../hooks/useCurrentConfirmation.test.ts | 59 +- .../hooks/useCurrentConfirmation.ts | 12 - .../hooks/useDecodedSignatureMetrics.test.ts | 161 + .../hooks/useDecodedSignatureMetrics.ts | 56 + .../confirmations/hooks/useGasEstimates.js | 1 + .../hooks/useGasEstimates.test.js | 9 +- .../confirmations/hooks/useGasFeeErrors.js | 2 +- .../confirmations/hooks/useGasFeeInputs.js | 16 +- .../hooks/useGasFeeInputs.test.js | 1 + .../hooks/useGetTokenStandardAndDetails.ts | 17 +- .../useSmartTransactionFeatureFlags.test.ts | 37 + .../hooks/useSmartTransactionFeatureFlags.ts | 6 + ...rackERC20WithoutDecimalInformation.test.ts | 4 +- .../useTrackERC20WithoutDecimalInformation.ts | 51 +- .../hooks/useTransactionFunction.test.js | 12 + .../hooks/useTransactionFunctions.js | 3 +- .../useTypesSignSimulationEnabledInfo.test.ts | 71 + .../useTypesSignSimulationEnabledInfo.ts | 65 + ui/pages/confirmations/send/send.constants.js | 2 +- .../token-allowance.test.js.snap | 4 +- .../token-allowance/token-allowance.test.js | 10 +- ui/pages/confirmations/utils/confirm.test.ts | 9 +- ui/pages/confirmations/utils/confirm.ts | 7 +- .../connected-sites.container.js | 12 +- .../create-account/connect-hardware/index.js | 2 +- .../connect-hardware/index.test.tsx | 1 + ui/pages/home/home.component.js | 61 +- ui/pages/home/home.container.js | 36 +- ui/pages/index.js | 9 +- ui/pages/institutional/custody/custody.tsx | 6 +- .../notification-details-body.tsx | 16 +- .../notification-details-footer.tsx | 20 +- .../notification-details.tsx | 16 +- ...fications-settings-allow-notifications.tsx | 4 +- .../erc1155-sent-received.tsx | 9 +- .../erc20-sent-received.tsx | 9 +- .../erc721-sent-received.tsx | 9 +- .../eth-sent-received/eth-sent-received.tsx | 9 +- .../feature-announcement.tsx | 9 +- .../notification-components/index.ts | 7 +- .../lido-stake-ready-to-be-withdrawn.tsx | 9 +- .../lido-withdrawal-requested.tsx | 9 +- .../notification-components/node-guard.ts | 4 +- .../notification-components/snap/snap.tsx | 210 +- .../notification-components/stake/stake.tsx | 9 +- .../swap-completed/swap-completed.tsx | 9 +- .../types/notifications/notifications.ts | 39 +- .../notifications-list-item.stories.tsx | 55 +- .../notifications/notifications-list-item.tsx | 18 +- ...otifications-list-read-all-button.test.tsx | 2 +- .../notifications-list-read-all-button.tsx | 25 +- .../notifications/notifications-list.test.tsx | 3 +- ui/pages/notifications/notifications-list.tsx | 11 +- ui/pages/notifications/notifications.test.tsx | 2 - ui/pages/notifications/notifications.tsx | 68 +- ui/pages/notifications/snap/types/types.ts | 17 - ui/pages/notifications/snap/utils/utils.ts | 17 - .../creation-successful.js | 4 +- .../metametrics/metametrics.js | 30 +- .../pin-extension/pin-extension.js | 12 +- .../pin-extension/pin-extension.test.js | 113 +- .../privacy-settings/privacy-settings.js | 6 +- .../__snapshots__/connect-page.test.tsx.snap | 4 +- .../connect-page/connect-page.tsx | 17 +- .../permissions-connect.component.js | 42 +- .../snaps/snap-install/snap-install.js | 2 +- .../snaps/snaps-connect/snaps-connect.js | 2 +- .../remove-snap-account/snap-account-card.tsx | 13 +- ui/pages/routes/routes.component.js | 53 +- ui/pages/routes/routes.component.test.js | 120 +- ui/pages/routes/routes.container.js | 18 +- .../advanced-tab.component.test.js.snap | 10 +- .../developer-options-tab.test.tsx.snap | 2 +- .../profile-sync.test.tsx | 58 + .../developer-options-tab/profile-sync.tsx | 63 +- .../experimental-tab.component.tsx | 60 - .../experimental-tab.container.ts | 14 - .../experimental-tab/experimental-tab.test.js | 20 +- ui/pages/settings/index.scss | 3 +- .../settings/info-tab/info-tab.component.js | 12 + .../settings/info-tab/info-tab.stories.js | 2 +- ui/pages/settings/info-tab/info-tab.test.tsx | 4 +- .../__snapshots__/security-tab.test.js.snap | 135 +- .../delete-metametrics-data-button.test.tsx | 11 +- .../delete-metametrics-data-button.tsx | 7 +- .../metametrics-toggle.test.tsx | 2 +- .../metametrics-toggle/metametrics-toggle.tsx | 8 +- .../profile-sync-toggle.test.tsx | 2 +- .../profile-sync-toggle.tsx | 4 +- .../security-tab/security-tab.component.js | 84 +- ui/pages/settings/settings.component.js | 46 +- ui/pages/settings/settings.container.js | 23 + ui/pages/settings/settings.stories.js | 2 + ui/pages/settings/settings.test.js | 1 + .../smart-transaction-status-page.tsx | 3 +- ...nap-account-transaction-loading-screen.tsx | 2 +- ui/pages/snaps/snap-view/snap-home.js | 20 - ui/pages/snaps/snap-view/snap-settings.js | 2 +- ui/pages/snaps/snap-view/snap-view.js | 4 +- ui/pages/swaps/awaiting-swap/awaiting-swap.js | 5 +- .../swaps/hooks/useUpdateSwapsState.test.ts | 2 +- ui/pages/swaps/hooks/useUpdateSwapsState.ts | 2 +- ui/pages/swaps/index.js | 2 +- ui/pages/swaps/index.test.js | 4 +- .../list-with-search/list-with-search.js | 2 +- .../mascot-background-animation.js | 12 +- ui/pages/swaps/prepare-swap-page/index.scss | 4 +- .../prepare-swap-page/prepare-swap-page.js | 54 +- .../prepare-swap-page.test.js | 56 + .../swaps/prepare-swap-page/review-quote.js | 9 +- .../item-list/item-list.component.js | 2 +- .../list-item-search.component.js | 2 +- .../smart-transaction-status.js | 2 +- .../transaction-settings.test.js.snap | 8 +- .../swaps/transaction-settings/index.scss | 4 +- .../transaction-settings.js | 171 +- ui/selectors/accounts.test.ts | 6 +- ui/selectors/accounts.ts | 7 +- ui/selectors/approvals.ts | 48 +- ui/selectors/confirm-transaction.js | 19 +- ui/selectors/identity/authentication.test.ts | 27 + .../authentication.ts | 18 +- .../profile-syncing.test.ts | 1 + .../profile-syncing.ts | 0 ui/selectors/institutional/selectors.ts | 5 +- .../authentication.test.ts | 26 - .../metamask-notifications.ts | 42 +- ui/selectors/multichain.test.ts | 31 +- ui/selectors/multichain.ts | 58 +- ui/selectors/nft.ts | 5 +- ui/selectors/permissions.js | 65 +- ui/selectors/permissions.test.js | 202 +- ui/selectors/selectors.js | 513 +- ui/selectors/selectors.test.js | 162 +- ui/selectors/selectors.types.ts | 2 +- ui/selectors/transactions.js | 6 +- ui/store/actionConstants.ts | 1 + ui/store/actions.test.js | 80 +- ui/store/actions.ts | 269 +- ui/store/store.ts | 2 +- yarn.lock | 2451 ++-- 1487 files changed, 81372 insertions(+), 67036 deletions(-) delete mode 100755 .circleci/scripts/check-working-tree.sh rename .circleci/scripts/{git-diff-develop.ts => git-diff-default-branch.ts} (89%) delete mode 100755 .circleci/scripts/trigger-beta-build.sh delete mode 100755 .circleci/scripts/validate-source-maps-beta.sh create mode 100644 .github/workflows/build-beta.yml create mode 100644 .github/workflows/publish-prerelease.yml create mode 100644 .github/workflows/runway.yml create mode 100644 .github/workflows/test-deps-audit.yml create mode 100644 .github/workflows/test-deps-depcheck.yml create mode 100644 .github/workflows/test-lint-changelog.yml create mode 100644 .github/workflows/test-lint-lockfile.yml create mode 100644 .github/workflows/test-lint-shellcheck.yml create mode 100644 .github/workflows/test-lint.yml create mode 100644 .github/workflows/test-yarn-dedupe.yml create mode 100644 .github/workflows/validate-lavamoat-allow-scripts.yml create mode 100644 .github/workflows/validate-lavamoat-policy-build.yml create mode 100644 .github/workflows/validate-lavamoat-policy-webapp.yml create mode 100644 .github/workflows/wait-for-circleci-workflow-status.yml create mode 100644 .storybook/index.css create mode 100644 .yarn/patches/@json-schema-spec-json-pointer-npm-0.1.2-3d06119887.patch create mode 100644 .yarn/patches/@json-schema-tools-reference-resolver-npm-1.2.6-4e1497c16d.patch create mode 100644 .yarn/patches/@metamask-assets-controllers-patch-d114308c1b.patch create mode 100644 .yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch delete mode 100644 .yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch delete mode 100644 .yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch create mode 100644 .yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch delete mode 100644 .yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch delete mode 100644 .yarn/patches/@metamask-ppom-validator-npm-0.32.0-f677deea54.patch delete mode 100644 .yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch create mode 100644 .yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch delete mode 100644 .yarn/patches/luxon-npm-3.2.1-56f8d97395.patch create mode 100644 app/images/b3.svg create mode 100644 app/images/hollow-circle.svg delete mode 100644 app/images/info-fox.svg create mode 100644 app/images/ink-sepolia.svg create mode 100644 app/images/ink.svg create mode 100644 app/images/kaia.svg delete mode 100644 app/images/klaytn.svg create mode 100644 app/images/lisk.svg create mode 100644 app/images/lisk_sepolia.svg create mode 100644 app/images/slide-bridge-icon.svg create mode 100644 app/images/slide-card-icon.svg create mode 100644 app/images/slide-fund-icon.svg create mode 100644 app/images/slide-sell-icon.svg create mode 100644 app/images/soneium.svg create mode 100644 app/scripts/controllers/bridge-status/mocks.ts delete mode 100644 app/scripts/controllers/permissions/caveat-mutators.js delete mode 100644 app/scripts/controllers/permissions/caveat-mutators.test.js create mode 100644 app/scripts/lib/approval/utils.test.ts create mode 100644 app/scripts/lib/approval/utils.ts create mode 100644 app/scripts/lib/createMainFrameOriginMiddleware.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.test.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/request-accounts.test.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.test.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.test.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.test.ts create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.ts create mode 100644 app/scripts/lib/transaction/smart-transactions-mocks.ts create mode 100644 app/scripts/migrations/131.1.test.ts create mode 100644 app/scripts/migrations/131.1.ts create mode 100644 app/scripts/migrations/133.1.test.ts create mode 100644 app/scripts/migrations/133.1.ts create mode 100644 app/scripts/migrations/133.2.test.ts create mode 100644 app/scripts/migrations/133.2.ts create mode 100644 app/scripts/migrations/133.test.ts create mode 100644 app/scripts/migrations/133.ts create mode 100644 app/scripts/migrations/134.test.ts create mode 100644 app/scripts/migrations/134.ts create mode 100644 app/scripts/migrations/135.test.ts create mode 100644 app/scripts/migrations/135.ts create mode 100644 app/scripts/migrations/136.test.ts create mode 100644 app/scripts/migrations/136.ts create mode 100644 app/scripts/migrations/137.test.ts create mode 100644 app/scripts/migrations/137.ts create mode 100644 app/scripts/migrations/138.test.ts create mode 100644 app/scripts/migrations/138.ts create mode 100644 app/scripts/migrations/139.test.ts create mode 100644 app/scripts/migrations/139.ts create mode 100644 app/scripts/migrations/140.test.ts create mode 100644 app/scripts/migrations/140.ts create mode 100644 app/scripts/migrations/141.test.ts create mode 100644 app/scripts/migrations/141.ts delete mode 100644 development/charts/flamegraph/chart/index.html delete mode 100644 development/charts/flamegraph/lib/d3-flamegraph-tooltip.js delete mode 100644 development/charts/flamegraph/lib/d3-flamegraph.css delete mode 100644 development/charts/flamegraph/lib/d3-flamegraph.js delete mode 100644 development/charts/table/index.html delete mode 100644 development/charts/table/jquery.min.js create mode 100644 development/lib/get-manifest-flag.ts create mode 100644 docs/lavamoat-policy-review-process.md create mode 100644 shared/constants/multichain/accounts.ts create mode 100644 shared/lib/accounts/snaps.ts delete mode 100644 shared/lib/index.d.ts rename {ui/pages/bridge => shared/modules/bridge-utils}/bridge.util.test.ts (76%) rename {ui/pages/bridge => shared/modules/bridge-utils}/bridge.util.ts (74%) create mode 100644 shared/modules/bridge-utils/quote.ts rename {ui/pages/bridge/utils => shared/modules/bridge-utils}/validators.ts (80%) create mode 100644 shared/types/bridge.ts create mode 100644 test/data/bridge/mock-token-data.ts create mode 100644 test/e2e/flask/solana/check-balance.spec.ts create mode 100644 test/e2e/flask/solana/common-solana.ts create mode 100644 test/e2e/flask/solana/create-solana-account.spec.ts create mode 100644 test/e2e/flask/solana/send-flow.spec.ts create mode 100644 test/e2e/flask/solana/solana-eth-networks.spec.ts create mode 100644 test/e2e/flask/solana/switching-network-accounts.spec.ts rename test/e2e/helpers/{ => identity}/user-storage/userStorageMockttpController.test.ts (100%) rename test/e2e/helpers/{ => identity}/user-storage/userStorageMockttpController.ts (100%) delete mode 100644 test/e2e/json-rpc/wallet_requestPermissions.spec.js create mode 100644 test/e2e/json-rpc/wallet_requestPermissions.spec.ts delete mode 100644 test/e2e/json-rpc/wallet_revokePermissions.spec.js create mode 100644 test/e2e/json-rpc/wallet_revokePermissions.spec.ts delete mode 100644 test/e2e/mv3-perf-stats/index.js delete mode 100755 test/e2e/mv3-perf-stats/init-load-stats.js delete mode 100755 test/e2e/mv3-stats.js create mode 100644 test/e2e/page-objects/flows/watch-account.flow.ts create mode 100644 test/e2e/page-objects/pages/confirmations/redesign/accountDetailsModal.ts create mode 100644 test/e2e/page-objects/pages/confirmations/redesign/add-token-confirmations.ts create mode 100644 test/e2e/page-objects/pages/confirmations/redesign/permit-confirmation.ts create mode 100644 test/e2e/page-objects/pages/confirmations/redesign/personal-sign-confirmation.ts create mode 100644 test/e2e/page-objects/pages/confirmations/redesign/sign-typed-data-confirmation.ts create mode 100644 test/e2e/page-objects/pages/dialog/account-details-modal.ts create mode 100644 test/e2e/page-objects/pages/dialog/add-tokens.ts create mode 100644 test/e2e/page-objects/pages/dialog/confirm-alert.ts create mode 100644 test/e2e/page-objects/pages/dialog/create-contract.ts create mode 100644 test/e2e/page-objects/pages/hardware-wallet/connect-hardware-wallet-page.ts create mode 100644 test/e2e/page-objects/pages/hardware-wallet/select-trezor-account-page.ts create mode 100644 test/e2e/page-objects/pages/home/activity-list.ts create mode 100644 test/e2e/page-objects/pages/home/asset-list.ts create mode 100644 test/e2e/page-objects/pages/home/bitcoin-homepage.ts create mode 100644 test/e2e/page-objects/pages/home/homepage.ts create mode 100644 test/e2e/page-objects/pages/home/nft-list.ts create mode 100644 test/e2e/page-objects/pages/home/non-evm-homepage.ts delete mode 100644 test/e2e/page-objects/pages/homepage.ts create mode 100644 test/e2e/page-objects/pages/permission/permission-list-page.ts create mode 100644 test/e2e/page-objects/pages/permission/site-permission-page.ts create mode 100644 test/e2e/page-objects/pages/send/bitcoin-review-tx-page.ts create mode 100644 test/e2e/page-objects/pages/send/bitcoin-send-page.ts create mode 100644 test/e2e/page-objects/pages/send/solana-confirm-tx-page.ts create mode 100644 test/e2e/page-objects/pages/send/solana-send-page.ts create mode 100644 test/e2e/page-objects/pages/send/solana-tx-result-page.ts create mode 100644 test/e2e/page-objects/pages/settings/advanced-settings.ts create mode 100644 test/e2e/page-objects/pages/settings/general-settings.ts create mode 100644 test/e2e/page-objects/pages/test-snaps.ts create mode 100644 test/e2e/page-objects/pages/token-overview-page.ts delete mode 100644 test/e2e/snaps/test-snap-txinsights-v2.spec.js create mode 100644 test/e2e/tests/carousel/carousel.spec.ts create mode 100644 test/e2e/tests/confirmations/transactions/transaction-decoding-redesign.spec.ts delete mode 100644 test/e2e/tests/connections/connect-with-metamask.spec.js delete mode 100644 test/e2e/tests/connections/edit-account-flow.spec.js create mode 100644 test/e2e/tests/connections/edit-account-permissions.spec.ts delete mode 100644 test/e2e/tests/connections/edit-networks-flow.spec.js create mode 100644 test/e2e/tests/connections/edit-networks-permissions.spec.ts delete mode 100644 test/e2e/tests/dapp-interactions/contract-interactions.spec.js delete mode 100644 test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js delete mode 100644 test/e2e/tests/dapp-interactions/failing-contract.spec.js delete mode 100644 test/e2e/tests/hardware-wallets/trezor-account.spec.js create mode 100644 test/e2e/tests/hardware-wallets/trezor-account.spec.ts rename test/e2e/tests/{notifications => identity}/account-syncing/helpers.ts (100%) rename test/e2e/tests/{notifications => identity}/account-syncing/importing-private-key-account.spec.ts (81%) rename test/e2e/tests/{notifications => identity}/account-syncing/mockData.ts (100%) rename test/e2e/tests/{notifications => identity}/account-syncing/new-user-sync.spec.ts (85%) rename test/e2e/tests/{notifications => identity}/account-syncing/onboarding-with-opt-out.spec.ts (87%) rename test/e2e/tests/{notifications => identity}/account-syncing/sync-after-adding-account.spec.ts (84%) rename test/e2e/tests/{notifications => identity}/account-syncing/sync-after-modifying-account-name.spec.ts (78%) rename test/e2e/tests/{notifications => identity}/account-syncing/sync-after-onboarding.spec.ts (82%) rename test/e2e/tests/{notifications => identity}/account-syncing/sync-with-account-balances.spec.ts (87%) create mode 100644 test/e2e/tests/identity/constants.ts create mode 100644 test/e2e/tests/identity/mocks.ts create mode 100644 test/e2e/tests/metrics/app-opened.spec.ts create mode 100644 test/e2e/tests/multichain/aggregated-balances.spec.ts delete mode 100644 test/e2e/tests/multichain/all-permissions-page.spec.js create mode 100644 test/e2e/tests/multichain/all-permissions-page.spec.ts create mode 100644 test/e2e/tests/multichain/asset-list.spec.ts delete mode 100644 test/e2e/tests/multichain/permission-page.spec.js create mode 100644 test/e2e/tests/multichain/permission-page.spec.ts delete mode 100644 test/e2e/tests/network/network-error.spec.js delete mode 100644 test/e2e/tests/portfolio/portfolio-site.spec.js create mode 100644 test/e2e/tests/portfolio/portfolio-site.spec.ts delete mode 100644 test/e2e/tests/privacy-mode/privacy-mode.spec.js create mode 100644 test/e2e/tests/privacy-mode/privacy-mode.spec.ts delete mode 100644 test/e2e/tests/privacy/basic-functionality.spec.js create mode 100644 test/e2e/tests/privacy/basic-functionality.spec.ts create mode 100644 test/e2e/tests/remote-feature-flag/mock-data.ts create mode 100644 test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts delete mode 100644 test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js create mode 100644 test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts delete mode 100644 test/e2e/tests/request-queuing/enable-queuing.spec.js delete mode 100644 test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.js create mode 100644 test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.ts delete mode 100644 test/e2e/tests/settings/4byte-directory.spec.js delete mode 100644 test/e2e/tests/settings/show-hex-data.spec.js delete mode 100644 test/e2e/tests/tokens/add-hide-token.spec.js create mode 100644 test/e2e/tests/tokens/add-hide-token.spec.ts delete mode 100644 test/e2e/tests/tokens/add-multiple-tokens.spec.js create mode 100644 test/e2e/tests/tokens/add-multiple-tokens.spec.ts create mode 100644 test/e2e/tests/tokens/add-token-using-search.ts delete mode 100644 test/e2e/tests/tokens/custom-token-add-approve.spec.js rename test/e2e/tests/tokens/{import-tokens.spec.js => import-tokens.spec.ts} (59%) delete mode 100644 test/e2e/tests/tokens/increase-token-allowance.spec.js delete mode 100644 test/e2e/tests/tokens/nft/erc1155-interaction.spec.js delete mode 100644 test/e2e/tests/tokens/nft/erc721-interaction.spec.js delete mode 100644 test/e2e/tests/tokens/nft/send-nft.spec.js delete mode 100644 test/e2e/tests/tokens/send-erc20-to-contract.spec.js create mode 100644 test/e2e/tests/tokens/send-erc20-to-contract.spec.ts create mode 100644 test/e2e/tests/tokens/watch-asset-call-add-token.ts create mode 100644 test/e2e/tests/transaction/incoming-transactions.spec.ts delete mode 100644 test/e2e/tests/transaction/simple-send.spec.ts create mode 100644 test/integration/confirmations/signatures/permit-batch.test.tsx create mode 100644 test/integration/confirmations/signatures/permit-seaport.test.tsx create mode 100644 test/integration/confirmations/signatures/permit-single.test.tsx create mode 100644 test/integration/confirmations/signatures/permit-tradeOrder.test.tsx create mode 100644 test/integration/confirmations/signatures/signature-helpers.ts delete mode 100644 types/eth-query.d.ts create mode 100644 ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.test.tsx rename ui/components/app/assets/nfts/nft-default-image/{nft-default-image.js => nft-default-image.tsx} (63%) create mode 100644 ui/components/app/assets/nfts/nft-grid/index.scss create mode 100644 ui/components/app/assets/nfts/nft-grid/nft-grid.tsx delete mode 100644 ui/components/app/assets/nfts/nft-options/nft-options.js create mode 100644 ui/components/app/assets/nfts/nft-options/nft-options.tsx rename ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/{index.js => index.ts} (100%) rename ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/{nfts-detection-notice-import-nfts.js => nfts-detection-notice-import-nfts.tsx} (83%) rename ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/{index.js => index.ts} (100%) rename ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/{nfts-detection-notice-nfts-tab.js => nfts-detection-notice-nfts-tab.tsx} (100%) delete mode 100644 ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx delete mode 100644 ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx delete mode 100644 ui/components/app/assets/nfts/nfts-items/index.js delete mode 100644 ui/components/app/assets/nfts/nfts-items/index.scss delete mode 100644 ui/components/app/assets/nfts/nfts-items/nfts-items.js delete mode 100644 ui/components/app/assets/nfts/nfts-items/nfts-items.stories.tsx delete mode 100644 ui/components/app/assets/nfts/nfts-items/nfts-items.test.js delete mode 100644 ui/components/app/assets/nfts/nfts-tab/index.scss rename ui/components/app/assets/nfts/nfts-tab/{index.js => index.ts} (100%) rename ui/components/app/assets/nfts/nfts-tab/{nfts-tab.js => nfts-tab.tsx} (50%) create mode 100644 ui/components/app/detected-token/detected-token-ignored-popover/__snapshots__/detected-token-ignored-popover.test.tsx.snap create mode 100644 ui/components/app/detected-token/detected-token-ignored-popover/detected-token-ignored-popover.test.tsx create mode 100644 ui/components/app/snaps/snap-settings-page/index.ts create mode 100644 ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx create mode 100644 ui/components/app/snaps/snap-ui-banner/index.ts create mode 100644 ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx create mode 100644 ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx create mode 100644 ui/components/app/snaps/snap-ui-renderer/components/banner.ts create mode 100644 ui/components/app/transaction-breakdown/transaction-breakdown-utils.ts create mode 100644 ui/components/component-library/skeleton/README.mdx create mode 100644 ui/components/component-library/skeleton/__snapshots__/skeleton.test.tsx.snap create mode 100644 ui/components/component-library/skeleton/index.ts create mode 100644 ui/components/component-library/skeleton/skeleton.scss create mode 100644 ui/components/component-library/skeleton/skeleton.stories.tsx create mode 100644 ui/components/component-library/skeleton/skeleton.test.tsx create mode 100644 ui/components/component-library/skeleton/skeleton.tsx create mode 100644 ui/components/component-library/skeleton/skeleton.types.ts create mode 100644 ui/components/multichain/account-overview/constants.ts create mode 100644 ui/components/multichain/carousel/carousel.stories.tsx create mode 100644 ui/components/multichain/carousel/carousel.test.tsx create mode 100644 ui/components/multichain/carousel/carousel.tsx create mode 100644 ui/components/multichain/carousel/carousel.types.ts create mode 100644 ui/components/multichain/carousel/constants.ts create mode 100644 ui/components/multichain/carousel/helpers.ts create mode 100644 ui/components/multichain/carousel/index.scss create mode 100644 ui/components/multichain/carousel/index.ts rename ui/components/multichain/nft-item/{index.js => index.ts} (100%) delete mode 100644 ui/components/multichain/nft-item/nft-item.js create mode 100644 ui/components/multichain/nft-item/nft-item.tsx create mode 100644 ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.test.tsx delete mode 100644 ui/components/ui/new-network-info/index.js delete mode 100644 ui/components/ui/new-network-info/index.scss delete mode 100644 ui/components/ui/new-network-info/new-network-info.js delete mode 100644 ui/components/ui/new-network-info/new-network-info.stories.js delete mode 100644 ui/components/ui/new-network-info/new-network-info.test.js create mode 100644 ui/components/ui/origin-pill/origin-pill.test.tsx create mode 100644 ui/components/ui/origin-pill/origin-pill.tsx create mode 100644 ui/contexts/identity/index.test.tsx create mode 100644 ui/contexts/identity/index.tsx create mode 100644 ui/ducks/bridge-status/actions.ts create mode 100644 ui/ducks/bridge-status/selectors.ts delete mode 100644 ui/helpers/utils/snaps.js create mode 100644 ui/helpers/utils/snaps.ts create mode 100644 ui/hooks/bridge/__snapshots__/useTokensWithFiltering.test.ts.snap create mode 100644 ui/hooks/bridge/events/types.ts create mode 100644 ui/hooks/bridge/events/useConvertedUsdAmounts.ts create mode 100644 ui/hooks/bridge/events/useQuoteProperties.ts create mode 100644 ui/hooks/bridge/events/useRequestMetadataProperties.ts create mode 100644 ui/hooks/bridge/events/useRequestProperties.ts create mode 100644 ui/hooks/bridge/events/useTradeProperties.ts create mode 100644 ui/hooks/bridge/useBridgeChainInfo.ts create mode 100644 ui/hooks/bridge/useBridgeExchangeRates.ts create mode 100644 ui/hooks/bridge/useBridgeTxHistoryData.ts create mode 100644 ui/hooks/bridge/useCrossChainSwapsEventTracker.ts create mode 100644 ui/hooks/bridge/useIsTxSubmittable.ts create mode 100644 ui/hooks/bridge/useQuoteFetchEvents.ts create mode 100644 ui/hooks/bridge/useTokensWithFiltering.test.ts create mode 100644 ui/hooks/bridge/useTokensWithFiltering.ts create mode 100644 ui/hooks/identity/useAuthentication/index.ts create mode 100644 ui/hooks/identity/useAuthentication/useAutoSignIn.test.tsx create mode 100644 ui/hooks/identity/useAuthentication/useAutoSignIn.ts create mode 100644 ui/hooks/identity/useAuthentication/useSignIn.test.tsx create mode 100644 ui/hooks/identity/useAuthentication/useSignIn.ts rename ui/hooks/{metamask-notifications => identity}/useCreateSession.test.ts (100%) rename ui/hooks/{metamask-notifications => identity}/useCreateSession.ts (80%) create mode 100644 ui/hooks/identity/useProfileSyncing/accountSyncing.test.tsx create mode 100644 ui/hooks/identity/useProfileSyncing/accountSyncing.ts rename ui/hooks/{metamask-notifications => identity}/useProfileSyncing/index.ts (78%) create mode 100644 ui/hooks/identity/useProfileSyncing/profileSyncing.test.tsx rename ui/hooks/{metamask-notifications => identity}/useProfileSyncing/profileSyncing.ts (69%) delete mode 100644 ui/hooks/metamask-notifications/useProfileSyncing/accountSyncing.test.tsx delete mode 100644 ui/hooks/metamask-notifications/useProfileSyncing/accountSyncing.ts delete mode 100644 ui/hooks/metamask-notifications/useProfileSyncing/profileSyncing.test.tsx create mode 100644 ui/hooks/snaps/useSnapSettings.ts rename ui/hooks/{metamask-notifications => }/useMetametrics.test.tsx (75%) rename ui/hooks/{metamask-notifications => }/useMetametrics.ts (72%) create mode 100644 ui/hooks/useMultichainBalances.test.ts create mode 100644 ui/hooks/useMultichainBalances.ts create mode 100644 ui/hooks/useNfts.ts create mode 100644 ui/hooks/useNotificationTimeouts.ts delete mode 100644 ui/hooks/useTokensWithFiltering.test.ts delete mode 100644 ui/hooks/useTokensWithFiltering.ts create mode 100644 ui/pages/bridge/awaiting-signatures/awaiting-signatures-cancel-button.tsx create mode 100644 ui/pages/bridge/awaiting-signatures/awaiting-signatures.tsx create mode 100644 ui/pages/bridge/awaiting-signatures/index.scss create mode 100644 ui/pages/bridge/awaiting-signatures/index.ts create mode 100644 ui/pages/bridge/hooks/useBridgeTokenDisplayData.ts create mode 100644 ui/pages/bridge/prepare/bridge-transaction-settings-modal.stories.tsx create mode 100644 ui/pages/bridge/prepare/bridge-transaction-settings-modal.tsx create mode 100644 ui/pages/bridge/prepare/bridge-tx-declined-message.tsx create mode 100644 ui/pages/bridge/prepare/components/bridge-asset-picker-button.tsx create mode 100644 ui/pages/bridge/prepare/prepare-bridge-page.stories.tsx delete mode 100644 ui/pages/bridge/quotes/quote-info-row.tsx create mode 100644 ui/pages/bridge/transaction-details/bridge-activity-item-tx-segments.tsx create mode 100644 ui/pages/bridge/transaction-details/bridge-explorer-links.tsx create mode 100644 ui/pages/bridge/transaction-details/bridge-step-description.tsx create mode 100644 ui/pages/bridge/transaction-details/bridge-step-list.stories.tsx create mode 100644 ui/pages/bridge/transaction-details/bridge-step-list.tsx create mode 100644 ui/pages/bridge/transaction-details/hollow-circle.tsx create mode 100644 ui/pages/bridge/transaction-details/index.scss create mode 100644 ui/pages/bridge/transaction-details/pulsing-circle.tsx create mode 100644 ui/pages/bridge/transaction-details/segment.stories.tsx create mode 100644 ui/pages/bridge/transaction-details/segment.tsx create mode 100644 ui/pages/bridge/transaction-details/step-progress-bar-item.tsx create mode 100644 ui/pages/bridge/transaction-details/transaction-detail-row.tsx create mode 100644 ui/pages/bridge/transaction-details/transaction-details.test.tsx create mode 100644 ui/pages/bridge/transaction-details/transaction-details.tsx delete mode 100644 ui/pages/bridge/types.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useSendingValueMetric.test.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useSendingValueMetric.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.test.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.test.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.ts delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/__snapshots__/decoded-simulation.test.tsx.snap delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.test.tsx delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/default-simulation/index.ts delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx delete mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/decoded-simulation/decoded-simulation.test.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/decoded-simulation/decoded-simulation.tsx rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation => typed-sign-v4-simulation}/decoded-simulation/index.ts (100%) create mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/index.ts rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation => typed-sign-v4-simulation}/native-value-display/native-value-display.test.tsx (100%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation => typed-sign-v4-simulation}/native-value-display/native-value-display.tsx (96%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation/default-simulation/__snapshots__/default-simulation.test.tsx.snap => typed-sign-v4-simulation/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap} (98%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{ => typed-sign-v4-simulation}/permit-simulation/index.ts (100%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation/default-simulation/default-simulation.test.tsx => typed-sign-v4-simulation/permit-simulation/permit-simulation.test.tsx} (92%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation/default-simulation/default-simulation.tsx => typed-sign-v4-simulation/permit-simulation/permit-simulation.tsx} (83%) create mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.test.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.tsx rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation => typed-sign-v4-simulation}/value-display/__snapshots__/value-display.test.tsx.snap (100%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation => typed-sign-v4-simulation}/value-display/value-display.test.tsx (74%) rename ui/pages/confirmations/components/confirm/info/typed-sign/{permit-simulation => typed-sign-v4-simulation}/value-display/value-display.tsx (69%) create mode 100644 ui/pages/confirmations/components/smart-transactions-banner-alert/index.ts create mode 100644 ui/pages/confirmations/components/smart-transactions-banner-alert/smart-transactions-banner-alert.test.tsx create mode 100644 ui/pages/confirmations/components/smart-transactions-banner-alert/smart-transactions-banner-alert.tsx create mode 100644 ui/pages/confirmations/confirmation/stories/snap-dialog.stories.tsx create mode 100644 ui/pages/confirmations/hooks/alerts/transactions/PendingTransactionAlertMessage.tsx create mode 100644 ui/pages/confirmations/hooks/useConfirmationNavigation.test.ts create mode 100644 ui/pages/confirmations/hooks/useConfirmationNavigation.ts create mode 100644 ui/pages/confirmations/hooks/useDecodedSignatureMetrics.test.ts create mode 100644 ui/pages/confirmations/hooks/useDecodedSignatureMetrics.ts create mode 100644 ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.test.ts create mode 100644 ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.ts delete mode 100644 ui/pages/notifications/snap/types/types.ts delete mode 100644 ui/pages/notifications/snap/utils/utils.ts create mode 100644 ui/pages/settings/developer-options-tab/profile-sync.test.tsx delete mode 100644 ui/pages/snaps/snap-view/snap-home.js create mode 100644 ui/selectors/identity/authentication.test.ts rename ui/selectors/{metamask-notifications => identity}/authentication.ts (66%) rename ui/selectors/{metamask-notifications => identity}/profile-syncing.test.ts (92%) rename ui/selectors/{metamask-notifications => identity}/profile-syncing.ts (100%) delete mode 100644 ui/selectors/metamask-notifications/authentication.test.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e3ccba9005e..03087695d452 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,32 +3,28 @@ version: 2.1 executors: node-browsers-small: docker: - - image: cimg/node:20.17-browsers + - image: cimg/node:20.18-browsers resource_class: small environment: NODE_OPTIONS: --max_old_space_size=2048 node-browsers-medium: docker: - - image: cimg/node:20.17-browsers + - image: cimg/node:20.18-browsers resource_class: medium environment: NODE_OPTIONS: --max_old_space_size=3072 node-linux-medium: machine: - image: ubuntu-2404:current + image: ubuntu-2404:2024.05.1 resource_class: medium #// linux medium: 2 CPUs, 7.5 GB RAM, 10 credits/min environment: NODE_OPTIONS: --max_old_space_size=6144 node-browsers-medium-plus: docker: - - image: cimg/node:20.17-browsers + - image: cimg/node:20.18-browsers resource_class: medium+ environment: NODE_OPTIONS: --max_old_space_size=4096 - shellcheck: - docker: - - image: koalaman/shellcheck-alpine@sha256:dfaf08fab58c158549d3be64fb101c626abc5f16f341b569092577ae207db199 - resource_class: small playwright: docker: - image: mcr.microsoft.com/playwright:v1.44.1-focal @@ -45,11 +41,11 @@ rc_branch_only: &rc_branch_only only: - /^Version-v(\d+)[.](\d+)[.](\d+)/ -develop_master_rc_only: &develop_master_rc_only +main_master_rc_only: &main_master_rc_only filters: branches: only: - - develop + - main - master - /^Version-v(\d+)[.](\d+)[.](\d+)/ @@ -87,18 +83,6 @@ aliases: cat ${HOME}/project/.circleci/scripts/enable-vnc.sh >> ~/.bashrc fi - # Check if MMI tests should run - - &check-mmi-trigger - name: Check if MMI tests should run - command: | - source mmi_trigger.env - if [ "${run_mmi_tests}" == "true" ]; then - echo "Running MMI tests" - else - echo "Skipping MMI tests" - circleci step halt - fi - workflows: test_and_release: when: @@ -107,44 +91,20 @@ workflows: - matches: pattern: /^l10n_crowdin_action$/ value: << pipeline.git.branch >> - - equal: [rerun-from-failed, << pipeline.schedule.name >>] + - matches: + pattern: /^rerun-from-failed.*/ + value: << pipeline.schedule.name >> jobs: - create_release_pull_request: <<: *rc_branch_only requires: - prep-deps - - trigger-beta-build: - requires: - - prep-deps - - check-mmi-trigger - prep-deps - get-changed-files-with-git-diff: filters: branches: ignore: - master - - test-deps-audit: - requires: - - prep-deps - - test-deps-depcheck: - requires: - - prep-deps - - test-yarn-dedupe: - requires: - - prep-deps - - validate-lavamoat-allow-scripts: - requires: - - prep-deps - - validate-lavamoat-policy-build: - requires: - - prep-deps - - validate-lavamoat-policy-webapp: - matrix: - parameters: - build-type: [main, beta, flask, mmi] - requires: - - prep-deps - - prep-build-mmi: requires: - prep-deps - prep-build: @@ -172,35 +132,16 @@ workflows: requires: - prep-deps - prep-build-test-flask-mv2: - <<: *develop_master_rc_only requires: - prep-deps - - prep-build-test-mmi: - requires: - - prep-deps - - check-mmi-trigger - - prep-build-test-mmi-playwright: - requires: - - prep-deps - - check-mmi-trigger - prep-build-storybook: requires: - prep-deps - prep-build-ts-migration-dashboard: requires: - prep-deps - - test-lint: - requires: - - prep-deps - - test-lint-shellcheck - - test-lint-lockfile: - requires: - - prep-deps - - test-lint-changelog: - requires: - - prep-deps - test-e2e-chrome-webpack: - <<: *develop_master_rc_only + <<: *main_master_rc_only requires: - prep-build-test-webpack - get-changed-files-with-git-diff @@ -209,7 +150,7 @@ workflows: - prep-build-test - get-changed-files-with-git-diff - test-e2e-firefox: - <<: *develop_master_rc_only + <<: *main_master_rc_only requires: - prep-build-test-mv2 - get-changed-files-with-git-diff @@ -220,6 +161,7 @@ workflows: - test-api-specs: requires: - prep-build-test + - get-changed-files-with-git-diff - test-e2e-chrome-multiple-providers: requires: - prep-build-test @@ -229,28 +171,17 @@ workflows: - prep-build-test-flask - get-changed-files-with-git-diff - test-e2e-firefox-flask: - <<: *develop_master_rc_only + <<: *main_master_rc_only requires: - prep-build-test-flask-mv2 - - test-e2e-chrome-mmi: - requires: - - prep-build-test-mmi - - get-changed-files-with-git-diff - - test-e2e-mmi-playwright: - requires: - - prep-build-test-mmi-playwright - test-e2e-swap-playwright - OPTIONAL: requires: - prep-build - - test-e2e-chrome-rpc-mmi: - requires: - - prep-build-test-mmi - - get-changed-files-with-git-diff - test-e2e-chrome-vault-decryption: filters: branches: only: - - develop + - main - /^Version-v(\d+)[.](\d+)[.](\d+)/ requires: - prep-build @@ -264,12 +195,6 @@ workflows: - validate-source-maps-mv2: requires: - prep-build-mv2 - - validate-source-maps-beta: - requires: - - trigger-beta-build - - validate-source-maps-mmi: - requires: - - prep-build-mmi - validate-source-maps-flask: requires: - prep-build-flask @@ -286,18 +211,8 @@ workflows: - prep-build-flask-mv2 - all-tests-pass: requires: - - test-deps-depcheck - - validate-lavamoat-allow-scripts - - validate-lavamoat-policy-build - - validate-lavamoat-policy-webapp - - test-lint - - test-lint-shellcheck - - test-lint-lockfile - - test-lint-changelog - validate-source-maps - - validate-source-maps-beta - validate-source-maps-flask - - validate-source-maps-mmi - test-mozilla-lint-mv2 - test-mozilla-lint-flask-mv2 - test-e2e-chrome @@ -305,8 +220,6 @@ workflows: - test-e2e-firefox - test-e2e-chrome-flask - test-e2e-firefox-flask - - test-e2e-chrome-mmi - - test-e2e-chrome-rpc-mmi - test-e2e-chrome-vault-decryption - test-e2e-chrome-webpack - test-storybook @@ -316,7 +229,7 @@ workflows: - user-actions-benchmark: requires: - prep-build-test - - stats-module-load-init: + - bundle-size: requires: - prep-build-test - job-publish-prerelease: @@ -324,15 +237,17 @@ workflows: - prep-deps - prep-build - prep-build-mv2 - - trigger-beta-build - - prep-build-mmi - prep-build-flask - prep-build-flask-mv2 + - prep-build-test + - prep-build-test-mv2 + - prep-build-test-flask + - prep-build-test-flask-mv2 - prep-build-storybook - prep-build-ts-migration-dashboard - benchmark - user-actions-benchmark - - stats-module-load-init + - bundle-size - all-tests-pass - job-publish-release: filters: @@ -342,32 +257,33 @@ workflows: - prep-deps - prep-build - prep-build-mv2 - - prep-build-mmi - prep-build-flask - prep-build-flask-mv2 - all-tests-pass - job-publish-storybook: filters: branches: - only: develop + only: main requires: - prep-build-storybook - job-publish-ts-migration-dashboard: filters: branches: - only: develop + only: main requires: - prep-build-ts-migration-dashboard rerun-from-failed: when: - equal: [rerun-from-failed, << pipeline.schedule.name >>] + matches: + pattern: /^rerun-from-failed.*/ + value: << pipeline.schedule.name >> jobs: - prep-deps - rerun-workflows-from-failed: filters: branches: - only: develop + only: main requires: - prep-deps @@ -392,35 +308,6 @@ workflows: - validate-locales-only jobs: - trigger-beta-build: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - when: - condition: - not: - matches: - pattern: /^master$/ - value: << pipeline.git.branch >> - steps: - - run: - name: Build beta prod - command: .circleci/scripts/trigger-beta-build.sh - - run: - name: Move beta build to 'dist-beta' to avoid conflict with production build - command: mv ./dist ./dist-beta - - run: - name: Move beta zips to 'builds-beta' to avoid conflict with production build - command: mv ./builds ./builds-beta - - persist_to_workspace: - root: . - paths: - - dist-beta - - builds-beta - create_release_pull_request: executor: node-browsers-medium steps: @@ -477,7 +364,7 @@ jobs: # This job is used for the e2e quality gate. # It must be run before any job which uses the run-all.js script. - # The job is skipped in develop, master or RC branches. + # The job is skipped in main, master or RC branches. get-changed-files-with-git-diff: executor: node-browsers-small steps: @@ -487,7 +374,7 @@ jobs: at: . - run: name: Get changed files with git diff - command: npx tsx .circleci/scripts/git-diff-develop.ts + command: yarn git-diff-default-branch - persist_to_workspace: root: . paths: @@ -502,51 +389,6 @@ jobs: at: . - run: yarn tsx .circleci/scripts/validate-locales-only.ts - validate-lavamoat-allow-scripts: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Validate allow-scripts config - command: yarn allow-scripts auto - - run: - name: Check working tree - command: .circleci/scripts/check-working-tree.sh - - validate-lavamoat-policy-build: - executor: node-browsers-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Validate LavaMoat build policy - command: yarn lavamoat:build:auto - - run: - name: Check working tree - command: .circleci/scripts/check-working-tree.sh - - validate-lavamoat-policy-webapp: - executor: node-browsers-medium-plus - parameters: - build-type: - type: string - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Validate LavaMoat << parameters.build-type >> policy - command: yarn lavamoat:webapp:auto:ci '--build-types=<< parameters.build-type >>' - - run: - name: Check working tree - command: .circleci/scripts/check-working-tree.sh - prep-build: executor: node-linux-medium steps: @@ -627,50 +469,6 @@ jobs: - dist-mv2 - builds-mv2 - prep-build-mmi: - executor: node-linux-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: corepack enable - - attach_workspace: - at: . - - when: - condition: - not: - matches: - pattern: /^master$/ - value: << pipeline.git.branch >> - steps: - - run: - name: build:dist - command: yarn build --build-type mmi dist - - when: - condition: - matches: - pattern: /^master$/ - value: << pipeline.git.branch >> - steps: - - run: - name: build:prod - command: yarn build --build-type mmi prod - - run: - name: build:debug - command: find dist/ -type f -exec md5sum {} \; | sort -k 2 - - run: - name: Move mmi build to 'dist-mmi' to avoid conflict with production build - command: mv ./dist ./dist-mmi - - run: - name: Move mmi zips to 'builds-mmi' to avoid conflict with production build - command: mv ./builds ./builds-mmi - - persist_to_workspace: - root: . - paths: - - dist-mmi - - builds-mmi - - store_artifacts: - path: builds-mmi - destination: builds-mmi - prep-build-flask: executor: node-linux-medium steps: @@ -786,10 +584,10 @@ jobs: name: Build extension for testing command: yarn build:test:flask:mv2 - run: - name: Move test build to 'dist-test-flask' to avoid conflict with production build + name: Move test build to 'dist-test-flask-mv2' to avoid conflict with production build command: mv ./dist ./dist-test-flask-mv2 - run: - name: Move test zips to 'builds-test-flask' to avoid conflict with production build + name: Move test zips to 'builds-test-flask-mv2' to avoid conflict with production build command: mv ./builds ./builds-test-flask-mv2 - persist_to_workspace: root: . @@ -797,57 +595,6 @@ jobs: - dist-test-flask-mv2 - builds-test-flask-mv2 - prep-build-test-mmi: - executor: node-linux-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: corepack enable - - attach_workspace: - at: . - - run: *check-mmi-trigger - - run: - name: Build extension for testing - command: yarn build:test:mmi - - run: - name: Move test build to 'dist-test' to avoid conflict with production build - command: mv ./dist ./dist-test-mmi - - run: - name: Move test zips to 'builds-test' to avoid conflict with production build - command: mv ./builds ./builds-test-mmi - - persist_to_workspace: - root: . - paths: - - dist-test-mmi - - builds-test-mmi - - prep-build-test-mmi-playwright: - executor: node-linux-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: corepack enable - - attach_workspace: - at: . - - run: *check-mmi-trigger - - run: - name: Build MMI extension for Playwright e2e - command: | - export MMI_CONFIGURATION_SERVICE_URL=$MMI_DEV_CONFIGURATION_SERVICE_URL - yarn dist:mmi - - run: - name: Move test build to 'dist-test' to avoid conflict with production build - command: mv ./dist ./dist-test-mmi-playwright - - run: - name: Move test zips to 'builds-test' to avoid conflict with production build - command: mv ./builds ./builds-test-mmi-playwright - - persist_to_workspace: - root: . - paths: - - dist-test-mmi-playwright - - builds-test-mmi-playwright - - store_artifacts: - path: builds-test-mmi-playwright - destination: builds-test-mmi-playwright - prep-build-test: executor: node-linux-medium steps: @@ -957,31 +704,6 @@ jobs: name: Rerun workflows from failed command: yarn ci-rerun-from-failed - test-yarn-dedupe: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Detect yarn lock deduplications - command: yarn dedupe --check - - test-lint: - executor: node-browsers-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Lint - command: yarn lint - - run: - name: Verify locales - command: yarn verify-locales --quiet - test-storybook: executor: node-browsers-medium-plus steps: @@ -996,78 +718,6 @@ jobs: name: Test Storybook command: yarn test-storybook:ci - test-lint-shellcheck: - executor: shellcheck - steps: - - checkout - - run: apk add --no-cache bash jq yarn - - run: - name: ShellCheck Lint - command: ./development/shellcheck.sh - - test-lint-lockfile: - executor: node-browsers-medium-plus - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: lockfile-lint - command: yarn lint:lockfile - - run: - name: check yarn resolutions - command: yarn --check-resolutions - - test-lint-changelog: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - when: - condition: - not: - matches: - pattern: /^Version-v(\d+)[.](\d+)[.](\d+)$/ - value: << pipeline.git.branch >> - steps: - - run: - name: Validate changelog - command: yarn lint:changelog - - when: - condition: - matches: - pattern: /^Version-v(\d+)[.](\d+)[.](\d+)$/ - value: << pipeline.git.branch >> - steps: - - run: - name: Validate release candidate changelog - command: .circleci/scripts/validate-changelog-in-rc.sh - - test-deps-audit: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: yarn audit - command: yarn audit - - test-deps-depcheck: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: depcheck - command: yarn depcheck - test-e2e-chrome-webpack: executor: node-browsers-medium-plus parallelism: 20 @@ -1192,30 +842,6 @@ jobs: - store_test_results: path: test/test-results/e2e - test-e2e-chrome-rpc-mmi: - executor: node-browsers-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: *check-mmi-trigger - - run: - name: Move test build to dist - command: mv ./dist-test-mmi ./dist - - run: - name: Move test zips to builds - command: mv ./builds-test-mmi ./builds - - run: - name: test:e2e:chrome:rpc - command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:rpc --build-type=mmi - no_output_timeout: 5m - - store_artifacts: - path: test-artifacts - destination: test-artifacts - - store_test_results: - path: test/test-results/e2e - test-e2e-chrome-vault-decryption: executor: node-browsers-medium-plus steps: @@ -1281,72 +907,6 @@ jobs: - store_test_results: path: test/test-results/e2e - test-e2e-chrome-mmi: - executor: node-browsers-medium-plus - parallelism: 12 - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: *check-mmi-trigger - - run: - name: Move test build to dist - command: mv ./dist-test-mmi ./dist - - run: - name: Move test zips to builds - command: mv ./builds-test-mmi ./builds - - run: - name: test:e2e:chrome:mmi - command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:mmi --build-type=mmi - no_output_timeout: 5m - - store_artifacts: - path: test-artifacts - destination: test-artifacts - - store_test_results: - path: test/test-results/e2e - - test-e2e-mmi-playwright: - executor: playwright - parallelism: 2 - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: corepack enable - - attach_workspace: - at: . - - run: *check-mmi-trigger - - run: - name: Move test build to dist - command: mv ./dist-test-mmi-playwright ./dist - - run: - name: Install chromium - command: yarn playwright install chromium - - run: - name: test:e2e:chrome:mmi - command: | - TESTFILES=$(circleci tests glob "test/e2e/playwright/mmi/**/*.spec.ts") - echo "$TESTFILES" - echo "$TESTFILES" | timeout 20m circleci tests run --command="xvfb-run xargs yarn playwright test --project=mmi" verbose --split-by=timings - no_output_timeout: 10m - - slack/notify: - branch_pattern: Version-v* - event: fail - mentions: <@antonio.regadas>, @ramon.acitores134 - template: basic_fail_1 - channel: C01LUJL3T98 - - slack/notify: - branch_pattern: develop - event: fail - mentions: <@antonio.regadas>, @ramon.acitores134 - template: basic_fail_1 - channel: C05QXJA7NP8 - - store_artifacts: - name: html-report and artifacts - path: public/playwright/playwright-reports - - store_test_results: - name: report for pipeline integration - path: public/playwright/playwright-reports/junit/test-results.xml - test-e2e-swap-playwright - OPTIONAL: executor: playwright parallelism: 2 @@ -1449,7 +1009,7 @@ jobs: paths: - test-artifacts - stats-module-load-init: + bundle-size: executor: node-browsers-small steps: - run: *shallow-git-clone-and-enable-vnc @@ -1463,16 +1023,8 @@ jobs: name: Move test zips to builds command: mv ./builds-test ./builds - run: - name: Run page load benchmark - command: | - mkdir -p test-artifacts/chrome/ - cp -R development/charts/flamegraph test-artifacts/chrome/initialisation - cp -R development/charts/flamegraph/chart test-artifacts/chrome/initialisation/background - cp -R development/charts/flamegraph/chart test-artifacts/chrome/initialisation/ui - cp -R development/charts/table test-artifacts/chrome/load_time - - run: - name: Run page load benchmark - command: yarn mv3:stats:chrome --out test-artifacts/chrome + name: Measure bundle size + command: yarn bundle-size --out test-artifacts/chrome - run: name: Install jq command: sudo apt install jq -y @@ -1500,34 +1052,29 @@ jobs: - store_artifacts: path: dist/sourcemaps destination: builds/sourcemaps - - store_artifacts: - path: dist-beta/sourcemaps - destination: builds-beta/sourcemaps - store_artifacts: path: dist-flask/sourcemaps destination: builds-flask/sourcemaps - store_artifacts: path: builds destination: builds - - store_artifacts: - path: builds-beta - destination: builds-beta - store_artifacts: path: builds-flask destination: builds-flask - store_artifacts: path: builds-flask-mv2 destination: builds-flask-mv2 - - store_artifacts: - path: builds-mmi - destination: builds-mmi - store_artifacts: path: builds-mv2 destination: builds-mv2 - store_artifacts: path: builds-test + - store_artifacts: + path: builds-test-mv2 - store_artifacts: path: builds-test-flask + - store_artifacts: + path: builds-test-flask-mv2 - store_artifacts: path: test-artifacts destination: test-artifacts @@ -1544,14 +1091,6 @@ jobs: - store_artifacts: path: development/ts-migration-dashboard/build/final destination: ts-migration-dashboard - - run: - name: Set branch parent commit env var - command: | - echo "export PARENT_COMMIT=$(git merge-base origin/HEAD HEAD)" >> $BASH_ENV - source $BASH_ENV - - run: - name: build:announce - command: ./development/metamaskbot-build-announce.js job-publish-release: executor: node-browsers-small @@ -1572,9 +1111,6 @@ jobs: - run: name: Publish Flask MV2 release to Sentry command: yarn sentry:publish --build-type flask --dist mv2 - - run: - name: Publish MMI release to Sentry - command: yarn sentry:publish --build-type mmi - run: name: Create GitHub release command: .circleci/scripts/release-create-gh-release.sh @@ -1624,34 +1160,6 @@ jobs: name: Validate source maps command: yarn validate-source-maps - validate-source-maps-beta: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Validate source maps - command: .circleci/scripts/validate-source-maps-beta.sh - - validate-source-maps-mmi: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Move mmi build to dist - command: mv ./dist-mmi ./dist - - run: - name: Move mmi zips to builds - command: mv ./builds-mmi ./builds - - run: - name: Validate source maps - command: yarn validate-source-maps - validate-source-maps-flask: executor: node-browsers-small steps: @@ -1743,18 +1251,3 @@ jobs: - run: name: All Tests Passed command: echo 'whew - everything passed!' - - check-mmi-trigger: - executor: node-browsers-medium - steps: - - checkout - - run: - name: Check for MMI Team Label or Reviewer - command: ./.circleci/scripts/check_mmi_trigger.sh - - store_artifacts: - path: mmi_trigger.env - destination: mmi_trigger.env - - persist_to_workspace: - root: . - paths: - - mmi_trigger.env diff --git a/.circleci/scripts/bundle-stats-commit.sh b/.circleci/scripts/bundle-stats-commit.sh index 14b3604d82ec..1c9f4380f694 100755 --- a/.circleci/scripts/bundle-stats-commit.sh +++ b/.circleci/scripts/bundle-stats-commit.sh @@ -16,9 +16,9 @@ then exit 1 fi -if [[ "${CIRCLE_BRANCH}" != "develop" ]] +if [[ "${CIRCLE_BRANCH}" != "main" ]] then - printf 'This is not develop branch' + printf 'This is not main branch' exit 0 fi diff --git a/.circleci/scripts/check-working-tree.sh b/.circleci/scripts/check-working-tree.sh deleted file mode 100755 index 5de67431ad33..000000000000 --- a/.circleci/scripts/check-working-tree.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -if ! git diff --exit-code -then - echo "Working tree dirty" - exit 1 -fi diff --git a/.circleci/scripts/check_mmi_trigger.sh b/.circleci/scripts/check_mmi_trigger.sh index c8d6fc44523b..b2a5ca13734d 100755 --- a/.circleci/scripts/check_mmi_trigger.sh +++ b/.circleci/scripts/check_mmi_trigger.sh @@ -9,7 +9,7 @@ if [ -z "$CIRCLE_PULL_REQUEST" ] || [ -z "$GITHUB_TOKEN" ]; then exit 0 fi -if [[ $CIRCLE_BRANCH = 'develop' || $CIRCLE_BRANCH = 'master' || $CIRCLE_BRANCH =~ ^Version-v[0-9.]* ]]; then +if [[ $CIRCLE_BRANCH = 'main' || $CIRCLE_BRANCH = 'master' || $CIRCLE_BRANCH =~ ^Version-v[0-9.]* ]]; then echo "Long-running branch detected, running MMI tests." echo "run_mmi_tests=true" > mmi_trigger.env exit 0 diff --git a/.circleci/scripts/git-diff-develop.ts b/.circleci/scripts/git-diff-default-branch.ts similarity index 89% rename from .circleci/scripts/git-diff-develop.ts rename to .circleci/scripts/git-diff-default-branch.ts index f4437d6154db..269de912dafd 100644 --- a/.circleci/scripts/git-diff-develop.ts +++ b/.circleci/scripts/git-diff-default-branch.ts @@ -10,7 +10,7 @@ const PR_NUMBER = process.env.CIRCLE_PR_NUMBER || process.env.CIRCLE_PULL_REQUEST?.split('/').pop(); -const MAIN_BRANCH = 'develop'; +const GITHUB_DEFAULT_BRANCH = 'main'; const SOURCE_BRANCH = `refs/pull/${PR_NUMBER}/head`; const CHANGED_FILES_DIR = 'changed-files'; @@ -48,7 +48,7 @@ async function getPrInfo(): Promise { */ async function fetchWithDepth(depth: number): Promise { try { - await exec(`git fetch --depth ${depth} origin "${MAIN_BRANCH}"`); + await exec(`git fetch --depth ${depth} origin "${GITHUB_DEFAULT_BRANCH}"`); await exec( `git fetch --depth ${depth} origin "${SOURCE_BRANCH}:${SOURCE_BRANCH}"`, ); @@ -84,7 +84,7 @@ async function fetchUntilMergeBaseFound() { } } } - await exec(`git fetch --unshallow origin "${MAIN_BRANCH}"`); + await exec(`git fetch --unshallow origin "${GITHUB_DEFAULT_BRANCH}"`); } /** @@ -123,9 +123,7 @@ async function storeGitDiffOutputAndPrBody() { // even if we want to skip this step. fs.mkdirSync(CHANGED_FILES_DIR, { recursive: true }); - console.log( - `Determining whether to run git diff...`, - ); + console.log(`Determining whether to run git diff...`); if (!PR_NUMBER) { console.log('Not a PR, skipping git diff'); return; @@ -137,11 +135,13 @@ async function storeGitDiffOutputAndPrBody() { if (!baseRef) { console.log('Not a PR, skipping git diff'); return; - } else if (baseRef !== MAIN_BRANCH) { + } else if (baseRef !== GITHUB_DEFAULT_BRANCH) { console.log(`This is for a PR targeting '${baseRef}', skipping git diff`); writePrBodyToFile(prInfo.body); return; - } else if (prInfo.labels.some(label => label.name === 'skip-e2e-quality-gate')) { + } else if ( + prInfo.labels.some((label) => label.name === 'skip-e2e-quality-gate') + ) { console.log('PR has the skip-e2e-quality-gate label, skipping git diff'); return; } @@ -164,4 +164,7 @@ async function storeGitDiffOutputAndPrBody() { } } -storeGitDiffOutputAndPrBody(); +// If main module (i.e. this is the TS file that was run directly) +if (require.main === module) { + storeGitDiffOutputAndPrBody(); +} diff --git a/.circleci/scripts/release-create-gh-release.sh b/.circleci/scripts/release-create-gh-release.sh index 29118f57b756..d8ade6686ec6 100755 --- a/.circleci/scripts/release-create-gh-release.sh +++ b/.circleci/scripts/release-create-gh-release.sh @@ -61,7 +61,6 @@ if [[ $current_commit_msg =~ Version[-[:space:]](v[[:digit:]]+.[[:digit:]]+.[[:d then tag="${BASH_REMATCH[1]}" flask_version="$(print_build_version 'flask')" - mmi_version="$(print_build_version 'mmi')" install_github_cli @@ -72,15 +71,12 @@ then --attach builds-mv2/metamask-firefox-*.zip \ --attach builds-flask/metamask-flask-chrome-*.zip \ --attach builds-flask-mv2/metamask-flask-firefox-*.zip \ - --attach builds-mmi/metamask-mmi-chrome-*.zip \ - --attach builds-mmi/metamask-mmi-firefox-*.zip \ --message "Version ${tag##v}" \ --message "$release_body" \ --commitish "$CIRCLE_SHA1" \ "$tag" publish_tag 'Flask' "${flask_version}" - publish_tag 'MMI' "${mmi_version}" else printf '%s\n' 'Version not found in commit message; skipping GitHub Release' exit 0 diff --git a/.circleci/scripts/rerun-ci-workflow-from-failed.ts b/.circleci/scripts/rerun-ci-workflow-from-failed.ts index 84827f11fd13..3c1826a4971e 100644 --- a/.circleci/scripts/rerun-ci-workflow-from-failed.ts +++ b/.circleci/scripts/rerun-ci-workflow-from-failed.ts @@ -1,4 +1,5 @@ const CIRCLE_TOKEN = process.env.API_V2_TOKEN; +const GITHUB_DEFAULT_BRANCH = 'main'; interface Actor { login: string; @@ -59,7 +60,7 @@ interface WorkflowStatusResponse { * Note: the API returns the first 20 workflows by default. * If we wanted to get older workflows, we would need to use the 'page-token' we would get in the first response * and perform a subsequent request with the 'page-token' parameter. - * This seems unnecessary as of today, as the amount of daily PRs merged to develop is not that high. + * This seems unnecessary as of today, as the amount of daily PRs merged to main is not that high. * * @returns {Promise} A promise that resolves to an array of workflow items. * @throws Will throw an error if the CircleCI token is not defined or if the HTTP request fails. @@ -177,7 +178,7 @@ async function rerunWorkflowById(workflowId: string) { } /** - * Re-runs failed CircleCI workflows from develop branch. + * Re-runs failed CircleCI workflows from default branch. * The workflow will only be re-runed if: * 1. It has the status of 'failed' * 2. It has only been run once @@ -186,9 +187,9 @@ async function rerunWorkflowById(workflowId: string) { * * @throws Will throw an error if fetching the workflows or re-running a workflow fails. */ -async function rerunFailedWorkflowsFromDevelop() { - console.log('Getting Circle Ci workflows from develop branch...'); - const workflows = await getCircleCiWorkflowsByBranch('develop'); +async function rerunFailedWorkflowsFromDefaultBranch() { + console.log('Getting Circle Ci workflows from main branch...'); + const workflows = await getCircleCiWorkflowsByBranch(GITHUB_DEFAULT_BRANCH); console.log('Assessing if any of the workflows needs to be rerun...'); for (const item of workflows) { @@ -204,7 +205,7 @@ async function rerunFailedWorkflowsFromDevelop() { console.log('Task completed successfully!'); } -rerunFailedWorkflowsFromDevelop() +rerunFailedWorkflowsFromDefaultBranch() .catch((error) => { console.error(error); process.exitCode = 1; diff --git a/.circleci/scripts/test-run-e2e-timeout-minutes.ts b/.circleci/scripts/test-run-e2e-timeout-minutes.ts index c539133b0c60..c486224139c3 100644 --- a/.circleci/scripts/test-run-e2e-timeout-minutes.ts +++ b/.circleci/scripts/test-run-e2e-timeout-minutes.ts @@ -1,8 +1,23 @@ +import { fetchManifestFlagsFromPRAndGit } from '../../development/lib/get-manifest-flag'; import { filterE2eChangedFiles } from '../../test/e2e/changedFilesUtil'; -const changedOrNewTests = filterE2eChangedFiles(); +fetchManifestFlagsFromPRAndGit().then((manifestFlags) => { + let timeout; -// 20 minutes, plus 3 minutes for every changed file, up to a maximum of 30 minutes -const extraTime = Math.min(20 + changedOrNewTests.length * 3, 30); + if (manifestFlags.circleci?.timeoutMinutes) { + timeout = manifestFlags.circleci?.timeoutMinutes; + } else { + const changedOrNewTests = filterE2eChangedFiles(); -console.log(extraTime); + // 20 minutes, plus 3 minutes for every changed file, up to a maximum of 30 minutes + timeout = Math.min(20 + changedOrNewTests.length * 3, 30); + } + + // If this is the Merge Queue, add 10 minutes + if (process.env.CIRCLE_BRANCH?.startsWith('gh-readonly-queue')) { + timeout += 10; + } + + // This is an essential log, that feeds into a bash script + console.log(timeout); +}); diff --git a/.circleci/scripts/trigger-beta-build.sh b/.circleci/scripts/trigger-beta-build.sh deleted file mode 100755 index 34801a476d80..000000000000 --- a/.circleci/scripts/trigger-beta-build.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -# => Version-v10.24.1 -version="${CIRCLE_BRANCH/Version-v/}" -current_commit_msg=$(git show -s --format='%s' HEAD) - -if [[ $current_commit_msg =~ Version[[:space:]](v[[:digit:]]+.[[:digit:]]+.[[:digit:]]+[-]beta.[[:digit:]]) ]] -then - # filter the commit message like Version v10.24.1-beta.1 - printf '%s\n' "Create a build for $version with beta version $current_commit_msg" - export ENABLE_MV3=true - yarn build --build-type beta --platform='chrome' dist - yarn build --build-type beta --platform='chrome' prod -else - printf '%s\n' 'Commit message does not match commit message for beta pattern; skipping beta automation build' - mkdir dist - mkdir builds - exit 0 -fi - -exit 0 diff --git a/.circleci/scripts/validate-source-maps-beta.sh b/.circleci/scripts/validate-source-maps-beta.sh deleted file mode 100755 index 186aa1548420..000000000000 --- a/.circleci/scripts/validate-source-maps-beta.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -# => Version-v10.24.1 -current_commit_msg=$(git show -s --format='%s' HEAD) - -if [[ $current_commit_msg =~ Version[[:space:]](v[[:digit:]]+.[[:digit:]]+.[[:digit:]]+[-]beta.[[:digit:]]) ]] -then - # filter the commit message like Version v10.24.1-beta.1 - mv ./dist-beta ./dist - mv ./builds-beta ./builds - printf '%s\n' "Validate source maps with beta version $current_commit_msg" - yarn validate-source-maps -else - printf '%s\n' 'Commit message does not match commit message for beta pattern; skipping validation of beta source maps' - exit 0 -fi - -exit 0 diff --git a/.devcontainer/download-builds.ts b/.devcontainer/download-builds.ts index 83e1822379a1..5408bba8d69f 100644 --- a/.devcontainer/download-builds.ts +++ b/.devcontainer/download-builds.ts @@ -7,7 +7,7 @@ function getGitBranch() { const gitOutput = execSync('git status').toString(); const branchRegex = /On branch (?.*)\n/; - return gitOutput.match(branchRegex)?.groups?.branch || 'develop'; + return gitOutput.match(branchRegex)?.groups?.branch || 'main'; } async function getCircleJobs(branch: string) { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f37a101e6cb2..bb1055ada8f9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,6 @@ # Lines starting with '#' are comments. + +# GUIDELINES: # Each line is a file pattern followed by one or more owners. # Owners bear a responsibility to the organization and the users of this # application. Repository administrators have the ability to merge pull @@ -7,9 +9,11 @@ # follows all policies or without full understanding of the impact of # those changes on build, release and publishing outcomes. -* @MetaMask/extension-devs -development/ @MetaMask/extension-devs @kumavis -lavamoat/ @MetaMask/extension-devs @MetaMask/supply-chain +# LavaMoat policy changes can highlight security risks. Teams are encouraged to +# audit these changes on their own, and leave their analysis in a comment. +# These codeowners will review this analysis, and review the policy changes in +# further detail if warranted. +lavamoat/ @MetaMask/extension-devs @MetaMask/policy-reviewers @MetaMask/supply-chain # The offscreen.ts script file that is included in the offscreen document html # file is responsible, at present, for loading the snaps execution environment @@ -28,7 +32,7 @@ offscreen/scripts/offscreen.ts @MetaMask/snaps-devs # empower our CI steps. ANY addition of orbs to our CircleCI config # should be brought to the attention of engineering leadership for # discussion -.circleci/ @MetaMask/library-admins @kumavis +.circleci/ @MetaMask/extension-security-team # The privacy-snapshot.json file includes a list of all hosts that the # extension communicates with during the E2E test suite runs. It is not a @@ -45,11 +49,11 @@ privacy-snapshot.json @MetaMask/extension-privacy-reviewers # of contributors. Modifications to this file result in a modification of # that agreement and can only be approved by those with the knowledge # and responsibility to publish libraries under the MetaMask name. -.github/CODEOWNERS @MetaMask/library-admins @kumavis +.github/CODEOWNERS @MetaMask/extension-security-team # For now, restricting approvals inside the .devcontainer folder to devs # who were involved with the Codespaces project. -.devcontainer/ @MetaMask/library-admins @HowardBraham @plasmacorral +.devcontainer/ @MetaMask/extension-security-team @HowardBraham # Confirmations team to own code for confirmations on UI. app/scripts/lib/ppom @MetaMask/confirmations @@ -69,14 +73,11 @@ ui/selectors/institutional @MetaMask/mmi # Slack handle: @metamask-design-system-team | Slack channel: #metamask-design-system ui/components/component-library @MetaMask/design-system-engineers -# The Notifications team is responsible for code related to notifications, -# authentication, and profile syncing inside the Extension. +# The Notifications team is responsible for code related to notifications # Controllers -**/controllers/authentication/** @MetaMask/notifications **/controllers/metamask-notifications/** @MetaMask/notifications **/controllers/push-platform-notifications/** @MetaMask/notifications -**/controllers/user-storage/** @MetaMask/notifications # UI **/metamask-notifications/** @MetaMask/notifications @@ -84,15 +85,28 @@ ui/components/component-library @MetaMask/design-system-engineers **/pages/notification*/** @MetaMask/notifications **/utils/notification.util.ts @MetaMask/notifications +# Identity team is responsible for authentication and profile syncing inside the Extension. +# Slack handle: @identity_team | Slack channel: #metamask-identity +**/identity/** @MetaMask/identity +ui/pages/settings/security-tab/profile-sync-toggle @MetaMask/identity + + # Accounts team is responsible for code related with snap management accounts # Slack handle: @accounts-team-devs | Slack channel: #metamask-accounts-team app/scripts/lib/snap-keyring @MetaMask/accounts-engineers -# Swaps team to own code for the swaps folder +# Swaps-Bridge team to own code for the swaps folder ui/pages/swaps @MetaMask/swaps-engineers app/scripts/controllers/swaps @MetaMask/swaps-engineers +# Swaps-Bridge team to own code for the bridge folder +**/bridge/** @MetaMask/swaps-engineers +**/bridge-status/** @MetaMask/swaps-engineers + +# Ramps team to own code for the ramps folder +**/ramps/** @MetaMask/ramp + # Snaps **/snaps/** @MetaMask/snaps-devs shared/constants/permissions.ts @MetaMask/snaps-devs diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 894c13b90238..41d6433db7d7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -13,14 +13,14 @@ If you're picking up a bounty or an existing issue, feel free to ask clarifying ### Submitting a pull request When you're done with your project / bugfix / feature and ready to submit a PR, there are a couple guidelines we ask you to follow: -- [ ] **Make sure you followed our [`coding guidelines`](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md)**: These guidelines aim to maintain consistency and readability across the codebase. They help ensure that the code is easy to understand, maintain, and modify, which is particularly important when working with multiple contributors. +- [ ] **Make sure you followed our [`coding guidelines`](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md)**: These guidelines aim to maintain consistency and readability across the codebase. They help ensure that the code is easy to understand, maintain, and modify, which is particularly important when working with multiple contributors. - [ ] **Test it**: For any new programmatic functionality, we like unit tests when possible, so if you can keep your code cleanly isolated, please do add a test file to the `tests` folder. - [ ] **Meet the spec**: Make sure the PR adds functionality that matches the issue you're closing. This is especially important for bounties: sometimes design or implementation details are included in the conversation, so read carefully! - [ ] **Close the issue**: If this PR closes an open issue, fill out the "Related issues" section with `Fixes: #$ISSUE_NUMBER`. Ex. For closing issue 418, include the line `Fixes: #418`. If it doesn't close the issue but addresses it partially, just include a reference to the issue number, like `#418`. - [ ] **Keep it simple**: Try not to include multiple features in a single PR, and don't make extraneous changes outside the scope of your contribution. All those touched files make things harder to review ;) -- [ ] **PR against `develop`**: Submit your PR against the `develop` branch. This is where we merge new features so they get some time to receive extra testing before being pushed to `master` for production. If your PR is a hot-fix that needs to be published urgently, you may submit a PR against the `master` branch, but this PR will receive tighter scrutiny before merging. +- [ ] **PR against `main`**: Submit your PR against the `main` branch. This is where we merge new features so they get some time to receive extra testing before being pushed to `master` for production. If your PR is a hot-fix that needs to be published urgently, you may submit a PR against the `master` branch, but this PR will receive tighter scrutiny before merging. - [ ] **Get reviewed by MetaMask Internal Developers**: All PRs require 2 approvals from MetaMask Internal Developers before merging. -- [ ] **Ensure the PR is correctly labeled.**: More detail about PR labels can be found [here](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md). +- [ ] **Ensure the PR is correctly labeled.**: More detail about PR labels can be found [here](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md). - [ ] **PR Titles**: Must adhere to the [Conventional Commits specification](https://www.conventionalcommits.org) - `[optional scope]: ` - Example: `feat(parser): add ability to parse arrays` diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 2b72c66524bd..a914f61ef82b 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -57,7 +57,8 @@ body: - In production (default) - In beta - During release testing - - On the development branch + - On main branch + - On a feature branch validations: required: true - type: input diff --git a/.github/guidelines/LABELING_GUIDELINES.md b/.github/guidelines/LABELING_GUIDELINES.md index 02081367bb5e..86affe59b646 100644 --- a/.github/guidelines/LABELING_GUIDELINES.md +++ b/.github/guidelines/LABELING_GUIDELINES.md @@ -14,7 +14,8 @@ It's essential to ensure that PRs have the appropriate labels before they are co - **regression-RC-x.y.z**: This label is manually added to a bug report issue by release engineers when a bug is found during release regerssion testing phase. The `x.y.z` in the label represents the release candidate (RC) version in which the bug's been discovered. ### Optional labels: -- **regression-develop**: This label can manually be added to a bug report issue at the time of its creation if the bug is present on the development branch, i.e., `develop`, but is not yet released in production. +- **regression-main**: This label can manually be added to a bug report issue at the time of its creation if the bug is present on the development branch, i.e., `main`, but is not yet released in production. +- **feature-branch-bug**: This label can manually be added to a bug report issue at the time of its creation if the bug is present on a feature branch, i.e., before merging to `main`. - **needs-qa**: If the PR includes a new features, complex testing steps, or large refactors, this label must be added to indicated PR requires a full manual QA prior being merged and added to a release. ### Labels prohibited when PR needs to be merged: diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index 59232248ef51..044f83571fc6 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -38,11 +38,11 @@ Fixes: ## **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 followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.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. +- [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** diff --git a/.github/scripts/check-pr-has-required-labels.ts b/.github/scripts/check-pr-has-required-labels.ts index 354dc2c2aa7d..fcce4dd23a82 100644 --- a/.github/scripts/check-pr-has-required-labels.ts +++ b/.github/scripts/check-pr-has-required-labels.ts @@ -73,7 +73,7 @@ async function main(): Promise { if (!hasTeamLabel) { errorMessage += 'No team labels found on the PR. '; } - errorMessage += `Please make sure the PR is appropriately labeled before merging it.\n\nSee labeling guidelines for more detail: https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md`; + errorMessage += `Please make sure the PR is appropriately labeled before merging it.\n\nSee labeling guidelines for more detail: https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md`; core.setFailed(errorMessage); process.exit(1); } diff --git a/.github/scripts/check-template-and-add-labels.ts b/.github/scripts/check-template-and-add-labels.ts index ed22e98dc9a1..25cce0998fa7 100644 --- a/.github/scripts/check-template-and-add-labels.ts +++ b/.github/scripts/check-template-and-add-labels.ts @@ -22,7 +22,8 @@ import { TemplateType, templates } from './shared/template'; import { retrievePullRequest } from './shared/pull-request'; enum RegressionStage { - Development, + DevelopmentFeature, + DevelopmentMain, Testing, Beta, Production @@ -132,7 +133,7 @@ async function main(): Promise { } else { const errorMessage = - "Issue body does not match any of expected templates ('general-issue.yml' or 'bug-report.yml').\n\nMake sure issue's body includes all section titles.\n\nSections titles are listed here: https://github.com/MetaMask/metamask-extension/blob/develop/.github/scripts/shared/template.ts#L14-L37"; + "Issue body does not match any of expected templates ('general-issue.yml' or 'bug-report.yml').\n\nMake sure issue's body includes all section titles.\n\nSections titles are listed here: https://github.com/MetaMask/metamask-extension/blob/main/.github/scripts/shared/template.ts#L14-L37"; console.log(errorMessage); // Add label to indicate issue doesn't match any template @@ -152,7 +153,7 @@ async function main(): Promise { ); } else { const errorMessage = - `PR body does not match template ('pull-request-template.md').\n\nMake sure PR's body includes all section titles.\n\nSections titles are listed here: https://github.com/MetaMask/metamask-extension/blob/develop/.github/scripts/shared/template.ts#L40-L47`; + `PR body does not match template ('pull-request-template.md').\n\nMake sure PR's body includes all section titles.\n\nSections titles are listed here: https://github.com/MetaMask/metamask-extension/blob/main/.github/scripts/shared/template.ts#L40-L47`; console.log(errorMessage); // Add label to indicate PR body doesn't match template @@ -217,8 +218,10 @@ function extractRegressionStageFromBugReportIssueBody( const extractedAnswer = match ? match[1].trim() : undefined; switch (extractedAnswer) { - case 'On the development branch': - return RegressionStage.Development; + case 'On a feature branch': + return RegressionStage.DevelopmentFeature; + case 'On main branch': + return RegressionStage.DevelopmentMain; case 'During release testing': return RegressionStage.Testing; case 'In beta': @@ -332,11 +335,18 @@ async function userBelongsToMetaMaskOrg( // This function crafts appropriate label, corresponding to regression stage and release version. function craftRegressionLabel(regressionStage: RegressionStage | undefined, releaseVersion: string | undefined): Label { switch (regressionStage) { - case RegressionStage.Development: + case RegressionStage.DevelopmentFeature: return { - name: `regression-develop`, + name: `feature-branch-bug`, color: '5319E7', // violet - description: `Regression bug that was found on development branch, but not yet present in production`, + description: `bug that was found on a feature branch, but not yet merged in main branch`, + }; + + case RegressionStage.DevelopmentMain: + return { + name: `regression-main`, + color: '5319E7', // violet + description: `Regression bug that was found on main branch, but not yet present in production`, }; case RegressionStage.Testing: @@ -364,7 +374,7 @@ function craftRegressionLabel(regressionStage: RegressionStage | undefined, rele return { name: `regression-*`, color: 'EDEDED', // grey - description: `TODO: Unknown regression stage. Please replace with correct regression label: 'regression-develop', 'regression-RC-x.y.z', or 'regression-prod-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`, + description: `TODO: Unknown regression stage. Please replace with correct regression label: 'regression-main', 'regression-RC-x.y.z', or 'regression-prod-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`, }; } } diff --git a/.github/workflows/add-mmi-reviewer-and-notify.yml b/.github/workflows/add-mmi-reviewer-and-notify.yml index 8821ccbd36e9..8372217bce70 100644 --- a/.github/workflows/add-mmi-reviewer-and-notify.yml +++ b/.github/workflows/add-mmi-reviewer-and-notify.yml @@ -3,7 +3,7 @@ name: Notify MMI team via Slack on: pull_request_target: branches: - - develop + - main types: - opened - reopened @@ -45,4 +45,4 @@ jobs: ] } env: - SLACK_WEBHOOK_URL: ${{ secrets.MMI_LABEL_SLACK_WEBHOOK_URL }} \ No newline at end of file + SLACK_WEBHOOK_URL: ${{ secrets.MMI_LABEL_SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/add-release-label.yml b/.github/workflows/add-release-label.yml index 94ba76fcefa0..2e34ad491b44 100644 --- a/.github/workflows/add-release-label.yml +++ b/.github/workflows/add-release-label.yml @@ -3,7 +3,7 @@ name: Add release label to PR and linked issues when PR gets merged on: pull_request: branches: - - develop + - main types: - closed diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml new file mode 100644 index 000000000000..98a291edbb4f --- /dev/null +++ b/.github/workflows/build-beta.yml @@ -0,0 +1,78 @@ +name: Build beta + +on: + workflow_call: + +jobs: + build-beta: + name: Build beta + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # By default, the checkout action checks out the last merge commit for pull requests. + # Source: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request + # However, we need the head commit (the latest commit pushed to the source branch) + # because in the workflow, we would like to parse the latest commit message. + # Specifying `ref` ensures that the head commit is checked out directly. + # For a `pull_request` event, the head commit hash is `github.event.pull_request.head.sha`. + # For a `push` event, the head commit hash is `github.sha`. + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Needs beta build + # For a `pull_request` event, the branch is `github.head_ref``. + # For a `push` event, the branch is `github.ref_name`. + if: ${{ (github.head_ref || github.ref_name) != 'master' }} + id: needs-beta-build + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + version="${BRANCH/Version-v/}" + commit_message=$(git show -s --format=%s HEAD) + beta_version_regex="Version v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+" + + if [[ "$commit_message" =~ $beta_version_regex ]]; then + printf '%s\n' "Creating a build for $version with $commit_message" + echo "NEEDS_BETA_BUILD=true" >> "$GITHUB_OUTPUT" + else + printf '%s\n' 'Commit message does not match commit message for beta pattern; skipping beta build' + echo "NEEDS_BETA_BUILD=false" >> "$GITHUB_OUTPUT" + fi + + - name: Setup environment + if: ${{ steps.needs-beta-build.outputs.NEEDS_BETA_BUILD == 'true' }} + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Run beta build + if: ${{ steps.needs-beta-build.outputs.NEEDS_BETA_BUILD == 'true' }} + env: + INFURA_PROJECT_ID: 00000000000 + INFURA_BETA_PROJECT_ID: 00000000000 + SEGMENT_BETA_WRITE_KEY: 00000000000 + ENABLE_MV3: true + run: | + yarn build --build-type beta --platform='chrome' dist + yarn build --build-type beta --platform='chrome' prod + + - name: Validate source maps + if: ${{ steps.needs-beta-build.outputs.NEEDS_BETA_BUILD == 'true' }} + run: yarn validate-source-maps + + - name: Upload 'dist-beta' to S3 + if: ${{ steps.needs-beta-build.outputs.NEEDS_BETA_BUILD == 'true' }} + uses: metamask/github-tools/.github/actions/upload-s3@1233659b3850eb84824d7375e2e0c58eb237701d + with: + aws-region: ${{ vars.AWS_REGION }} + role-to-assume: ${{ vars.AWS_IAM_ROLE }} + s3-bucket: ${{ vars.AWS_S3_BUCKET }}/${{ github.event.repository.name }}/${{ github.run_id }}/dist-beta + path: dist + + - name: Upload 'builds-beta' to S3 + if: ${{ steps.needs-beta-build.outputs.NEEDS_BETA_BUILD == 'true' }} + uses: metamask/github-tools/.github/actions/upload-s3@1233659b3850eb84824d7375e2e0c58eb237701d + with: + aws-region: ${{ vars.AWS_REGION }} + role-to-assume: ${{ vars.AWS_IAM_ROLE }} + s3-bucket: ${{ vars.AWS_S3_BUCKET }}/${{ github.event.repository.name }}/${{ github.run_id }}/builds-beta + path: builds diff --git a/.github/workflows/check-attributions.yml b/.github/workflows/check-attributions.yml index 4880cc65ebe8..cadaf0d1141b 100644 --- a/.github/workflows/check-attributions.yml +++ b/.github/workflows/check-attributions.yml @@ -3,13 +3,6 @@ name: Check Attributions on: push: branches: Version-v* - pull_request: - branches: Version-v* - types: - - opened - - reopened - - synchronize - - ready_for_review jobs: check-attributions: diff --git a/.github/workflows/check-pr-labels.yml b/.github/workflows/check-pr-labels.yml index 19c0576feaae..cc492c09319e 100644 --- a/.github/workflows/check-pr-labels.yml +++ b/.github/workflows/check-pr-labels.yml @@ -2,7 +2,7 @@ name: Check PR has required labels on: pull_request: branches: - - develop + - main types: - opened - reopened diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 73670c46d75d..a4b5bef7c9e7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ develop, Version-v*, cla-signatures, master, snaps ] + branches: [ main, Version-v*, cla-signatures, master, snaps ] pull_request: # The branches below must be a subset of the branches above - branches: [ develop ] + branches: [ main ] schedule: - cron: '28 12 * * 0' diff --git a/.github/workflows/codespaces.yml b/.github/workflows/codespaces.yml index 3455e2db54d4..5d37ba2d3dc3 100644 --- a/.github/workflows/codespaces.yml +++ b/.github/workflows/codespaces.yml @@ -4,7 +4,7 @@ on: push: branches: - 'codespaces**' - - 'develop' + - 'main' paths: - '**/yarn.lock' diff --git a/.github/workflows/crowdin-action.yml b/.github/workflows/crowdin-action.yml index 94bd8016cd4f..1a902087ad61 100644 --- a/.github/workflows/crowdin-action.yml +++ b/.github/workflows/crowdin-action.yml @@ -7,7 +7,7 @@ permissions: on: push: branches: - - develop + - main schedule: - cron: "0 */12 * * *" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f3cc68bebcec..0d977c549af1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,9 @@ name: Main on: push: branches: - - develop + - main - master + - Version-v* pull_request: types: - opened @@ -13,31 +14,101 @@ on: merge_group: jobs: - check-workflows: - name: Check workflows - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + lint-workflows: + name: Lint workflows + uses: metamask/github-tools/.github/workflows/lint-workflows.yml@c534f265e02af2f2422a3c686bb09a11bfbf4cc2 + + test-lint-shellcheck: + name: Test lint shellcheck + uses: ./.github/workflows/test-lint-shellcheck.yml + + test-lint: + name: Test lint + uses: ./.github/workflows/test-lint.yml + + test-lint-changelog: + name: Test lint changelog + uses: ./.github/workflows/test-lint-changelog.yml + + test-lint-lockfile: + name: Test lint lockfile + uses: ./.github/workflows/test-lint-lockfile.yml + + test-deps-audit: + name: Test deps audit + uses: ./.github/workflows/test-deps-audit.yml + + test-yarn-dedupe: + name: Test yarn dedupe + uses: ./.github/workflows/test-yarn-dedupe.yml + + test-deps-depcheck: + name: Test deps depcheck + uses: ./.github/workflows/test-deps-depcheck.yml - - name: Download actionlint - id: download-actionlint - run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.23 - shell: bash + validate-lavamoat-allow-scripts: + name: Validate lavamoat allow scripts + uses: ./.github/workflows/validate-lavamoat-allow-scripts.yml - - name: Check workflow files - run: ${{ steps.download-actionlint.outputs.executable }} -color - shell: bash + validate-lavamoat-policy-build: + name: Validate lavamoat policy build + uses: ./.github/workflows/validate-lavamoat-policy-build.yml + + validate-lavamoat-policy-webapp: + name: Validate lavamoat policy webapp + uses: ./.github/workflows/validate-lavamoat-policy-webapp.yml run-tests: name: Run tests uses: ./.github/workflows/run-tests.yml + wait-for-circleci-workflow-status: + name: Wait for CircleCI workflow status + uses: ./.github/workflows/wait-for-circleci-workflow-status.yml + + runway: + name: Runway + # For a `pull_request` event, the branch is `github.head_ref``. + # For a `push` event, the branch is `github.ref_name`. + if: ${{ startsWith(github.head_ref || github.ref_name, 'Version-v') }} + needs: + - wait-for-circleci-workflow-status + uses: ./.github/workflows/runway.yml + + build-beta: + name: Build beta + uses: ./.github/workflows/build-beta.yml + permissions: + contents: read + id-token: write + + publish-prerelease: + name: Publish prerelease + if: ${{ github.event_name == 'pull_request' }} + needs: + - wait-for-circleci-workflow-status + - build-beta + uses: ./.github/workflows/publish-prerelease.yml + secrets: + PR_COMMENT_TOKEN: ${{ secrets.PR_COMMENT_TOKEN }} + all-jobs-completed: name: All jobs completed runs-on: ubuntu-latest needs: - - check-workflows + - lint-workflows + - test-lint-shellcheck + - test-lint + - test-lint-changelog + - test-lint-lockfile + - test-yarn-dedupe + - test-deps-depcheck + - validate-lavamoat-allow-scripts + - validate-lavamoat-policy-build + - validate-lavamoat-policy-webapp - run-tests + - wait-for-circleci-workflow-status + - build-beta outputs: PASSED: ${{ steps.set-output.outputs.PASSED }} steps: @@ -58,3 +129,16 @@ jobs: if [[ $passed != "true" ]]; then exit 1 fi + + log-merge-group-failure: + name: Log merge group failure + # Only run this job if the merge group event fails, skip on forks + if: ${{ github.event_name == 'merge_group' && failure() && !github.event.repository.fork }} + needs: + - all-jobs-pass + uses: metamask/github-tools/.github/workflows/log-merge-group-failure.yml@6bbad335a01fce1a9ec1eabd9515542c225d46c0 + secrets: + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + GOOGLE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} + SPREADSHEET_ID: ${{ secrets.GOOGLE_MERGE_QUEUE_SPREADSHEET_ID }} + SHEET_NAME: ${{ secrets.GOOGLE_MERGE_QUEUE_SHEET_NAME }} diff --git a/.github/workflows/publish-prerelease.yml b/.github/workflows/publish-prerelease.yml new file mode 100644 index 000000000000..496a61b88e4f --- /dev/null +++ b/.github/workflows/publish-prerelease.yml @@ -0,0 +1,76 @@ +name: Publish prerelease + +on: + workflow_call: + secrets: + PR_COMMENT_TOKEN: + required: true + +jobs: + publish-prerelease: + name: Publish prerelease + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # This is needed to get merge base to calculate bundle size diff + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Get merge base commit hash + id: get-merge-base + env: + BASE_REF: ${{ github.event.pull_request.base.ref }} + run: | + merge_base="$(git merge-base "origin/${BASE_REF}" HEAD)" + echo "MERGE_BASE=${merge_base}" >> "$GITHUB_OUTPUT" + echo "Merge base is '${merge_base}'" + + - name: Get CircleCI job details + id: get-circleci-job-details + env: + OWNER: ${{ github.repository_owner }} + REPOSITORY: ${{ github.event.repository.name }} + # For a `pull_request` event, the branch is `github.head_ref``. + BRANCH: ${{ github.head_ref }} + # For a `pull_request` event, the head commit hash is `github.event.pull_request.head.sha`. + HEAD_COMMIT_HASH: ${{ github.event.pull_request.head.sha }} + JOB_NAME: job-publish-prerelease + run: | + pipeline_id=$(curl --silent "https://circleci.com/api/v2/project/gh/$OWNER/$REPOSITORY/pipeline?branch=$BRANCH" | jq --arg head_commit_hash "${HEAD_COMMIT_HASH}" -r '.items | map(select(.vcs.revision == $head_commit_hash)) | first | .id') + workflow_id=$(curl --silent "https://circleci.com/api/v2/pipeline/$pipeline_id/workflow" | jq -r ".items[0].id") + job_details=$(curl --silent "https://circleci.com/api/v2/workflow/$workflow_id/job" | jq --arg job_name "${JOB_NAME}" -r '.items[] | select(.name == $job_name)') + + build_num=$(echo "$job_details" | jq -r '.job_number') + echo 'CIRCLE_BUILD_NUM='"$build_num" >> "$GITHUB_OUTPUT" + + job_id=$(echo "$job_details" | jq -r '.id') + echo 'CIRCLE_WORKFLOW_JOB_ID='"$job_id" >> "$GITHUB_OUTPUT" + + echo "Getting artifacts from pipeline '${pipeline_id}', workflow '${workflow_id}', build number '${build_num}', job id '${job_id}'" + + - name: Get CircleCI job artifacts + env: + CIRCLE_WORKFLOW_JOB_ID: ${{ steps.get-circleci-job-details.outputs.CIRCLE_WORKFLOW_JOB_ID }} + run: | + mkdir -p test-artifacts/chrome/benchmark + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/test-artifacts/chrome/benchmark/pageload.json" > "test-artifacts/chrome/benchmark/pageload.json" + + mkdir -p test-artifacts/chrome + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/test-artifacts/chrome/bundle_size.json" > "test-artifacts/chrome/bundle_size.json" + + mkdir storybook-build + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/storybook/stories.json" > "storybook-build/stories.json" + + - name: Publish prerelease + env: + PR_COMMENT_TOKEN: ${{ secrets.PR_COMMENT_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_COMMIT_HASH: ${{ github.event.pull_request.head.sha }} + MERGE_BASE_COMMIT_HASH: ${{ steps.get-merge-base.outputs.MERGE_BASE }} + CIRCLE_BUILD_NUM: ${{ steps.get-circleci-job-details.outputs.CIRCLE_BUILD_NUM }} + CIRCLE_WORKFLOW_JOB_ID: ${{ steps.get-circleci-job-details.outputs.CIRCLE_WORKFLOW_JOB_ID }} + HOST_URL: ${{ vars.AWS_CLOUDFRONT_URL }}/${{ github.event.repository.name }}/${{ github.run_id }} + run: ./development/metamaskbot-build-announce.js diff --git a/.github/workflows/runway.yml b/.github/workflows/runway.yml new file mode 100644 index 000000000000..98f738c69154 --- /dev/null +++ b/.github/workflows/runway.yml @@ -0,0 +1,71 @@ +name: Runway + +on: + workflow_call: + +jobs: + runway: + name: Runway + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get build version + id: get-build-version + run: | + build_version=$(jq -r '.version' package.json) + echo 'BUILD_VERSION='"$build_version" >> "$GITHUB_OUTPUT" + echo "Build version is '${build_version}'" + + - name: Get CircleCI job details + id: get-circleci-job-details + env: + OWNER: ${{ github.repository_owner }} + REPOSITORY: ${{ github.event.repository.name }} + # For a `pull_request` event, the branch is `github.head_ref``. + # For a `push` event, the branch is `github.ref_name`. + BRANCH: ${{ github.head_ref || github.ref_name }} + # For a `pull_request` event, the head commit hash is `github.event.pull_request.head.sha`. + # For a `push` event, the head commit hash is `github.sha`. + HEAD_COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }} + JOB_NAME: job-publish-prerelease + run: | + pipeline_id=$(curl --silent "https://circleci.com/api/v2/project/gh/$OWNER/$REPOSITORY/pipeline?branch=$BRANCH" | jq --arg head_commit_hash "${HEAD_COMMIT_HASH}" -r '.items | map(select(.vcs.revision == $head_commit_hash)) | first | .id') + workflow_id=$(curl --silent "https://circleci.com/api/v2/pipeline/$pipeline_id/workflow" | jq -r ".items[0].id") + job_details=$(curl --silent "https://circleci.com/api/v2/workflow/$workflow_id/job" | jq --arg job_name "${JOB_NAME}" -r '.items[] | select(.name == $job_name)') + + job_id=$(echo "$job_details" | jq -r '.id') + echo 'CIRCLE_WORKFLOW_JOB_ID='"$job_id" >> "$GITHUB_OUTPUT" + + echo "Getting artifacts from pipeline '${pipeline_id}', workflow '${workflow_id}', job id '${job_id}'" + + - name: Download builds from CircleCI + env: + BUILD_VERSION: ${{ steps.get-build-version.outputs.BUILD_VERSION }} + CIRCLE_WORKFLOW_JOB_ID: ${{ steps.get-circleci-job-details.outputs.CIRCLE_WORKFLOW_JOB_ID }} + run: | + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/builds/metamask-chrome-${BUILD_VERSION}.zip" > "metamask-chrome-${BUILD_VERSION}.zip" + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/builds-mv2/metamask-firefox-${BUILD_VERSION}.zip" > "metamask-firefox-${BUILD_VERSION}.zip" + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/builds-flask/metamask-flask-chrome-${BUILD_VERSION}-flask.0.zip" > "metamask-flask-chrome-${BUILD_VERSION}-flask.0.zip" + curl --silent --location "https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0/builds-flask-mv2/metamask-flask-firefox-${BUILD_VERSION}-flask.0.zip" > "metamask-flask-firefox-${BUILD_VERSION}-flask.0.zip" + + - uses: actions/upload-artifact@v4 + with: + name: metamask-chrome-${{ steps.get-build-version.outputs.BUILD_VERSION }} + path: metamask-chrome-${{ steps.get-build-version.outputs.BUILD_VERSION }}.zip + + - uses: actions/upload-artifact@v4 + with: + name: metamask-firefox-${{ steps.get-build-version.outputs.BUILD_VERSION }} + path: metamask-firefox-${{ steps.get-build-version.outputs.BUILD_VERSION }}.zip + + - uses: actions/upload-artifact@v4 + with: + name: metamask-flask-chrome-${{ steps.get-build-version.outputs.BUILD_VERSION }}-flask.0 + path: metamask-flask-chrome-${{ steps.get-build-version.outputs.BUILD_VERSION }}-flask.0.zip + + - uses: actions/upload-artifact@v4 + with: + name: metamask-flask-firefox-${{ steps.get-build-version.outputs.BUILD_VERSION }}-flask.0 + path: metamask-flask-firefox-${{ steps.get-build-version.outputs.BUILD_VERSION }}-flask.0.zip diff --git a/.github/workflows/security-code-scanner.yml b/.github/workflows/security-code-scanner.yml index 7da1773d666c..cf0b60cf84ac 100644 --- a/.github/workflows/security-code-scanner.yml +++ b/.github/workflows/security-code-scanner.yml @@ -2,9 +2,9 @@ name: "MetaMask Security Code Scanner" on: push: - branches: [ 'develop' ] + branches: [ 'main' ] pull_request: - branches: [ 'develop' ] + branches: [ 'main' ] jobs: run-security-scan: diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 8b12876de1cd..9c8681bc8824 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -3,6 +3,9 @@ # actions that may result in code from that branch being executed # such as installing dependencies or running build scripts. +# For security reasons, this file always uses the latest version from the default branch. +# Changes made in feature branches or forks will not take effect until they are merged. + name: SonarCloud on: @@ -17,9 +20,9 @@ permissions: jobs: sonarcloud: - # Only scan code from non-forked repositories that have passed the tests + # Only scan code from non-forked repositories that have completed successfully using the push and pull_request events # This will skip scanning the code for forks, but will run for the main repository on PRs from forks - if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.repository.fork == false }} + if: ${{ github.event.workflow_run.conclusion == 'success' && contains(fromJSON('["push", "pull_request"]'), github.event.workflow_run.event) && !github.event.workflow_run.repository.fork }} name: SonarCloud runs-on: ubuntu-latest steps: @@ -50,6 +53,23 @@ jobs: fi echo "$sonar_project_properties" > sonar-project.properties + - name: Update sonar-project.properties with branch information + env: + EVENT: ${{ github.event.workflow_run.event }} + HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + BASE_BRANCH: ${{ github.event.workflow_run.pull_requests[0].base.ref || '' }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number || '' }} + run: | + if [ "$EVENT" == "pull_request" ]; then + { + echo "sonar.pullrequest.branch=$HEAD_BRANCH" + echo "sonar.pullrequest.base=$BASE_BRANCH" + echo "sonar.pullrequest.key=$PR_NUMBER" + } >> sonar-project.properties + else + echo "sonar.branch.name=$HEAD_BRANCH" >> sonar-project.properties + fi + - name: SonarCloud Scan # This is SonarSource/sonarcloud-github-action@v2.0.0 uses: SonarSource/sonarcloud-github-action@4b4d7634dab97dcee0b75763a54a6dc92a9e6bc1 diff --git a/.github/workflows/test-deps-audit.yml b/.github/workflows/test-deps-audit.yml new file mode 100644 index 000000000000..271746da2429 --- /dev/null +++ b/.github/workflows/test-deps-audit.yml @@ -0,0 +1,18 @@ +name: Test deps audit + +on: + workflow_call: + +jobs: + test-deps-audit: + name: Test deps audit + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Run audit + run: yarn audit diff --git a/.github/workflows/test-deps-depcheck.yml b/.github/workflows/test-deps-depcheck.yml new file mode 100644 index 000000000000..3860c485f25b --- /dev/null +++ b/.github/workflows/test-deps-depcheck.yml @@ -0,0 +1,18 @@ +name: Test deps depcheck + +on: + workflow_call: + +jobs: + test-deps-depcheck: + name: Test deps depcheck + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Run depcheck + run: yarn depcheck diff --git a/.github/workflows/test-lint-changelog.yml b/.github/workflows/test-lint-changelog.yml new file mode 100644 index 000000000000..9e8ee0e992c8 --- /dev/null +++ b/.github/workflows/test-lint-changelog.yml @@ -0,0 +1,27 @@ +name: Test lint changelog + +on: + workflow_call: + +jobs: + test-lint-changelog: + name: Test lint changelog + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Validate changelog + # For a `pull_request` event, the branch is `github.head_ref``. + # For a `push` event, the branch is `github.ref_name`. + if: ${{ !startsWith(github.head_ref || github.ref_name, 'Version-v') }} + run: yarn lint:changelog + + - name: Validate release candidate changelog + # For a `pull_request` event, the branch is `github.head_ref``. + # For a `push` event, the branch is `github.ref_name`. + if: ${{ startsWith(github.head_ref || github.ref_name, 'Version-v') }} + run: .circleci/scripts/validate-changelog-in-rc.sh diff --git a/.github/workflows/test-lint-lockfile.yml b/.github/workflows/test-lint-lockfile.yml new file mode 100644 index 000000000000..cc84318624ce --- /dev/null +++ b/.github/workflows/test-lint-lockfile.yml @@ -0,0 +1,21 @@ +name: Test lint lockfile + +on: + workflow_call: + +jobs: + test-lint-lockfile: + name: Test lint lockfile + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Lint lockfile + run: yarn lint:lockfile + + - name: Check yarn resolutions + run: yarn --check-resolutions diff --git a/.github/workflows/test-lint-shellcheck.yml b/.github/workflows/test-lint-shellcheck.yml new file mode 100644 index 000000000000..c4127902a2f4 --- /dev/null +++ b/.github/workflows/test-lint-shellcheck.yml @@ -0,0 +1,15 @@ +name: Test lint shellcheck + +on: + workflow_call: + +jobs: + test-lint-shellcheck: + name: Test lint shellcheck + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: ShellCheck Lint + run: ./development/shellcheck.sh diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml new file mode 100644 index 000000000000..df40a3a7ef27 --- /dev/null +++ b/.github/workflows/test-lint.yml @@ -0,0 +1,21 @@ +name: Test lint + +on: + workflow_call: + +jobs: + test-lint: + name: Test lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Lint + run: yarn lint + + - name: Verify locales + run: yarn verify-locales --quiet diff --git a/.github/workflows/test-yarn-dedupe.yml b/.github/workflows/test-yarn-dedupe.yml new file mode 100644 index 000000000000..40bda1dfb3d2 --- /dev/null +++ b/.github/workflows/test-yarn-dedupe.yml @@ -0,0 +1,18 @@ +name: Test yarn dedupe + +on: + workflow_call: + +jobs: + test-yarn-dedupe: + name: Test yarn dedupe + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Detect yarn lock deduplications + run: yarn dedupe --check diff --git a/.github/workflows/update-coverage.yml b/.github/workflows/update-coverage.yml index fd1b0d5134e3..e65cdcbe978b 100644 --- a/.github/workflows/update-coverage.yml +++ b/.github/workflows/update-coverage.yml @@ -43,4 +43,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} run: | - gh pr create --title "chore: Update coverage.json" --body "This PR is automatically opened to update the coverage.json file when test coverage increases. Coverage increased from $STORED_COVERAGE% to $CURRENT_COVERAGE%." --base develop --head metamaskbot/update-coverage || gh pr edit --body "This PR is automatically opened to update the coverage.json file when test coverage increases. Coverage increased from $STORED_COVERAGE% to $CURRENT_COVERAGE%." + gh pr create --title "chore: Update coverage.json" --body "This PR is automatically opened to update the coverage.json file when test coverage increases. Coverage increased from $STORED_COVERAGE% to $CURRENT_COVERAGE%." --base main --head metamaskbot/update-coverage || gh pr edit --body "This PR is automatically opened to update the coverage.json file when test coverage increases. Coverage increased from $STORED_COVERAGE% to $CURRENT_COVERAGE%." diff --git a/.github/workflows/validate-conventional-commits.yml b/.github/workflows/validate-conventional-commits.yml index 8cb416844339..b8b5c56eabb6 100644 --- a/.github/workflows/validate-conventional-commits.yml +++ b/.github/workflows/validate-conventional-commits.yml @@ -2,7 +2,7 @@ name: Validate Conventional Commit Title on: pull_request: branches: - - develop + - main types: [opened, edited, reopened, synchronize] jobs: diff --git a/.github/workflows/validate-lavamoat-allow-scripts.yml b/.github/workflows/validate-lavamoat-allow-scripts.yml new file mode 100644 index 000000000000..637a2d9aeb54 --- /dev/null +++ b/.github/workflows/validate-lavamoat-allow-scripts.yml @@ -0,0 +1,25 @@ +name: Validate lavamoat allow scripts + +on: + workflow_call: + +jobs: + validate-lavamoat-allow-scripts: + name: Validate lavamoat allow scripts + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Validate allow-scripts config + run: yarn allow-scripts auto + + - name: Check working tree + run: | + if ! git diff --exit-code; then + echo "::error::Working tree dirty." + exit 1 + fi diff --git a/.github/workflows/validate-lavamoat-policy-build.yml b/.github/workflows/validate-lavamoat-policy-build.yml new file mode 100644 index 000000000000..4524cc26a546 --- /dev/null +++ b/.github/workflows/validate-lavamoat-policy-build.yml @@ -0,0 +1,27 @@ +name: Validate lavamoat policy build + +on: + workflow_call: + +jobs: + validate-lavamoat-policy-build: + name: Validate lavamoat policy build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Validate lavamoat build policy + run: yarn lavamoat:build:auto + env: + INFURA_PROJECT_ID: 00000000000 + + - name: Check working tree + run: | + if ! git diff --exit-code; then + echo "::error::Working tree dirty." + exit 1 + fi diff --git a/.github/workflows/validate-lavamoat-policy-webapp.yml b/.github/workflows/validate-lavamoat-policy-webapp.yml new file mode 100644 index 000000000000..37ff9ede00fc --- /dev/null +++ b/.github/workflows/validate-lavamoat-policy-webapp.yml @@ -0,0 +1,30 @@ +name: Validate lavamoat policy webapp + +on: + workflow_call: + +jobs: + validate-lavamoat-policy-webapp: + name: Validate lavamoat policy webapp + runs-on: ubuntu-latest + strategy: + matrix: + build-type: [main, beta, flask, mmi] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Validate lavamoat ${{ matrix.build-type }} policy + run: yarn lavamoat:webapp:auto:ci --build-types=${{ matrix.build-type }} + env: + INFURA_PROJECT_ID: 00000000000 + + - name: Check working tree + run: | + if ! git diff --exit-code; then + echo "::error::Working tree dirty." + exit 1 + fi diff --git a/.github/workflows/wait-for-circleci-workflow-status.yml b/.github/workflows/wait-for-circleci-workflow-status.yml new file mode 100644 index 000000000000..30efc6d35776 --- /dev/null +++ b/.github/workflows/wait-for-circleci-workflow-status.yml @@ -0,0 +1,36 @@ +name: Wait for CircleCI workflow status + +on: + workflow_call: + +jobs: + wait-for-circleci-workflow-status: + name: Wait for CircleCI workflow status + runs-on: ubuntu-latest + steps: + - name: Wait for CircleCI workflow status + env: + OWNER: ${{ github.repository_owner }} + REPOSITORY: ${{ github.event.repository.name }} + # For a `pull_request` event, the branch is `github.head_ref``. + # For a `push` event, the branch is `github.ref_name`. + BRANCH: ${{ github.head_ref || github.ref_name }} + # For a `pull_request` event, the head commit hash is `github.event.pull_request.head.sha`. + # For a `push` event, the head commit hash is `github.sha`. + HEAD_COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + pipeline_id=$(curl --silent "https://circleci.com/api/v2/project/gh/$OWNER/$REPOSITORY/pipeline?branch=$BRANCH" | jq --arg head_commit_hash "${HEAD_COMMIT_HASH}" -r '.items | map(select(.vcs.revision == $head_commit_hash)) | first | .id') + echo "Waiting for pipeline '${pipeline_id}', commit hash '${HEAD_COMMIT_HASH}'" + workflow_status=$(curl --silent "https://circleci.com/api/v2/pipeline/$pipeline_id/workflow" | jq -r ".items[0].status") + + if [ "$workflow_status" == "running" ]; then + while [ "$workflow_status" == "running" ]; do + sleep 30 + workflow_status=$(curl --silent "https://circleci.com/api/v2/pipeline/$pipeline_id/workflow" | jq -r ".items[0].status") + done + fi + + if [ "$workflow_status" != "success" ]; then + echo "::error::Workflow status is '$workflow_status'. Exiting with error." + exit 1 + fi diff --git a/.gitignore b/.gitignore index 074f4076a7cc..6ee150dd8653 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.yalc +yalc.lock + npm-debug.log yarn-error.log node_modules diff --git a/.nvmrc b/.nvmrc index 8cfab175cf90..bd67975ba627 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.17 +v20.18 diff --git a/.prettierignore b/.prettierignore index d8d8cfe4a15c..6f500515e7c6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,7 +6,6 @@ node_modules/**/* /app/vendor/** /builds/**/* /coverage/**/* -/development/charts/** /development/chromereload.js /development/ts-migration-dashboard/filesToConvert.json /development/ts-migration-dashboard/build/** diff --git a/.storybook/index.css b/.storybook/index.css new file mode 100644 index 000000000000..48ebae78fe46 --- /dev/null +++ b/.storybook/index.css @@ -0,0 +1,18 @@ +/* Fixes for any styles that are not compatible with Storybook */ + +.create-snap-account-page, .remove-snap-account-page, .snap-ui-renderer { + width: 100% !important; +} + +.snap-ui-renderer__footer-centered { + position: initial !important; + margin-top: auto !important; +} + +.snap-ui-renderer__container { + padding-bottom: 0 !important; +} + +.snap-ui-renderer__panel { + overflow-y: auto !important; +} diff --git a/.storybook/preview.js b/.storybook/preview.js index 525c364f2072..fc23daec5533 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -21,6 +21,7 @@ import { metamaskStorybookTheme } from './metamask-storybook-theme'; import { DocsContainer } from '@storybook/addon-docs'; import { themes } from '@storybook/theming'; import { AlertMetricsProvider } from '../ui/components/app/alert-system/contexts/alertMetricsContext'; +import './index.css'; // eslint-disable-next-line /* @ts-expect-error: Avoids error from window property not existing */ @@ -185,7 +186,6 @@ const withColorScheme = (Story, context) => {
nativeSymbolOverrides.get(nativeCurrency) ?? nativeCurrency); ++ const url = getMultiPricingURL(fsyms, [fiatCurrency], includeUSDRate); + const response = await (0, controller_utils_1.handleFetch)(url); + handleErrorResponse(response); + const rates = {}; + for (const [cryptocurrency, values] of Object.entries(response)) { +- rates[cryptocurrency.toLowerCase()] = { ++ const key = (0, assetsUtil_1.getKeyByValue)(nativeSymbolOverrides, cryptocurrency); ++ rates[key?.toLowerCase() ?? cryptocurrency.toLowerCase()] = { + [fiatCurrency.toLowerCase()]: values[fiatCurrency.toUpperCase()], + ...(includeUSDRate && { usd: values.USD }), + }; +diff --git a/dist/crypto-compare-service/crypto-compare.mjs b/dist/crypto-compare-service/crypto-compare.mjs +index 58db2280159669c1b48fb94a9164b8e0be2850a7..74e0560718d42202e9183ff7ff1bfdac1424506e 100644 +--- a/dist/crypto-compare-service/crypto-compare.mjs ++++ b/dist/crypto-compare-service/crypto-compare.mjs +@@ -1,6 +1,7 @@ + import { handleFetch } from "@metamask/controller-utils"; ++import { getKeyByValue } from "../assetsUtil.mjs"; + /** + * A map from native currency symbol to CryptoCompare identifier. + * This is only needed when the values don't match. + */ + const nativeSymbolOverrides = new Map([ +@@ -101,12 +101,14 @@ export async function fetchExchangeRate(currency, nativeCurrency, includeUSDRate + * @returns Promise resolving to exchange rates for given currencies. + */ + export async function fetchMultiExchangeRate(fiatCurrency, cryptocurrencies, includeUSDRate) { +- const url = getMultiPricingURL(cryptocurrencies, [fiatCurrency], includeUSDRate); ++ const fsyms = cryptocurrencies.map((nativeCurrency) => nativeSymbolOverrides.get(nativeCurrency) ?? nativeCurrency); ++ const url = getMultiPricingURL(fsyms, [fiatCurrency], includeUSDRate); + const response = await handleFetch(url); + handleErrorResponse(response); + const rates = {}; + for (const [cryptocurrency, values] of Object.entries(response)) { +- rates[cryptocurrency.toLowerCase()] = { ++ const key = getKeyByValue(nativeSymbolOverrides, cryptocurrency); ++ rates[key?.toLowerCase() ?? cryptocurrency.toLowerCase()] = { + [fiatCurrency.toLowerCase()]: values[fiatCurrency.toUpperCase()], + ...(includeUSDRate && { usd: values.USD }), + }; diff --git a/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch b/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch new file mode 100644 index 000000000000..02e6d3f694e5 --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch @@ -0,0 +1,61 @@ +diff --git a/dist/multicall.cjs b/dist/multicall.cjs +index bf9aa5e86573fc1651f421cc0b64f5af121c3ab2..43a0531ed86cd3ee1774dcda3f990dd40f7f52de 100644 +--- a/dist/multicall.cjs ++++ b/dist/multicall.cjs +@@ -342,9 +342,22 @@ const multicallOrFallback = async (calls, chainId, provider, maxCallsPerMultical + return []; + } + const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; +- return await (multicallAddress +- ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) +- : fallback(calls, maxCallsParallel)); ++ if (multicallAddress) { ++ try { ++ return await multicall(calls, multicallAddress, provider, maxCallsPerMulticall); ++ } ++ catch (error) { ++ // Fallback only on revert ++ // https://docs.ethers.org/v5/troubleshooting/errors/#help-CALL_EXCEPTION ++ if (!error || ++ typeof error !== 'object' || ++ !('code' in error) || ++ error.code !== 'CALL_EXCEPTION') { ++ throw error; ++ } ++ } ++ } ++ return await fallback(calls, maxCallsParallel); + }; + exports.multicallOrFallback = multicallOrFallback; + //# sourceMappingURL=multicall.cjs.map +\ No newline at end of file +diff --git a/dist/multicall.mjs b/dist/multicall.mjs +index 8fbe0112303d5df1d868e0357a9d31e43a3b6cf9..860dfdbddd813659cb2be5f7faed5d4016db5966 100644 +--- a/dist/multicall.mjs ++++ b/dist/multicall.mjs +@@ -339,8 +339,21 @@ export const multicallOrFallback = async (calls, chainId, provider, maxCallsPerM + return []; + } + const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; +- return await (multicallAddress +- ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) +- : fallback(calls, maxCallsParallel)); ++ if (multicallAddress) { ++ try { ++ return await multicall(calls, multicallAddress, provider, maxCallsPerMulticall); ++ } ++ catch (error) { ++ // Fallback only on revert ++ // https://docs.ethers.org/v5/troubleshooting/errors/#help-CALL_EXCEPTION ++ if (!error || ++ typeof error !== 'object' || ++ !('code' in error) || ++ error.code !== 'CALL_EXCEPTION') { ++ throw error; ++ } ++ } ++ } ++ return await fallback(calls, maxCallsParallel); + }; + //# sourceMappingURL=multicall.mjs.map +\ No newline at end of file diff --git a/.yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch b/.yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch deleted file mode 100644 index e82feb182c3a..000000000000 --- a/.yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/dist/wallet.js b/dist/wallet.js -index fce8272ab926443df4c5971c811664f849791425..9237ffcaaea2260e01182feecec667b10edd35a0 100644 ---- a/dist/wallet.js -+++ b/dist/wallet.js -@@ -293,7 +293,7 @@ exports.createWalletMiddleware = createWalletMiddleware; - */ - function validateVerifyingContract(data) { - const { domain: { verifyingContract } = {} } = (0, normalize_1.parseTypedMessage)(data); -- if (verifyingContract && !(0, utils_1.isValidHexAddress)(verifyingContract)) { -+ if (verifyingContract && verifyingContract !== 'cosmos' && !(0, utils_1.isValidHexAddress)(verifyingContract)) { - throw rpc_errors_1.rpcErrors.invalidInput(); - } - } diff --git a/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch b/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch deleted file mode 100644 index 5719ae0284f7..000000000000 --- a/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch +++ /dev/null @@ -1,33 +0,0 @@ -diff --git a/PATCH.txt b/PATCH.txt -new file mode 100644 -index 0000000000000000000000000000000000000000..78b9156dc2b0bf7c33dadf325cb3ec0bfae71ccb ---- /dev/null -+++ b/PATCH.txt -@@ -0,0 +1,3 @@ -+We remove `lookupNetwork` from `initializeProvider` in the network controller to prevent network requests before user onboarding is completed. -+The network lookup is done after onboarding is completed, and when the extension reloads if onboarding has been completed. -+This patch is part of a temporary fix that will be reverted soon to make way for a more permanent solution. https://github.com/MetaMask/metamask-extension/pull/23005 -diff --git a/dist/chunk-BV3ZGWII.mjs b/dist/chunk-BV3ZGWII.mjs -index 0d1bf3b6348ad4ec7a799083fcadf36f9fc74851..48a09c6e474da9c18115bec88130a88888337044 100644 ---- a/dist/chunk-BV3ZGWII.mjs -+++ b/dist/chunk-BV3ZGWII.mjs -@@ -468,7 +468,6 @@ var NetworkController = class extends BaseController { - */ - async initializeProvider() { - __privateMethod(this, _applyNetworkSelection, applyNetworkSelection_fn).call(this, this.state.selectedNetworkClientId); -- await this.lookupNetwork(); - } - /** - * Refreshes the network meta with EIP-1559 support and the network status -diff --git a/dist/chunk-YOHMQPGM.js b/dist/chunk-YOHMQPGM.js -index ff15cd78ef90b35f86aae9dc64d17d1d2efe352d..14a8bba39c204585164dfb252d0a183844a58d63 100644 ---- a/dist/chunk-YOHMQPGM.js -+++ b/dist/chunk-YOHMQPGM.js -@@ -468,7 +468,6 @@ var NetworkController = class extends _basecontroller.BaseController { - */ - async initializeProvider() { - _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _applyNetworkSelection, applyNetworkSelection_fn).call(this, this.state.selectedNetworkClientId); -- await this.lookupNetwork(); - } - /** - * Refreshes the network meta with EIP-1559 support and the network status diff --git a/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch b/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch new file mode 100644 index 000000000000..a1a3e7401c0c --- /dev/null +++ b/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch @@ -0,0 +1,34 @@ +diff --git a/PATCH.txt b/PATCH.txt +new file mode 100644 +index 0000000000000000000000000000000000000000..376255036ca54b83a3f3c3e0277c2666473df30e +--- /dev/null ++++ b/PATCH.txt +@@ -0,0 +1,4 @@ ++We remove lookupNetwork from initializeProvider in the network controller to prevent network requests before user onboarding is completed. ++The network lookup is done after onboarding is completed, and when the extension reloads if onboarding has been completed. ++This patch is part of a temporary fix that will be reverted soon to make way for a more permanent solution. https://github.com/MetaMask/metamask-extension/pull/23005 ++You can see the changes before compilation on this branch: https://github.com/MetaMask/core/compare/pnf/ext-23622-review?expand=1 +diff --git a/dist/NetworkController.cjs b/dist/NetworkController.cjs +index cc9793f576eb39a51ab141b7d03de57cf99e5570..184153067f2bbd58ea76d7db33c2af56245cd8c0 100644 +--- a/dist/NetworkController.cjs ++++ b/dist/NetworkController.cjs +@@ -422,7 +422,6 @@ class NetworkController extends base_controller_1.BaseController { + */ + async initializeProvider() { + __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_applyNetworkSelection).call(this, this.state.selectedNetworkClientId); +- await this.lookupNetwork(); + } + /** + * Refreshes the network meta with EIP-1559 support and the network status +diff --git a/dist/NetworkController.mjs b/dist/NetworkController.mjs +index 806f32edeffaad9f7eb1cafa4184368ec95f63e7..7ba60e613ec8de7d273c32282be564f36873cbd2 100644 +--- a/dist/NetworkController.mjs ++++ b/dist/NetworkController.mjs +@@ -397,7 +397,6 @@ export class NetworkController extends BaseController { + */ + async initializeProvider() { + __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_applyNetworkSelection).call(this, this.state.selectedNetworkClientId); +- await this.lookupNetwork(); + } + /** + * Refreshes the network meta with EIP-1559 support and the network status diff --git a/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch b/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch deleted file mode 100644 index fb9a5c1ef5f6..000000000000 --- a/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/dist/NonceTracker.js b/dist/NonceTracker.js -index 7cfa1e1962c930a425b3dbf6e1520450f0bf1747..2f4ff77a678fe0501e96a92d16f63a8e5f299401 100644 ---- a/dist/NonceTracker.js -+++ b/dist/NonceTracker.js -@@ -12,7 +12,6 @@ class NonceTracker { - constructor(opts) { - this.provider = opts.provider; - this.blockTracker = opts.blockTracker; -- this.web3 = new Web3Provider(opts.provider); - this.getPendingTransactions = opts.getPendingTransactions; - this.getConfirmedTransactions = opts.getConfirmedTransactions; - this.lockMap = {}; -@@ -96,7 +95,7 @@ class NonceTracker { - // we need to make sure our base count - // and pending count are from the same block - const blockNumber = await this.blockTracker.getLatestBlock(); -- const baseCount = await this.web3.getTransactionCount(address, blockNumber); -+ const baseCount = await new Web3Provider(this.provider).getTransactionCount(address, blockNumber); - assert_1.default(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`); - return { - name: 'network', -diff --git a/dist/NonceTracker.js.map b/dist/NonceTracker.js.map -index 70e16afc468187dddb2ba1a6752e60c0aadb0f82..0e9d43aed1f7c5ccc2e16f91318aaab88da104ea 100644 ---- a/dist/NonceTracker.js.map -+++ b/dist/NonceTracker.js.map -@@ -1 +1 @@ --{"version":3,"file":"NonceTracker.js","sourceRoot":"","sources":["../src/NonceTracker.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,6CAAoC;AAGpC,qGAAqG;AACrG,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;AAqF7D,MAAa,YAAY;IAavB,YAAY,IAAyB;QACnC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,sBAAsB,CAAC;QAC1D,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,wBAAwB,CAAC;QAC9D,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,0BAA0B;QAC1B,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,OAAO,EAAE,WAAW,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,OAAe;QAChC,0BAA0B;QAC1B,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9B,kCAAkC;QAClC,MAAM,WAAW,GAAiB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI;YACF,yCAAyC;YACzC,MAAM,kBAAkB,GACtB,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YAC3C,MAAM,uBAAuB,GAC3B,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,gBAAgB,GAAW,kBAAkB,CAAC,KAAK,CAAC;YAC1D,MAAM,gBAAgB,GAAW,IAAI,CAAC,GAAG,CACvC,gBAAgB,EAChB,uBAAuB,CACxB,CAAC;YAEF,MAAM,UAAU,GAAkB,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACvE,MAAM,gBAAgB,GACpB,IAAI,CAAC,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;YAE/D,MAAM,YAAY,GAAiB;gBACjC,MAAM,EAAE;oBACN,uBAAuB;oBACvB,gBAAgB;oBAChB,gBAAgB;iBACjB;gBACD,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,kBAAkB;aAC5B,CAAC;YAEF,MAAM,SAAS,GAAW,IAAI,CAAC,GAAG,CAChC,kBAAkB,CAAC,KAAK,EACxB,gBAAgB,CAAC,KAAK,CACvB,CAAC;YACF,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;YAEF,8BAA8B;YAC9B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;SACjD;QAAC,OAAO,GAAG,EAAE;YACZ,wCAAwC;YACxC,WAAW,EAAE,CAAC;YACd,MAAM,GAAG,CAAC;SACX;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,WAAW,EAAE,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,MAAM,KAAK,GAAU,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAiB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACxD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,IAAI,KAAK,GAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE;YACV,KAAK,GAAG,IAAI,mBAAK,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;SAC9B;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,oBAAoB,CAAC,OAAe;QACxC,uBAAuB;QACvB,sCAAsC;QACtC,4CAA4C;QAC5C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAW,MAAM,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAC3D,OAAO,EACP,WAAW,CACZ,CAAC;QACF,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;QACF,OAAO;YACL,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;SACpC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,2BAA2B,CAAC,OAAe;QACzC,MAAM,qBAAqB,GACzB,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,OAAO,GAAW,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,MAAqB;QACpC,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,YAAY,GAAW,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,yBAAyB,CACvB,MAAqB,EACrB,UAAkB;QAElB,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,GAAW,UAAU,CAAC;QACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;YAC/B,OAAO,IAAI,CAAC,CAAC;SACd;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;IAC7E,CAAC;CACF;AA3LD,oCA2LC"} -\ No newline at end of file -+{"version":3,"file":"NonceTracker.js","sourceRoot":"","sources":["../src/NonceTracker.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,6CAAoC;AAGpC,qGAAqG;AACrG,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;AAqF7D,MAAa,YAAY;IAavB,YAAY,IAAyB;QACnC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,sBAAsB,CAAC;QAC1D,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,wBAAwB,CAAC;QAC9D,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,0BAA0B;QAC1B,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,OAAO,EAAE,WAAW,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,OAAe;QAChC,0BAA0B;QAC1B,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9B,kCAAkC;QAClC,MAAM,WAAW,GAAiB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI;YACF,yCAAyC;YACzC,MAAM,kBAAkB,GACtB,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YAC3C,MAAM,uBAAuB,GAC3B,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,gBAAgB,GAAW,kBAAkB,CAAC,KAAK,CAAC;YAC1D,MAAM,gBAAgB,GAAW,IAAI,CAAC,GAAG,CACvC,gBAAgB,EAChB,uBAAuB,CACxB,CAAC;YAEF,MAAM,UAAU,GAAkB,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACvE,MAAM,gBAAgB,GACpB,IAAI,CAAC,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;YAE/D,MAAM,YAAY,GAAiB;gBACjC,MAAM,EAAE;oBACN,uBAAuB;oBACvB,gBAAgB;oBAChB,gBAAgB;iBACjB;gBACD,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,kBAAkB;aAC5B,CAAC;YAEF,MAAM,SAAS,GAAW,IAAI,CAAC,GAAG,CAChC,kBAAkB,CAAC,KAAK,EACxB,gBAAgB,CAAC,KAAK,CACvB,CAAC;YACF,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;YAEF,8BAA8B;YAC9B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;SACjD;QAAC,OAAO,GAAG,EAAE;YACZ,wCAAwC;YACxC,WAAW,EAAE,CAAC;YACd,MAAM,GAAG,CAAC;SACX;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,WAAW,EAAE,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,MAAM,KAAK,GAAU,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAiB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACxD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,IAAI,KAAK,GAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE;YACV,KAAK,GAAG,IAAI,mBAAK,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;SAC9B;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,oBAAoB,CAAC,OAAe;QACxC,uBAAuB;QACvB,sCAAsC;QACtC,4CAA4C;QAC5C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAW,MAAM,IAAI,YAAY,CAC9C,IAAI,CAAC,QAAQ,CACd,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;QACF,OAAO;YACL,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;SACpC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,2BAA2B,CAAC,OAAe;QACzC,MAAM,qBAAqB,GACzB,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,OAAO,GAAW,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,MAAqB;QACpC,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,YAAY,GAAW,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,yBAAyB,CACvB,MAAqB,EACrB,UAAkB;QAElB,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,GAAW,UAAU,CAAC;QACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;YAC/B,OAAO,IAAI,CAAC,CAAC;SACd;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;IAC7E,CAAC;CACF;AAzLD,oCAyLC"} -\ No newline at end of file diff --git a/.yarn/patches/@metamask-ppom-validator-npm-0.32.0-f677deea54.patch b/.yarn/patches/@metamask-ppom-validator-npm-0.32.0-f677deea54.patch deleted file mode 100644 index d996c0bdd022..000000000000 --- a/.yarn/patches/@metamask-ppom-validator-npm-0.32.0-f677deea54.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/dist/ppom-controller.js b/dist/ppom-controller.js -index 9cf1502efabec00b25ad381bf2001200ccc9f34f..bfe55b6e68989f794deab069e8b80fc8d719ec25 100644 ---- a/dist/ppom-controller.js -+++ b/dist/ppom-controller.js -@@ -203,7 +203,9 @@ async function _PPOMController_initialisePPOM() { - console.error(`Error in deleting files: ${error.message}`); - }); - }, _PPOMController_onNetworkChange = function _PPOMController_onNetworkChange(networkControllerState) { -- const id = (0, util_1.addHexPrefix)(networkControllerState.providerConfig.chainId); -+ const selectedNetworkClient = this.messagingSystem.call('NetworkController:getNetworkClientById', networkControllerState.selectedNetworkClientId); -+ const { chainId } = selectedNetworkClient.configuration; -+ const id = (0, util_1.addHexPrefix)(chainId); - if (id === __classPrivateFieldGet(this, _PPOMController_chainId, "f")) { - return; - } diff --git a/.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch b/.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch deleted file mode 100644 index e801e600534c..000000000000 --- a/.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch +++ /dev/null @@ -1,27 +0,0 @@ -diff --git a/index.js b/index.js -index 13e9f3c25e7d3bee6a4ec3c2c5e1eea31e86a377..18b050ded27baf3603708fd6d595a554ea3c19c8 100644 ---- a/index.js -+++ b/index.js -@@ -1,9 +1,9 @@ - const extend = require('xtend') - const createRandomId = require('json-rpc-random-id')() -+const debug = require('debug')('eth-query') - - module.exports = EthQuery - -- - function EthQuery(provider){ - const self = this - self.currentProvider = provider -@@ -63,7 +63,10 @@ EthQuery.prototype.submitHashrate = generateFnFor('eth_subm - - EthQuery.prototype.sendAsync = function(opts, cb){ - const self = this -- self.currentProvider.sendAsync(createPayload(opts), function(err, response){ -+ const payload = createPayload(opts) -+ debug('making request %o', payload) -+ self.currentProvider.sendAsync(payload, function(err, response){ -+ debug('got err = %o, response = %o', err, response) - if (!err && response.error) err = new Error('EthQuery - RPC Error - '+response.error.message) - if (err) return cb(err) - cb(null, response.result) diff --git a/.yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch b/.yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch new file mode 100644 index 000000000000..d3b14fd157ca --- /dev/null +++ b/.yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch @@ -0,0 +1,48 @@ +diff --git a/src/loadPolicy.js b/src/loadPolicy.js +index b3053356c739a5f351fd4e271b67e31ee00bb4dc..7daebe4104ce5d799f90068516b1d0aaa58546c0 100644 +--- a/src/loadPolicy.js ++++ b/src/loadPolicy.js +@@ -101,10 +101,9 @@ async function loadPolicyAndApplyOverrides({ + + const finalPolicy = mergePolicy(policy, policyOverride) + +- // TODO: Only write if merge results in changes. +- // Would have to make a deep equal check on whole policy, which is a waste of time. +- // mergePolicy() should be able to do it in one pass. +- await fs.writeFile(policyPath, jsonStringifySortedPolicy(finalPolicy)) ++ // Skip policy write step to prevent intermittent build failures ++ // The extension validates the policy in a separate step, we don't need it ++ // to be written to disk here. + + return finalPolicy + } +diff --git a/src/scuttle.js b/src/scuttle.js +index c096a1fbf0bfe8a8f22290852881598f74fff4b1..b7438881be5e25b48ea18919a4b642a0b14cc317 100644 +--- a/src/scuttle.js ++++ b/src/scuttle.js +@@ -77,6 +77,8 @@ function generateScuttleOpts(globalRef, originalOpts = create(null)) { + exceptions: [], + scuttlerName: '', + } ++ // cache regular expressions to work around https://github.com/MetaMask/metamask-extension/issues/21006 ++ const regexCache = new Map() + const opts = assign( + create(null), + originalOpts === true ? defaultOpts : originalOpts, +@@ -109,10 +111,15 @@ function generateScuttleOpts(globalRef, originalOpts = create(null)) { + if (!except.startsWith('/')) { + return except + } ++ if (regexCache.has(except)) { ++ return regexCache.get(except) ++ } + const parts = except.split('/') + const pattern = parts.slice(1, -1).join('/') + const flags = parts[parts.length - 1] +- return new RegExp(pattern, flags) ++ const re = new RegExp(pattern, flags) ++ regexCache.set(except, re) ++ return re + } + } + diff --git a/.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch b/.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch deleted file mode 100644 index 20ee46808781..000000000000 --- a/.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/build/cjs-browser/luxon.js b/build/cjs-browser/luxon.js -index 776c38ae1679eaed97ef5fa9a666e37e48747df6..336fb0ddfffd12604b5ab1609ba0685755739c9c 100644 ---- a/build/cjs-browser/luxon.js -+++ b/build/cjs-browser/luxon.js -@@ -4226,7 +4226,7 @@ var Interval = /*#__PURE__*/function () { - * @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> Mon, Nov 07, 6:00 – 8:00 p - * @return {string} - */; -- _proto.toLocaleString = function toLocaleString(formatOpts, opts) { -+ Reflect.defineProperty(_proto, 'toLocaleString', { value: function toLocaleString(formatOpts, opts) { - if (formatOpts === void 0) { - formatOpts = DATE_SHORT; - } -@@ -4234,7 +4234,7 @@ var Interval = /*#__PURE__*/function () { - opts = {}; - } - return this.isValid ? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this) : INVALID$1; -- } -+ }}) - - /** - * Returns an ISO 8601-compliant string representation of this Interval. -@@ -6598,7 +6598,7 @@ var DateTime = /*#__PURE__*/function () { - * @example DateTime.now().toLocaleString({ hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }); //=> '11:32' - * @return {string} - */; -- _proto.toLocaleString = function toLocaleString(formatOpts, opts) { -+ Reflect.defineProperty(_proto, 'toLocaleString', { value: function toLocaleString(formatOpts, opts) { - if (formatOpts === void 0) { - formatOpts = DATE_SHORT; - } -@@ -6606,7 +6606,7 @@ var DateTime = /*#__PURE__*/function () { - opts = {}; - } - return this.isValid ? Formatter.create(this.loc.clone(opts), formatOpts).formatDateTime(this) : INVALID; -- } -+ }}) - - /** - * Returns an array of format "parts", meaning individual tokens along with metadata. This is allows callers to post-process individual sections of the formatted output. diff --git a/.yarnrc.yml b/.yarnrc.yml index 8e12d8037c6a..652a13c3c19c 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -43,6 +43,14 @@ npmAuditIgnoreAdvisories: # not appear to be used. - 1092461 + # Issue: Malware in @solana/web3.js + # URL: https://github.com/advisories/GHSA-2mhj-xmf4-pr8m + # we patched this to ensure the vulnerable versions are not included, but the advisory + # was mistakenly originally created to flag all versions as vulnerable + - 1101059 + + + # Temp fix for https://github.com/MetaMask/metamask-extension/pull/16920 for the sake of 11.7.1 hotfix # This will be removed in this ticket https://github.com/MetaMask/metamask-extension/issues/22299 - 'ts-custom-error (deprecation)' diff --git a/CHANGELOG.md b/CHANGELOG.md index e849728f7781..9f78659c6afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,216 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.10.1] +### Changed +- Stop publishing MMI builds to the release page ([#29732](https://github.com/MetaMask/metamask-extension/pull/29732)) + +## [12.10.0] +### Added +- Added B3 network logo for improved identification ([#27778](https://github.com/MetaMask/metamask-extension/pull/27778)) +- Enabled multi-network selection and token address search in the Asset Picker ([#28975](https://github.com/MetaMask/metamask-extension/pull/28975)) +- Removed scroll-to-bottom requirement for confirming personal sign requests ([#29053](https://github.com/MetaMask/metamask-extension/pull/29053)) +- Added a hyperlink to the pending transaction alert for quick access to documentation ([#28721](https://github.com/MetaMask/metamask-extension/pull/28721)) +- Implemented unified confirmation navigation for seamless handling of multiple pending confirmations ([#28761](https://github.com/MetaMask/metamask-extension/pull/28761)) +- Upgraded transaction controller to retrieve incoming transactions via the accounts API instead of Etherscan ([#28597](https://github.com/MetaMask/metamask-extension/pull/28597)) +- Added expanded view support for Snap notifications ([#27407](https://github.com/MetaMask/metamask-extension/pull/27407)) +- Added an info message to clarify redirection when a hardware wallet user declines a transaction during the bridge process ([#29198](https://github.com/MetaMask/metamask-extension/pull/29198)) +- Implemented proper routing for failed transactions ([#29158](https://github.com/MetaMask/metamask-extension/pull/29158)) +- Added hardware wallet confirmation screen for Bridge transactions ([#29113](https://github.com/MetaMask/metamask-extension/pull/29113)) +- Added calculation for max total gas fee ([#29116](https://github.com/MetaMask/metamask-extension/pull/29116)) +- Added a banner to notify users to contact Support if a Bridge transaction is delayed ([#28849](https://github.com/MetaMask/metamask-extension/pull/28849)) +- Enhanced cross-chain swap support for Smart Transactions (STX) ([#28460](https://github.com/MetaMask/metamask-extension/pull/28460)) +- Added UI components for Bridge Transaction Details ([#28657](https://github.com/MetaMask/metamask-extension/pull/28657)) +- Introduced a carousel component on the homepage to display up to 5 banners about new changes ([#28956](https://github.com/MetaMask/metamask-extension/pull/28956)) +- [FLASK] Enabled Solana feature in Flask build with account creation support in experimental settings ([#29147](https://github.com/MetaMask/metamask-extension/pull/29147)) + +### Changed +- Redesigned cross-chain swaps UI with updated layout, styling, asset picker, advanced settings, and enhanced quote validation ([#28373](https://github.com/MetaMask/metamask-extension/pull/28373)) +- Migrated Base Mainnet RPC to Infura for improved reliability and performance ([#28974](https://github.com/MetaMask/metamask-extension/pull/28974)) +- Updated default network avatar style to a rounded square shape positioned at the bottom right of tokens ([#28976](https://github.com/MetaMask/metamask-extension/pull/28976)) +- Replaced Spinner component with Preloader component in Snaps ([#29143](https://github.com/MetaMask/metamask-extension/pull/29143)) +- Updated Swaps to redirect Bridge links to the native Bridge experience instead of the Portfolio Bridge. ([#29175](https://github.com/MetaMask/metamask-extension/pull/29175)) +- Updated Bridge carousel cards to direct users to the in-app Bridge experience instead of the Portfolio ([#29169](https://github.com/MetaMask/metamask-extension/pull/29169)) +- Disabled buttons during submission and ensured proper state reset after submission completion ([#29149](https://github.com/MetaMask/metamask-extension/pull/29149)) +- Introduced a delay for Linea bridge transactions to improve reliability and reduce flakiness ([#29109](https://github.com/MetaMask/metamask-extension/pull/29109)) +- Enhanced Bridge transaction details screen ([#29075](https://github.com/MetaMask/metamask-extension/pull/29075)) +- Removed the Cancel button for Bridge transactions in the activity list ([#28902](https://github.com/MetaMask/metamask-extension/pull/28902)) +- Removed the product tour from the Permissions Page ([#28966](https://github.com/MetaMask/metamask-extension/pull/28966)) +- [FLASK] Added support to display selected currencies for non-EVM accounts like BTC ([#28963](https://github.com/MetaMask/metamask-extension/pull/28963)) + +### Fixed +- Replaced hardcoded slippage with dynamic values and disabled transaction submission 30 seconds after final quote fetch ([#29028](https://github.com/MetaMask/metamask-extension/pull/29028)) +- Fixed fallback icon to correctly display the first letter of the network name for non-popular networks ([#29121](https://github.com/MetaMask/metamask-extension/pull/29121)) +- Fixed token network filter to display icons and tooltips for 9 popular networks only ([#29112](https://github.com/MetaMask/metamask-extension/pull/29112)) +- Fixed token detection to display results across multiple networks when 'popular networks' filter is selected ([#29108](https://github.com/MetaMask/metamask-extension/pull/29108)) +- Fixed token list to respect the 'hide zero balance' setting for both native and ERC20 tokens across network filters ([#29058](https://github.com/MetaMask/metamask-extension/pull/29058)) +- Fixed sticky behavior of the autodetection banner and updated 'ignore all' functionality ([#29061](https://github.com/MetaMask/metamask-extension/pull/29061)) +- Fixed app crash when re-adding a network and interacting with the import token banner ([#28870](https://github.com/MetaMask/metamask-extension/pull/28870)) +- Applied design fixes to re-designed confirmation pages for improved user experience ([#29137](https://github.com/MetaMask/metamask-extension/pull/29137)) +- Fixed duplicate labels in the signature decoding section for multiple asset state changes ([#29020](https://github.com/MetaMask/metamask-extension/pull/29020)) +- Updated "Amount" Row visibility in transaction details for better clarity in contract interactions ([#3783](https://github.com/MetaMask/MetaMask-planning/issues/3783)) +- Updated NFT approve confirmation title to 'Withdrawal request' for consistency ([#29017](https://github.com/MetaMask/metamask-extension/pull/29017)) +- Increased alert severity to 'Danger' for pending Smart Transaction requests and blocked confirm button until resolved ([#29140](https://github.com/MetaMask/metamask-extension/pull/29140)) +- Updated 'Signing in with' label to display exclusively for SIWE requests, improving clarity in signature requests ([#28984](https://github.com/MetaMask/metamask-extension/pull/28984)) +- Fixed decimal input issue on advanced gas modal for macOS and improved validation for custom nonce and gas limit inputs ([#28869](https://github.com/MetaMask/metamask-extension/pull/28869)) +- Hid the first-time interaction alert for transactions involving internal accounts ([#28990](https://github.com/MetaMask/metamask-extension/pull/28990)) +- Added an origin row and content divider to transfer confirmations, and fixed a margin issue in the simulation details UI ([#28936](https://github.com/MetaMask/metamask-extension/pull/28936)) +- Fixed signature decoding data display for ERC-1155 tokens ([#28921](https://github.com/MetaMask/metamask-extension/pull/28921)) +- Updated confirmations to display Identicons ([#28645](https://github.com/MetaMask/metamask-extension/pull/28645)) +- Disabled link out modal for preinstalled Snap links ([#29142](https://github.com/MetaMask/metamask-extension/pull/29142)) +- Fixed app crash issue occurring after submitting a bridge transaction ([#29203](https://github.com/MetaMask/metamask-extension/pull/29203)) +- Limited Bridge transaction amounts to 6 decimal places to prevent text cutoff in activity items ([#29153](https://github.com/MetaMask/metamask-extension/pull/29153)) +- Fixed incorrect token symbol, token amount, and currency display in cross-chain swaps activity items ([#28899](https://github.com/MetaMask/metamask-extension/pull/28899)) +- Fixed transaction order for cross-chain swaps ([#28939](https://github.com/MetaMask/metamask-extension/pull/28939)) +- Fixed issue where clearing Activity tab data wiped txHistory for both source and destination chains in Bridge transactions ([#29000](https://github.com/MetaMask/metamask-extension/pull/29000)) +- [FLASK] Fixed Solana native balance display in the account selector ([#29054](https://github.com/MetaMask/metamask-extension/pull/29054)) + +## [12.9.3] +### Fixed +- Fix some cases where users were incorrectly seeing 0 token balances ([#29361](https://github.com/MetaMask/metamask-extension/pull/29361)) +- Ensure users that opt out of smart transaction decoding don't send network requests to related APIs ([#29341](https://github.com/ +MetaMask/metamask-extension/pull/29341)) + +## [12.9.2] +### Changed +- Display the "Amount" row within the advanced view of contract interaction confirmations, and whenever the amount being sent differs from the "You Send" row of the transaction simulation information by more than 5% ([#29131](https://github.com/MetaMask/metamask-extension/pull/29131)) +- Improved phishing detection protections ([#28782](https://github.com/MetaMask/metamask-extension/pull/28782)) + +### Fixed +- Ensure that the correct fallback letter is used for network icons within the token list ([#29121](https://github.com/MetaMask/metamask-extension/pull/29121)) +- Ensure users have to click through a blocking red warning before submitting multiple Smart Transactions while one is already pending ([#29140](https://github.com/MetaMask/metamask-extension/pull/29140)) +- Prevent users from being stuck on an "Invalid string length" error screen, by deleting tokens from their state of the data was invalid because the `decimals` property of the token was `null` ([#29245](https://github.com/MetaMask/metamask-extension/pull/29245)) + +## [12.9.1] +### Changed +- The 'All Networks' view of assets on the home screen will now only get data across the 9 'popular networks' ([#29071](https://github.com/MetaMask/metamask-extension/pull/29071)) + +### Fixed +- Ensure tokens with zero balance are hidden if the hide zero balance setting is on ([#29058](https://github.com/MetaMask/metamask-extension/pull/29058)) +- Ensure token detection prompt is no longer shown after it is clicked and the subsquent prompt is closed ([#29059](https://github.com/MetaMask/metamask-extension/pull/29059)) + +## [12.9.0] +### Added +- Added error handling to ensure users are not redirected to an incorrect network when sending or swapping tokens ([#28740](https://github.com/MetaMask/metamask-extension/pull/28740)) +- Added optional chaining to currency rates check for improved stability ([#28753](https://github.com/MetaMask/metamask-extension/pull/28753)) +- Enabled Portfolio View ([#28661](https://github.com/MetaMask/metamask-extension/pull/28661)) +- Added a selector to enable cross-chain polling for aggregated balances ([#28662](https://github.com/MetaMask/metamask-extension/pull/28662)) +- Ensured the network filter respects the PortfolioView feature flag, displaying tokens accordingly ([#28626](https://github.com/MetaMask/metamask-extension/pull/28626)) +- Implemented multichain token detection, enabling periodic polling and storing detected tokens across all supported networks ([#28380](https://github.com/MetaMask/metamask-extension/pull/28380)) +- Added PortfolioView to display tokens across all networks in one list ([#28593](https://github.com/MetaMask/metamask-extension/pull/28593)) +- Added cross-chain aggregated balance calculation ([#28456](https://github.com/MetaMask/metamask-extension/pull/28456)) +- Enabled redesigned transaction confirmations for all users, with automatic toggling ([#28321](https://github.com/MetaMask/metamask-extension/pull/28321)) +- Added a first-time interaction warning to alert users when interacting with an address for the first time ([#28435](https://github.com/MetaMask/metamask-extension/pull/28435)) +- Added a default value to the custom nonce modal ([#28659](https://github.com/MetaMask/metamask-extension/pull/28659)) +- Added an alert when the selected account differs from the signing account in the confirmation screen ([#28562](https://github.com/MetaMask/metamask-extension/pull/28562)) +- Display "< 0.01" instead of "0.00" for the fiat value of network fees ([#28543](https://github.com/MetaMask/metamask-extension/pull/28543)) +- Improved handling of very long names by truncating names longer than 15 characters with an ellipsis ([#28560](https://github.com/MetaMask/metamask-extension/pull/28560)) +- Enabled account syncing in production ([#28596](https://github.com/MetaMask/metamask-extension/pull/28596)) +- Added various updates to account syncing in preparation for re-enablement ([#28541](https://github.com/MetaMask/metamask-extension/pull/28541)) +- Added entry points to the Portfolio for viewing and managing spending caps from the extension ([#27607](https://github.com/MetaMask/metamask-extension/pull/27607)) + +### Changed +- Updated the new network popup to only display for compatible accounts ([#28535](https://github.com/MetaMask/metamask-extension/pull/28535)) +- Removed the "You're now using..." network modal after adding a network ([#28765](https://github.com/MetaMask/metamask-extension/pull/28765)) +- Updated the transaction list message on the token detail page to reflect the current network ([#28764](https://github.com/MetaMask/metamask-extension/pull/28764)) +- Updated the description of the setting to enable simulation to include signatures ([#28536](https://github.com/MetaMask/metamask-extension/pull/28536)) +- Reduced maximum pet name length to 12 characters ([#28660](https://github.com/MetaMask/metamask-extension/pull/28660)) +- Updated NFT token send design ([#28433](https://github.com/MetaMask/metamask-extension/pull/28433)) +- Improved design aspects of PortfolioView, including networks, sorting, and menu ([#28663](https://github.com/MetaMask/metamask-extension/pull/28663)) +- Provided maximal space for asset list filter to display "All networks" text fully and ellipsize long account names properly ([#28590](https://github.com/MetaMask/metamask-extension/pull/28590)) + +### Fixed +- [FLASK] Fixed issue where non-EVM accounts were incorrectly included in the account connection flow ([#28436](https://github.com/MetaMask/metamask-extension/pull/28436)) +- Fixed issue with detecting NFTs when switching networks on the NFT tab ([#28769](https://github.com/MetaMask/metamask-extension/pull/28769)) +- Passed decimal balance from asset page to swaps UI to ensure proper prepopulation ([#28707](https://github.com/MetaMask/metamask-extension/pull/28707)) +- Fixed issue where the incorrect native token was prepopulated in the swap UI when swapping from a different chain in PortfolioView ([#28639](https://github.com/MetaMask/metamask-extension/pull/28639)) +- Fixed issue where tokens from non-current networks were being hidden incorrectly ([#28674](https://github.com/MetaMask/metamask-extension/pull/28674)) +- Fixed market data retrieval for native tokens with non-zero addresses, such as Polygon's native token ([#28584](https://github.com/MetaMask/metamask-extension/pull/28584)) +- Fixed display issues for test networks in Portfolio View when the price checker setting is off ([#28601](https://github.com/MetaMask/metamask-extension/pull/28601)) +- Fixed account list item display for PortfolioView with and without the feature flag ([#28598](https://github.com/MetaMask/metamask-extension/pull/28598)) +- Fixed display bug on coin overview and account list item when the "Show balance and token price checker" setting is off ([#28569](https://github.com/MetaMask/metamask-extension/pull/28569)) +- Fixed styling issue affecting all dialogs by limiting it to the quotes modal ([#28739](https://github.com/MetaMask/metamask-extension/pull/28739)) +- Fixed swaps approval checking for amounts greater than 0 but less than the swap amount ([#28680](https://github.com/MetaMask/metamask-extension/pull/28680)) +- Fixed transaction flow section layout on redesigned confirmation pages ([#28720](https://github.com/MetaMask/metamask-extension/pull/28720)) +- Prevented duplicate contact names and added warnings for duplicates in the contact list ([#28249](https://github.com/MetaMask/metamask-extension/pull/28249)) +- Made QR scanner more strict about the contents it allows, fixing unexpected behavior with certain QR codes ([#28521](https://github.com/MetaMask/metamask-extension/pull/28521)) +- Fixed avatar size for the current network ([#28731](https://github.com/MetaMask/metamask-extension/pull/28731)) +- Fixed account names and length display for dApp connections ([#28725](https://github.com/MetaMask/metamask-extension/pull/28725)) + +## [12.8.1] +### Fixed +- Update default Base rpc to https://base-mainnet.infura.io/ ([#28974](https://github.com/MetaMask/metamask-extension/pull/28974)) + +## [12.8.0] +### Added +- Added multi-chain polling for token prices ([#28158](https://github.com/MetaMask/metamask-extension/pull/28158)) +- Added account_type/snap_id for buy/send metrics ([#28011](https://github.com/MetaMask/metamask-extension/pull/28011)) +- Made UI changes to show decoding data for permits ([#28342](https://github.com/MetaMask/metamask-extension/pull/28342)) +- Implemented Sentry user report on error screen ([#27857](https://github.com/MetaMask/metamask-extension/pull/27857)) +- Showed network badge in detected tokens modal ([#28231](https://github.com/MetaMask/metamask-extension/pull/28231)) +- Migrated MetaMetricsController to BaseControllerV2 ([#28113](https://github.com/MetaMask/metamask-extension/pull/28113)) +- Converted MMI controller to a non-controller ([#27983](https://github.com/MetaMask/metamask-extension/pull/27983)) +- Upgraded alert controller to BaseControllerV2 ([#28054](https://github.com/MetaMask/metamask-extension/pull/28054)) +- Added token verification source count and link to block explorer ([#27759](https://github.com/MetaMask/metamask-extension/pull/27759)) +- Added "Add a new Solana account" link to the account creation dialog ([#28270](https://github.com/MetaMask/metamask-extension/pull/28270)) +- Added Solana snap to preinstall list ([#28141](https://github.com/MetaMask/metamask-extension/pull/28141)) +- Added the experimental toggle for Solana ([#28190](https://github.com/MetaMask/metamask-extension/pull/28190)) +- Added gravity logo and image mappings ([#28306](https://github.com/MetaMask/metamask-extension/pull/28306)) +- Used accounts API for token detection ([#28254](https://github.com/MetaMask/metamask-extension/pull/28254)) +- Displayed bridge quotes ([#28031](https://github.com/MetaMask/metamask-extension/pull/28031)) + +### Changed +- Upgraded assets controllers to version 43 with multi-chain polling for token lists and detection which allows for more efficient and accurate tracking of tokens across multiple chains ([#28447](https://github.com/MetaMask/metamask-extension/pull/28447)) +- Changed expand icon to align with the new design, improving the user interface and overall user experience ([#28267](https://github.com/MetaMask/metamask-extension/pull/28267)) +- Prevented polling of token prices during onboarding or when the wallet is locked, ensuring that unnecessary network requests are avoided. ([#28465](https://github.com/MetaMask/metamask-extension/pull/28465)) +- Disabled the buy feature for BTC testnet accounts to prevent users from attempting to purchase BTC on test networks ([#28341](https://github.com/MetaMask/metamask-extension/pull/28341)) +- Removed the warning prop from settings to clean up the code and prevent potential issues ([#27990](https://github.com/MetaMask/metamask-extension/pull/27990)) +- Improved error handling for state log download failures, providing better feedback and stability when issues occur ([#26999](https://github.com/MetaMask/metamask-extension/pull/26999)) +- Improved token lookup performance in useAccountTotalFiatBalance, enhancing the speed and efficiency of balance calculations ([#28233](https://github.com/MetaMask/metamask-extension/pull/28233)) +- Limited the frequency of bridge quote requests and added functionality to cancel requests, reducing unnecessary network traffic and improving performance ([#27237](https://github.com/MetaMask/metamask-extension/pull/27237)) +- Bumped Snaps packages to the latest versions, ensuring compatibility and leveraging new features and fixes ([#28215](https://github.com/MetaMask/metamask-extension/pull/28215)) +- Removed the STX opt-in modal to streamline the user experience and reduce unnecessary prompts ([#28291](https://github.com/MetaMask/metamask-extension/pull/28291)) +- Added the gas_included prop into the Quotes Requested event, providing more detailed and accurate event tracking for gas usage ([#28295](https://github.com/MetaMask/metamask-extension/pull/28295)) + +### Fixed +- Fixed network client ID used on the useGasFeeInputs hook ([#28391](https://github.com/MetaMask/metamask-extension/pull/28391)) +- Ignored error when getTokenStandardAndDetails fails ([#[28030](https://github.com/MetaMask/metamask-extension/pull/28030)]) +- Adjusted margin on asset chart min/max indicators ([#[27916](https://github.com/MetaMask/metamask-extension/pull/27916)]) +- Removed multiple overlapping spinners ([#[28301](https://github.com/MetaMask/metamask-extension/pull/28301)]) +- Hid "interacting with" when simulated balance changes are shown ([#[28409](https://github.com/MetaMask/metamask-extension/pull/28409)]) +- Ensured supportedChains does not block the confirmation process ([#[28313](https://github.com/MetaMask/metamask-extension/pull/28313)]) +- Returned to send page with different asset types ([#[28382](https://github.com/MetaMask/metamask-extension/pull/28382)]) +- Addressed design review for ERC20 token send ([#[28212](https://github.com/MetaMask/metamask-extension/pull/28212)]) +- Improved gas limit estimation ([#[28327](https://github.com/MetaMask/metamask-extension/pull/28327)]) +- Updated simulations component ([#[28107](https://github.com/MetaMask/metamask-extension/pull/28107)]) +- Used transaction address to get lock for custom nonce ([#[28272](https://github.com/MetaMask/metamask-extension/pull/28272)]) +- Removed scroll-to-bottom requirement in redesigned transaction confirmations ([#[27910](https://github.com/MetaMask/metamask-extension/pull/27910)]) +- Hid fiat values on test networks ([#[28219](https://github.com/MetaMask/metamask-extension/pull/28219)]) +- Corrected Permit message dataTree value using default ERC20 decimals for non-ERC20 token values ([#[28142](https://github.com/MetaMask/metamask-extension/pull/28142)]) +- Prevented coercing symbols to zero in the edit spending cap modal ([#[28192](https://github.com/MetaMask/metamask-extension/pull/28192)]) +- Fixed MV2 Firefox CSP header ([#[27770](https://github.com/MetaMask/metamask-extension/pull/27770)]) +- Allowed outer click to close import modal ([#[28448](https://github.com/MetaMask/metamask-extension/pull/28448)]) +- Updated PortfolioView flag ([#[28446](https://github.com/MetaMask/metamask-extension/pull/28446)]) +- Added metric trait for privacy mode ([#[28335](https://github.com/MetaMask/metamask-extension/pull/28335)]) +- Properly ellipsized long token names ([#[28392](https://github.com/MetaMask/metamask-extension/pull/28392)]) +- Reverted "fix: Negate privacy mode in Send screen" ([#[28360](https://github.com/MetaMask/metamask-extension/pull/28360)]) +- Fixed alignment of long RPC labels in Networks menu ([#[28244](https://github.com/MetaMask/metamask-extension/pull/28244)]) +- Fixed attribution generation ([#[28415](https://github.com/MetaMask/metamask-extension/pull/28415)]) +- Added different copy for tooltip when a snap is requesting a signature ([#[27492](https://github.com/MetaMask/metamask-extension/pull/27492)]) +- Bumped @metamask/queued-request-controller with patch fix ([#[28355](https://github.com/MetaMask/metamask-extension/pull/28355)]) +- Corrected notification settings type ([[#28271](https://github.com/MetaMask/metamask-extension/pull/28271)]) +- Improved performance to Ensured setupLocale doesn't fetch _locales/en/messages.json twice ([[#26553](https://github.com/MetaMask/metamask-extension/pull/26553)]) + + +## [12.7.2] +### Fixed +- Fix message signatures for Gridplus lattice hardware wallets ([#28694](https://github.com/MetaMask/metamask-extension/pull/28694)) + +## [12.7.1] +### Fixed +- Fix bug that could prevent security warnings from being shown on token transfer confirmations in some cases ([#28487](https://github.com/MetaMask/metamask-extension/pull/28487)) +- Fix balance display, so that it correctly shows ETH and fiat values, when the "Show balane and token price checker" toggle is off ([#28569](https://github.com/MetaMask/metamask-extension/pull/28569)) + ## [12.7.0] ### 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)) @@ -42,6 +252,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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.2] +### Fixed +- Prevent QR code scanning from setting incorrect recipient addresses during the send flow by restricting the QR scanner feature to only handle simple sends, and fail on QR codes that encode more complex transaction types ([#28521](https://github.com/MetaMask/metamask-extension/pull/28521)) + ## [12.6.1] ### Fixed - Fixed gas limit estimation on Base and BNB chains ([#28327](https://github.com/MetaMask/metamask-extension/pull/28327)) @@ -5343,8 +5557,19 @@ 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.7.0...HEAD -[12.7.0]: https://github.com/MetaMask/metamask-extension/compare/v12.6.1...v12.7.0 +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.10.1...HEAD +[12.10.1]: https://github.com/MetaMask/metamask-extension/compare/v12.10.0...v12.10.1 +[12.10.0]: https://github.com/MetaMask/metamask-extension/compare/v12.9.3...v12.10.0 +[12.9.3]: https://github.com/MetaMask/metamask-extension/compare/v12.9.2...v12.9.3 +[12.9.2]: https://github.com/MetaMask/metamask-extension/compare/v12.9.1...v12.9.2 +[12.9.1]: https://github.com/MetaMask/metamask-extension/compare/v12.9.0...v12.9.1 +[12.9.0]: https://github.com/MetaMask/metamask-extension/compare/v12.8.1...v12.9.0 +[12.8.1]: https://github.com/MetaMask/metamask-extension/compare/v12.8.0...v12.8.1 +[12.8.0]: https://github.com/MetaMask/metamask-extension/compare/v12.7.2...v12.8.0 +[12.7.2]: https://github.com/MetaMask/metamask-extension/compare/v12.7.1...v12.7.2 +[12.7.1]: https://github.com/MetaMask/metamask-extension/compare/v12.7.0...v12.7.1 +[12.7.0]: https://github.com/MetaMask/metamask-extension/compare/v12.6.2...v12.7.0 +[12.6.2]: https://github.com/MetaMask/metamask-extension/compare/v12.6.1...v12.6.2 [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 diff --git a/README.md b/README.md index f3e738a40abc..f76e913a6b6c 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ MetaMask supports Firefox, Google Chrome, and Chromium-based browsers. We recomm For up to the minute news, follow us on [X](https://x.com/MetaMask). -To learn how to develop MetaMask-compatible applications, visit our [Developer Docs](https://metamask.github.io/metamask-docs/). +To learn how to develop MetaMask-compatible applications, visit our [Developer Docs](https://docs.metamask.io/). To learn how to contribute to the MetaMask codebase, visit our [Contributor Docs](https://github.com/MetaMask/contributor-docs). -To learn how to contribute to the MetaMask Extension project itself, visit our [Extension Docs](https://github.com/MetaMask/metamask-extension/tree/develop/docs). +To learn how to contribute to the MetaMask Extension project itself, visit our [Extension Docs](https://github.com/MetaMask/metamask-extension/tree/main/docs). ## GitHub Codespaces quickstart diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index f118bc17df41..fac85861828c 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -369,9 +369,6 @@ "loading": { "message": "በመጫን ላይ…" }, - "loadingTokens": { - "message": "ተለዋጭ ስሞችን በመጫን ላይ..." - }, "lock": { "message": "ዘግተህ ውጣ" }, @@ -431,9 +428,6 @@ "noConversionRateAvailable": { "message": "ምንም የልወጣ ተመን አይገኝም" }, - "noTransactions": { - "message": "ግብይቶች የሉዎትም" - }, "noWebcamFound": { "message": "የኮምፒዩተርዎ ካሜራ አልተገኘም። እባክዎ እንደገና ይሞክሩ።" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index d9717df6b190..33585243d016 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -385,9 +385,6 @@ "loading": { "message": "جارٍ التحميل..." }, - "loadingTokens": { - "message": "جارِ تحميل العملات الرمزية ..." - }, "localhost": { "message": "المضيف المحلي 8545" }, @@ -447,9 +444,6 @@ "noConversionRateAvailable": { "message": "لا يوجد معدل تحويل متاح" }, - "noTransactions": { - "message": "لا توجد لديك معاملات" - }, "noWebcamFound": { "message": "لم يتم العثور على كاميرا ويب للكمبيوتر الخاص بك. حاول مرة اخرى." }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 749b1561dafe..a6dbac690242 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Зарежда се..." }, - "loadingTokens": { - "message": "Зареждане на жетони..." - }, "localhost": { "message": "Локален хост 8545" }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "Няма наличен процент на преобръщане" }, - "noTransactions": { - "message": "Нямате транзакции" - }, "noWebcamFound": { "message": "Уеб камерата на компютърa Ви не беше намерена. Моля, опитайте отново." }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 15acaa2e6765..1df74dc0e941 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -375,9 +375,6 @@ "loading": { "message": "লোড হচ্ছে..." }, - "loadingTokens": { - "message": "টোকেনগুলি লোড করছে..." - }, "localhost": { "message": "লোকালহোস্ট 8545" }, @@ -440,9 +437,6 @@ "noConversionRateAvailable": { "message": "কোনো বিনিময় হার উপলভ্য নয়" }, - "noTransactions": { - "message": "আপনার কোনো লেনদেন নেই" - }, "noWebcamFound": { "message": "আপনার কম্পিউটারের ওয়েবক্যাম খুঁজে পাওয়া যায়নি। অনুগ্রহ করে আবার চেষ্টা করুন।" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index fc9e2afb41e6..b988ae488143 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -372,9 +372,6 @@ "loading": { "message": "S'està carregant..." }, - "loadingTokens": { - "message": "Carregant els tokens..." - }, "localhost": { "message": "Host local 8545" }, @@ -434,9 +431,6 @@ "noConversionRateAvailable": { "message": "No hi ha cap tarifa de conversió disponible" }, - "noTransactions": { - "message": "No tens transaccions" - }, "noWebcamFound": { "message": "No s'ha trovat la webcam del teu ordinador. Si us plau prova de nou." }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 4113f8c5cc42..adf67dbda77d 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -175,9 +175,6 @@ "loading": { "message": "Načítám..." }, - "loadingTokens": { - "message": "Načítám tokeny..." - }, "lock": { "message": "Odhlásit" }, @@ -213,9 +210,6 @@ "next": { "message": "Další" }, - "noTransactions": { - "message": "Žádné transakce" - }, "passwordNotLongEnough": { "message": "Heslo není dost dlouhé" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 37e4663523cf..080dd75f22d6 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -375,9 +375,6 @@ "loading": { "message": "Indlæser..." }, - "loadingTokens": { - "message": "Indlæser tokens..." - }, "lock": { "message": "Log ud" }, @@ -434,9 +431,6 @@ "noConversionRateAvailable": { "message": "Ingen tilgængelig omregningskurs" }, - "noTransactions": { - "message": "Du har ingen transaktioner" - }, "noWebcamFound": { "message": "Din computers webkamera blev ikke fundet. Prøv venligst igen." }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 0c8ba19030f2..f75eb99aa689 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Netzwerk wird hinzugefügt" }, - "addingTokens": { - "message": "Hinzufügen von Token" - }, "additionalNetworks": { "message": "Zusätzliche Netzwerke" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Angreifer imitieren manchmal Websites, indem sie kleine Änderungen an der Adresse der Website vornehmen. Vergewissern Sie sich, dass Sie mit der beabsichtigten Website interagieren, bevor Sie fortfahren." }, + "alertMessageChangeInSimulationResults": { + "message": "Die voraussichtlichen Änderungen für diese Transaktion wurden aktualisiert. Überprüfen Sie diese sorgfältig, bevor Sie fortfahren." + }, "alertMessageGasEstimateFailed": { "message": "Wir sind nicht in der Lage, eine genaue Gebühr anzugeben, und diese Schätzung könnte zu hoch sein. Wir schlagen vor, dass Sie ein individuelles Gas-Limit eingeben, aber es besteht das Risiko, dass die Transaktion trotzdem fehlschlägt." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Wir können mit dieser Transaktion nicht fortfahren, bis Sie die Gebühr manuell aktualisieren." }, - "alertMessagePendingTransactions": { - "message": "Diese Transaktion wird erst dann durchgeführt, wenn eine vorherige Transaktion abgeschlossen ist. Erfahren Sie, wie Sie eine Transaktion abbrechen oder beschleunigen können." - }, "alertMessageSignInDomainMismatch": { "message": "Die Website, die die Anfrage stellt, ist nicht die Website, bei der Sie sich anmelden. Dies könnte ein Versuch sein, Ihre Anmeldedaten zu stehlen." }, "alertMessageSignInWrongAccount": { "message": "Diese Seite fordert Sie auf, sich mit dem falschen Konto anzumelden." }, - "alertMessageSigningOrSubmitting": { - "message": "Diese Transaktion wird erst durchgeführt, wenn Ihre vorherige Transaktion abgeschlossen ist." - }, "alertModalAcknowledge": { "message": "Ich habe das Risiko erkannt und möchte trotzdem fortfahren" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Alle Benachrichtigungen überprüfen" }, + "alertReasonChangeInSimulationResults": { + "message": "Ergebnisse haben sich geändert" + }, "alertReasonGasEstimateFailed": { "message": "Ungenaue Gebühr" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Automatische Erkennung von Tokens in Ihrer Wallet, Anzeige von NFTs und stapelweise Aktualisierung des Kontostands" }, - "attemptSendingAssets": { - "message": "Wenn Sie versuchen, Assets direkt von einem Netzwerk in ein anderes zu senden, kann dies zu einem dauerhaften Asset-Verlust führen. Verwenden Sie unbedingt eine Bridge." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Sie können Ihre Assets verlieren, wenn Sie versuchen, sie von einem anderen Netzwerk zu versenden. Übertragen Sie Gelder sicher zwischen den Netzwerken, indem Sie eine Bridge wie $1 verwenden." - }, "attemptToCancelSwapForFree": { "message": "Versuch, den Swap kostenlos zu stornieren" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Bridge" }, - "bridgeDontSend": { - "message": "Bridge, nicht senden" + "bridgeCalculatingAmount": { + "message": "Berechnen ..." + }, + "bridgeEnterAmount": { + "message": "Betrag eingeben" }, "bridgeFrom": { "message": "Bridge von" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Netzwerk wählen" }, + "bridgeSelectTokenAndAmount": { + "message": "Token und Betrag auswählen" + }, "bridgeTo": { "message": "Bridge nach" }, @@ -945,9 +942,6 @@ "message": "Klicken Sie hier, um Ihren Ledger über WebHID zu verbinden.", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Sie können Tokens jederzeit manuell hinzufügen." - }, "close": { "message": "Schließen" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Geheime Wiederherstellungsphrase bestätigen" }, - "confirmTitleApproveTransaction": { - "message": "Bewilligungsanfrage" - }, "confirmTitleDeployContract": { "message": "Einen Kontrakt nutzen" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Transaktionsanfrage" }, - "confirmationAlertModalDetails": { - "message": "Um Ihre Assets und Anmeldedaten zu schützen, empfehlen wir die Ablehnung der Anfrage." - }, "confirmationAlertModalTitle": { "message": "Diese Anfrage scheint verdächtig" }, @@ -1160,6 +1148,14 @@ "message": "Verbunden mit $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 Netzwerke verbunden", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Verbunden mit $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Verbinden" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Kaufoptionen per Debit- oder Kreditkarte" + }, "decimal": { "message": "Token-Dezimale" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Die Ausgabenobergrenze darf nicht mehr als $1 Dezimalstellen überschreiten. Entfernen Sie die Dezimalstellen, um fortzufahren." }, + "editSpendingCapSpecialCharError": { + "message": "Nur Zahlen eingeben" + }, "enableAutoDetect": { "message": " Automatische Erkennung aktivieren" }, @@ -1904,10 +1906,42 @@ "message": "Code: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Support kontaktieren", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Beschreiben Sie, was vorgefallen ist", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Ihre Daten können nicht angezeigt werden. Keine Sorge, Ihre Wallet und Ihr Geld sind sicher.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Fehlermeldung", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Beschreiben Sie, was vorgefallen ist", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Die Übermittlung von Details, z. B. wie wir den Fehler reproduzieren können, wird uns bei der Behebung des Problems helfen.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Vielen Dank! Wir werden uns bald damit befassen.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask hat einen Fehler festgestellt.", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Erneut versuchen", + "description": "Button for try again" + }, "errorStack": { "message": "Stapel:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Funktionstyp" }, + "fundingMethod": { + "message": "Methode der Mittelbereitstellung" + }, "gas": { "message": "Gas" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Gas-Gebühr" }, - "gasIsETH": { - "message": "Gas ist $1" - }, "gasLimit": { "message": "Gas-Limit" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "Betrügereien und Sicherheitsrisiken." }, - "learnToBridge": { - "message": "Lernen Sie zu bridgen" - }, "leaveMetaMask": { "message": "MetaMask verlassen?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Bitte schließen Sie die Transaktion im Snap ab." }, - "loadingTokens": { - "message": "Tokens werden geladen ..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Wallet-Benachrichtigungen sind momentan nicht aktiv." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps wird gewartet. Bitte versuchen Sie es später erneut." }, @@ -2992,10 +3017,6 @@ "message": "$1 bittet um Ihre Zustimmung zu:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Das native Token dieses Netzwerks ist $1. Dieses Token wird für die Gas-Gebühr verwendet. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Netzwerkdetails bearbeiten" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Nein, danke!" }, - "noTransactions": { - "message": "Keine Transaktionen" - }, "noWebcamFound": { "message": "Die Webcam Ihres Computers wurde nicht gefunden. Bitte versuchen Sie es erneut." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Ihr Browser ist veraltet. Wenn Sie Ihren Browser nicht aktualisieren, können Sie keine Sicherheits-Patches und neue Funktionen von MetaMask erhalten." }, + "overrideContentSecurityPolicyHeader": { + "message": "Content-Security-Policy-Header aufheben" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Diese Option dient als Behelfslösung für ein bekanntes Problem in Firefox, bei dem der Content-Security-Policy-Header einer dApp verhindern kann, dass die Erweiterung ordnungsgemäß geladen wird. Die Deaktivierung dieser Option wird nicht empfohlen, es sei denn, sie ist für die Kompatibilität bestimmter Webseiten erforderlich." + }, "padlock": { "message": "Padlock" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Hier können Sie die Genehmigungen sehen, die Sie installierten Snaps oder verbundenen Websites gegeben haben." }, - "permissionsPageTourDescription": { - "message": "Dies ist Ihr Kontrollfeld zum Verwalten der Genehmigungen für verbundene Websites und installierte Snaps." - }, - "permissionsPageTourTitle": { - "message": "Verbundene Websites sind nun Genehmigungen" - }, "permitSimulationDetailInfo": { "message": "Sie erteilen dem Spender die Genehmigung, diese Menge an Tokens von Ihrem Konto auszugeben." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Schützen Sie Ihre Gelder." }, - "redesignedConfirmationsEnabledToggle": { - "message": "Verbesserte Unterschriftsanfrage" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Schalten Sie dies ein, um sich Unterschriftsanfragen in einem erweiterten Format anzeigen zu lassen." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Verbesserte Transaktionsanfragen" - }, - "redesignedTransactionsToggleDescription": { - "message": "Schalten Sie dies ein, um sich Transaktionsanfragen in einem erweiterten Format anzeigen zu lassen." - }, "refreshList": { "message": "Liste aktualisieren" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Dies ist die Seite, auf der Sie um Ihre Unterschrift gebeten werden." }, + "requestFromInfoSnap": { + "message": "Dies ist der Snap, der Sie zur Unterschrift auffordert." + }, "requestFromTransactionDescription": { "message": "Dies ist die Website, die Sie um Ihre Bestätigung bittet." }, @@ -4417,6 +4426,10 @@ "message": "Anfordern für $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Anforderung von $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "Anfragen warten auf Bestätigung" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Sie erhalten" }, + "simulationDetailsNoChanges": { + "message": "Keine Änderungen" + }, "simulationDetailsOutgoingHeading": { "message": "Sie senden" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Diese Transaktion wird wahrscheinlich scheitern" }, + "simulationDetailsUnavailable": { + "message": "Nicht verfügbar" + }, "simulationErrorMessageV2": { "message": "Wir konnten das Gas nicht schätzen. Es könnte einen Fehler im Contract geben und diese Transaktion könnte fehlschlagen." }, @@ -5143,6 +5162,15 @@ "message": "Kontaktieren Sie die Ersteller von $1 für weitere Unterstützung.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Bei Aktivierung dieser Funktion haben Sie die Möglichkeit, ein Solana-Konto zu Ihrer MetaMask-Erweiterung hinzuzufügen, das von Ihrer bestehenden geheimen Wiederherstellungsphrase abgeleitet ist. Hierbei handelt es sich um eine experimentelle Beta-Funktion, deren Nutzung also auf eigene Gefahr erfolgt." + }, + "solanaSupportToggleTitle": { + "message": "Aktivierung der Funktion „Ein neues Solana-Konto hinzufügen (Beta)“" + }, "somethingDoesntLookRight": { "message": "Scheint irgendetwas nicht in Ordnung zu sein? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Möglicherweise unglaubwürdiges Token" }, + "swapTokenVerifiedSources": { + "message": "Bestätigt durch $1 Quellen. Auf $2 verifiziert.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 erlaubt bis zu $2 Dezimalstellen", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 ist jetzt aktiv bei $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Sie verwenden jetzt" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Das Wechseln der Netzwerke wird alle ausstehenden Bestätigungen stornieren." }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Wählen Sie Ihr bevorzugtes MetaMask-Motiv aus." }, - "thingsToKeep": { - "message": "Was Sie beachten sollten:" - }, "thirdPartySoftware": { "message": "Mitteilung bzgl. Drittanbieter-Software", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Tipps" }, + "tipsForUsingAWallet": { + "message": "Tipps zur Verwendung einer Wallet" + }, + "tipsForUsingAWalletDescription": { + "message": "Das Hinzufügen von Tokens eröffnet weitere Möglichkeiten zur Nutzung von web3." + }, "to": { "message": "An" }, @@ -5925,18 +5957,6 @@ "message": "An: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Dadurch können Sie ein Netzwerk für jede Website auswählen, anstatt ein einziges Netzwerk für alle Websites auszuwählen. Diese Funktion verhindert, dass Sie manuell zwischen den Netzwerken wechseln müssen, was die Benutzerfreundlichkeit auf bestimmten Websites beeinträchtigen könnte." - }, - "toggleRequestQueueField": { - "message": "Wählen Sie Netzwerke für jede Website" - }, - "toggleRequestQueueOff": { - "message": "Aus" - }, - "toggleRequestQueueOn": { - "message": "An" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Token-Listen" }, + "tokenMarketplace": { + "message": "Token-Marktplatz" + }, "tokenScamSecurityRisk": { "message": "Token-Betrügereien und Sicherheitsrisiken" }, - "tokenShowUp": { - "message": "Ihre Tokens werden möglicherweise nicht automatisch in Ihrer Wallet angezeigt. " - }, "tokenStandard": { "message": "Token-Standard" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Smart Contracts dekodieren" }, - "use4ByteResolutionDescription": { - "message": "Um das Benutzererlebnis zu verbessern, passen wir die Aktivitätsregisterkarte mit Nachrichten an, die auf den Smart Contracts basieren, mit denen Sie interagieren. MetaMask verwendet einen Dienst namens 4byte.directory, um Daten zu entschlüsseln und Ihnen eine Version eines Smart Contracts anzuzeigen, die leichter zu lesen ist. Dies trägt dazu bei, die Wahrscheinlichkeit zu verringern, dass Sie bösartige Smart-Contract-Aktionen genehmigen, kann aber dazu führen, dass Ihre IP-Adresse weitergegeben wird." - }, "useMultiAccountBalanceChecker": { "message": "Kontoguthaben-Anfragen sammeln" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Laut unseren Aufzeichnungen stimmt dieser Netzwerkname nicht mit dieser Chain-ID überein." }, - "xOfYPending": { - "message": "$1 von $2 ausstehend", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Ja" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 31cd6809080b..4b0daa851f12 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Προσθήκη δικτύου" }, - "addingTokens": { - "message": "Προσθήκη tokens" - }, "additionalNetworks": { "message": "Επιπλέον δίκτυα" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Οι εισβολείς μερικές φορές αντιγράφουν ιστότοπους κάνοντας μικρές αλλαγές στη διεύθυνση του ιστότοπου. Βεβαιωθείτε ότι αλληλεπιδράτε με τον ιστότοπο που θέλετε πριν συνεχίσετε." }, + "alertMessageChangeInSimulationResults": { + "message": "Οι εκτιμώμενες αλλαγές για αυτή τη συναλλαγή έχουν ενημερωθεί. Ελέγξτε τις προσεκτικά πριν προχωρήσετε." + }, "alertMessageGasEstimateFailed": { "message": "Δεν μπορούμε να παράσχουμε τα τέλη με ακρίβεια και αυτή η εκτίμηση μπορεί να είναι υψηλή. Σας προτείνουμε να εισάγετε ένα προσαρμοσμένο όριο τελών συναλλαγών, αλλά υπάρχει κίνδυνος η συναλλαγή να αποτύχει και πάλι." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Δεν μπορούμε να συνεχίσουμε με αυτή τη συναλλαγή μέχρι να ενημερώσετε τα τέλη μη αυτόματα." }, - "alertMessagePendingTransactions": { - "message": "Αυτή η συναλλαγή δεν θα πραγματοποιηθεί μέχρι να ολοκληρωθεί μια προηγούμενη συναλλαγή. Μάθετε πώς να ακυρώσετε ή να επισπεύσετε μια συναλλαγή." - }, "alertMessageSignInDomainMismatch": { "message": "Ο ιστότοπος που υποβάλλει το αίτημα δεν είναι ο ιστότοπος στον οποίο έχετε συνδεθεί. Αυτό θα μπορούσε να είναι μια απόπειρα κλοπής των στοιχείων σύνδεσής σας." }, "alertMessageSignInWrongAccount": { "message": "Αυτός ο ιστότοπος σας ζητάει να συνδεθείτε χρησιμοποιώντας λάθος λογαριασμό." }, - "alertMessageSigningOrSubmitting": { - "message": "Αυτή η συναλλαγή θα πραγματοποιηθεί μόνο όταν ολοκληρωθεί η προηγούμενη συναλλαγή σας." - }, "alertModalAcknowledge": { "message": "Αναγνωρίζω τον κίνδυνο και εξακολουθώ να θέλω να συνεχίσω" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Έλεγχος όλων των ειδοποιήσεων" }, + "alertReasonChangeInSimulationResults": { + "message": "Τα αποτελέσματα έχουν αλλάξει" + }, "alertReasonGasEstimateFailed": { "message": "Ανακριβή τέλη" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Αυτόματος εντοπισμός tokens στο πορτοφόλι σας, εμφάνιση NFT και ομαδοποιημένες ενημερώσεις υπολοίπων λογαριασμών" }, - "attemptSendingAssets": { - "message": "Ενδέχεται να χάσετε τα περιουσιακά σας στοιχεία εάν προσπαθήσετε να τα στείλετε από άλλο δίκτυο. Μεταφέρετε κεφάλαια με ασφάλεια μεταξύ δικτύων χρησιμοποιώντας μια διασύνδεση." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Μπορεί να χάσετε τα περιουσιακά σας στοιχεία αν προσπαθήσετε να τα στείλετε από άλλο δίκτυο. Μεταφέρετε χρήματα με ασφάλεια μεταξύ δικτύων χρησιμοποιώντας μια διασύνδεση, όπως το $1" - }, "attemptToCancelSwapForFree": { "message": "Προσπάθεια ακύρωσης των ανταλλαγών δωρεάν" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Διασύνδεση" }, - "bridgeDontSend": { - "message": "Μην στείλετε χωρίς διασύνδεση" + "bridgeCalculatingAmount": { + "message": "Υπολογισμός..." + }, + "bridgeEnterAmount": { + "message": "Πληκτρολογήστε το ποσό" }, "bridgeFrom": { "message": "Γέφυρα από" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Επιλέξτε δίκτυο" }, + "bridgeSelectTokenAndAmount": { + "message": "Επιλέξτε token και ποσό" + }, "bridgeTo": { "message": "Γέφυρα σε" }, @@ -945,9 +942,6 @@ "message": "Κάντε κλικ εδώ για να συνδέσετε το Ledger σας μέσω WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Μπορείτε πάντα να προσθέσετε τα tokens χειροκίνητα." - }, "close": { "message": "Κλείσιμο" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Επιβεβαιώστε τη Μυστική Φράση Ανάκτησης" }, - "confirmTitleApproveTransaction": { - "message": "Αίτημα χορήγησης άδειας" - }, "confirmTitleDeployContract": { "message": "Ανάπτυξη συμβολαίου" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Αίτημα συναλλαγής" }, - "confirmationAlertModalDetails": { - "message": "Για να προστατεύσετε τα περιουσιακά σας στοιχεία και τις πληροφορίες σύνδεσης, σας προτείνουμε να απορρίψετε το αίτημα." - }, "confirmationAlertModalTitle": { "message": "Αυτό το αίτημα είναι ύποπτο" }, @@ -1160,6 +1148,14 @@ "message": "Συνδέεται με $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 δίκτυα συνδεδεμένα", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Συνδέθηκε με το $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Σύνδεση" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Επιλογές αγοράς με χρεωστική ή πιστωτική κάρτα" + }, "decimal": { "message": "Δεκαδικά ψηφία του token" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Το ανώτατο όριο δαπανών δεν μπορεί να υπερβαίνει τα $1 δεκαδικά ψηφία. Αφαιρέστε τα δεκαδικά ψηφία για να συνεχίσετε." }, + "editSpendingCapSpecialCharError": { + "message": "Πληκτρολογήστε μόνο αριθμούς" + }, "enableAutoDetect": { "message": " Ενεργοποίηση αυτόματου εντοπισμού" }, @@ -1904,10 +1906,42 @@ "message": "Κωδικός: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Επικοινωνήστε με την υποστήριξη", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Περιγράψτε τι έχει συμβεί", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Οι πληροφορίες σας δεν μπορούν να εμφανιστούν. Μην ανησυχείτε, το πορτοφόλι και τα κεφάλαιά σας είναι ασφαλή.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Μήνυμα σφάλματος", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Περιγράψτε τι έχει συμβεί", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Η παροχή λεπτομερειών, όπως ο τρόπος με τον οποίο μπορούμε να επαναλαμβάνουμε το σφάλμα, θα μας βοηθήσει να διορθώσουμε το πρόβλημα.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Ευχαριστούμε! Θα ρίξουμε μια ματιά σύντομα.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "Το MetaMask αντιμετώπισε ένα σφάλμα", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Προσπαθήστε ξανά", + "description": "Button for try again" + }, "errorStack": { "message": "Στοίβα:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Τύπος λειτουργίας" }, + "fundingMethod": { + "message": "Μέθοδος χρηματοδότησης" + }, "gas": { "message": "Τέλος συναλλαγής" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Τέλη συναλλαγών" }, - "gasIsETH": { - "message": "Τέλη συναλλαγής $1 " - }, "gasLimit": { "message": "Όριο τέλους συναλλαγής" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "απάτες και κίνδυνοι ασφάλειας." }, - "learnToBridge": { - "message": "Μάθετε για την διασύνδεση" - }, "leaveMetaMask": { "message": "Αποχώρηση από το MetaMask;" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Ολοκληρώστε τη συναλλαγή στο Snap." }, - "loadingTokens": { - "message": "Φόρτωση των tokens..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Οι ειδοποιήσεις του πορτοφολιού δεν είναι προς το παρόν ενεργές." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "Το MetaMask Swaps είναι υπό συντήρηση. Παρακαλείστε να επιστρέψετε αργότερα." }, @@ -2992,10 +3017,6 @@ "message": "Το $1 ζητάει την έγκρισή σας για:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Το αρχικό token σε αυτό το δίκτυο είναι το $1. Είναι το token που χρησιμοποιείται για τα τέλη συναλλαγών.", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Επεξεργασία λεπτομερειών δικτύου" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Όχι, ευχαριστώ" }, - "noTransactions": { - "message": "Δεν έχετε καμιά συναλλαγή" - }, "noWebcamFound": { "message": "Η κάμερα του υπολογιστή σας δεν βρέθηκε. Προσπαθήστε ξανά." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Το πρόγραμμα περιήγησής σας δεν είναι ενημερωμένο. Εάν δεν ενημερώσετε το πρόγραμμα περιήγησής σας, δεν θα μπορείτε να λαμβάνετε διορθώσεις ασφαλείας και νέες λειτουργίες από το MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Επικεφαλίδα Παράκαμψης Περιεχομένου-Ασφάλεια-Πολιτική" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Αυτή η επιλογή είναι μια λύση για ένα γνωστό πρόβλημα στον Firefox, όπου η επικεφαλίδα Παράκαμψης Περιεχομένου-Ασφάλεια-Πολιτική μιας αποκεντρωμένης εφαρμογής μπορεί να εμποδίσει τη σωστή λειτουργία μιας επέκτασης. Η απενεργοποίηση αυτής της επιλογής δεν συνιστάται, εκτός αν απαιτείται για συγκεκριμένη συμβατότητα ιστοσελίδας." + }, "padlock": { "message": "Padlock" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Εδώ μπορείτε να δείτε τις άδειες χρήσης που έχετε δώσει στα εγκατεστημένα Snaps ή στους συνδεδεμένους ιστότοπους." }, - "permissionsPageTourDescription": { - "message": "Αυτός είναι ο πίνακας ελέγχου για τη διαχείριση των αδειών χρήσης που έχετε δώσει στους συνδεδεμένους ιστότοπους και στα εγκατεστημένα Snaps." - }, - "permissionsPageTourTitle": { - "message": "Οι συνδεδεμένοι ιστότοποι είναι τώρα με άδειες χρήσης" - }, "permitSimulationDetailInfo": { "message": "Δίνετε στον διαθέτη την άδεια να δαπανήσει τα tokens από τον λογαριασμό σας." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Προστατέψτε τα κεφάλαιά σας" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Αιτήματα βελτιωμένων υπογραφών" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Ενεργοποιήστε το για να δείτε αιτήματα υπογραφών σε βελτιωμένη μορφή." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Βελτιωμένα αιτήματα συναλλαγών" - }, - "redesignedTransactionsToggleDescription": { - "message": "Ενεργοποιήστε το για να δείτε τα αιτήματα συναλλαγών σε βελτιωμένη μορφή." - }, "refreshList": { "message": "Ανανέωση λίστας" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Αυτός είναι ο ιστότοπος που ζητά την υπογραφή σας." }, + "requestFromInfoSnap": { + "message": "Αυτό είναι το Snap που ζητάει την υπογραφή σας." + }, "requestFromTransactionDescription": { "message": "Αυτός είναι ο ιστότοπος που ζητά την επιβεβαίωσή σας." }, @@ -4417,6 +4426,10 @@ "message": "Ζητήθηκε για $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Ζητάει για το $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "αιτήματα που περιμένουν να επιβεβαιωθούν" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Λαμβάνετε" }, + "simulationDetailsNoChanges": { + "message": "Δεν υπάρχουν αλλαγές" + }, "simulationDetailsOutgoingHeading": { "message": "Στέλνετε" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Αυτή η συναλλαγή είναι πιθανό να αποτύχει" }, + "simulationDetailsUnavailable": { + "message": "Μη διαθέσιμο" + }, "simulationErrorMessageV2": { "message": "Δεν ήμασταν σε θέση να εκτιμήσουμε το τέλος συναλλαγής. Μπορεί να υπάρχει σφάλμα στο συμβόλαιο και η συναλλαγή αυτή να αποτύχει." }, @@ -5143,6 +5162,15 @@ "message": "Επικοινωνήστε με τους διαχειριστές του $1 για περαιτέρω υποστήριξη.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Η ενεργοποίηση αυτής της λειτουργίας θα σας δώσει τη δυνατότητα να προσθέσετε έναν λογαριασμό Solana στην επέκταση του MetaMask που προέρχεται από την υπάρχουσα Μυστική Φράση Ανάκτησης. Πρόκειται για μια πειραματική λειτουργία Beta, οπότε θα πρέπει να τη χρησιμοποιήσετε με δική σας ευθύνη." + }, + "solanaSupportToggleTitle": { + "message": "Ενεργοποίηση της λειτουργίας \"Προσθήκη νέου λογαριασμού Solana (Beta)\"" + }, "somethingDoesntLookRight": { "message": "Κάτι δεν φαίνεται σωστό; $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Ενδεχομένως μη αυθεντικό token" }, + "swapTokenVerifiedSources": { + "message": "Επιβεβαιώνεται από $1 πηγές. Επαληθεύστε στο $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "Το $1 επιτρέπει έως και $2 δεκαδικά ψηφία", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "Το $1 είναι τώρα ενεργό στο $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Τώρα χρησιμοποιείτε το" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Η αλλαγή δικτύων θα ακυρώσει όλες τις εκκρεμείς επιβεβαιώσεις" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Επιλέξτε το προτιμώμενο θέμα σας για το MetaMask." }, - "thingsToKeep": { - "message": "Πράγματα που πρέπει να έχετε υπόψη σας:" - }, "thirdPartySoftware": { "message": "Ειδοποίηση για λογισμικό τρίτων", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Συμβουλές" }, + "tipsForUsingAWallet": { + "message": "Συμβουλές για τη χρήση πορτοφολιού" + }, + "tipsForUsingAWalletDescription": { + "message": "Η προσθήκη tokens ξεκλειδώνει περισσότερους τρόπους χρήσης του web3." + }, "to": { "message": "Προς" }, @@ -5925,18 +5957,6 @@ "message": "Προς: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Αυτό σας επιτρέπει να επιλέξετε ένα δίκτυο για κάθε ιστότοπο αντί για ένα μόνο επιλεγμένο δίκτυο για όλους τους ιστότοπους. Αυτή η λειτουργία θα σας αποτρέψει από το να αλλάζετε δίκτυα χειροκίνητα, το οποίο μπορεί να διαταράξει την εμπειρία του χρήστη σε ορισμένους ιστότοπους." - }, - "toggleRequestQueueField": { - "message": "Επιλογή δικτύων για κάθε ιστότοπο" - }, - "toggleRequestQueueOff": { - "message": "Απενεργοποίηση" - }, - "toggleRequestQueueOn": { - "message": "Ενεργοποίηση" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Λίστες με tokens" }, + "tokenMarketplace": { + "message": "Αγορά tokens" + }, "tokenScamSecurityRisk": { "message": "απάτες με token και κίνδυνοι ασφάλειας" }, - "tokenShowUp": { - "message": "Τα tokens σας ενδέχεται να μην εμφανιστούν αυτόματα στο πορτοφόλι σας." - }, "tokenStandard": { "message": "Πρότυπο Token" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Αποκωδικοποίηση έξυπνων συμβολαίων" }, - "use4ByteResolutionDescription": { - "message": "Για να βελτιώσουμε την εμπειρία του χρήστη, προσαρμόζουμε την καρτέλα δραστηριότητας με μηνύματα που βασίζονται στα έξυπνα συμβόλαια με τα οποία αλληλεπιδράτε. Το MetaMask χρησιμοποιεί μια υπηρεσία που ονομάζεται 4byte.directory για την αποκωδικοποίηση δεδομένων και την εμφάνιση μιας έκδοσης ενός έξυπνου συμβολαίου που είναι πιο ευανάγνωστο. Αυτό συμβάλλει στη μείωση των πιθανοτήτων σας να εγκρίνετε κακόβουλες ενέργειες έξυπνων συμβολαίων, αλλά μπορεί να έχει ως αποτέλεσμα την κοινοποίηση της διεύθυνσης IP σας." - }, "useMultiAccountBalanceChecker": { "message": "Μαζικά αιτήματα υπολοίπου λογαριασμού" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Σύμφωνα με τα αρχεία μας, το όνομα του δικτύου ενδέχεται να μην αντιστοιχεί με αυτό το αναγνωριστικό αλυσίδας." }, - "xOfYPending": { - "message": "$1 από $2 σε εκκρεμότητα", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Ναι" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index e9e9cc807ccd..c840716b6aae 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Adding Network" }, - "addingTokens": { - "message": "Adding tokens" - }, "additionalNetworks": { "message": "Additional networks" }, @@ -440,18 +437,12 @@ "alertMessageNoGasPrice": { "message": "We can’t move forward with this transaction until you manually update the fee." }, - "alertMessagePendingTransactions": { - "message": "This transaction won’t go through until a previous transaction is complete. Learn how to cancel or speed up a transaction." - }, "alertMessageSignInDomainMismatch": { "message": "The site making the request is not the site you’re signing into. This could be an attempt to steal your login credentials." }, "alertMessageSignInWrongAccount": { "message": "This site is asking you to sign in using the wrong account." }, - "alertMessageSigningOrSubmitting": { - "message": "This transaction will only go through once your previous transaction is complete." - }, "alertModalAcknowledge": { "message": "I have acknowledged the risk and still want to proceed" }, @@ -510,8 +501,7 @@ "message": "No accounts available to connect" }, "allNetworks": { - "message": "All networks", - "description": "Speicifies to token network filter to filter by all Networks" + "message": "All networks" }, "allOfYour": { "message": "All of your $1", @@ -584,6 +574,9 @@ "message": "MetaMask Institutional", "description": "The name of the application (MMI)" }, + "apply": { + "message": "Apply" + }, "approve": { "message": "Approve spend limit" }, @@ -637,21 +630,21 @@ "asset": { "message": "Asset" }, + "assetMultipleNFTsBalance": { + "message": "$1 NFTs" + }, "assetOptions": { "message": "Asset options" }, + "assetSingleNFTBalance": { + "message": "$1 NFT" + }, "assets": { "message": "Assets" }, "assetsDescription": { "message": "Autodetect tokens in your wallet, display NFTs, and get batched account balance updates" }, - "attemptSendingAssets": { - "message": "You may lose your assets if you try to send them from another network. Transfer funds safely between networks by using a bridge." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "You may lose your assets if you try to send them from another network. Transfer funds safely between networks by using a bridge, like $1" - }, "attemptToCancelSwapForFree": { "message": "Attempt to cancel swap for free" }, @@ -744,12 +737,14 @@ "beCareful": { "message": "Be careful" }, + "bestPrice": { + "message": "Best price" + }, "beta": { "message": "Beta" }, "betaHeaderText": { - "message": "This is a beta version. Please report bugs $1", - "description": "$1 represents the word 'here' in a hyperlink" + "message": "This is a beta version. Please report bugs $1" }, "betaMetamaskInstitutionalVersion": { "message": "MetaMask Institutional Beta Version" @@ -856,43 +851,132 @@ "bridge": { "message": "Bridge" }, + "bridgeAllowSwappingOf": { + "message": "Allow exact access to $1 $2 on $3 for bridging", + "description": "Shows a user that they need to allow a token for swapping on their hardware wallet" + }, "bridgeApproval": { "message": "Approve $1 for bridge", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be bridged. $1 is the symbol of a token that has been approved." }, + "bridgeApprovalWarning": { + "message": "You are allowing access to the specified amount, $1 $2. The contract will not access any additional funds." + }, + "bridgeApprovalWarningForHardware": { + "message": "You will need to allow access to $1 $2 for bridging, and then approve bridging to $2. This will require two separate confirmations." + }, "bridgeCalculatingAmount": { "message": "Calculating..." }, - "bridgeDontSend": { - "message": "Bridge, don't send" + "bridgeConfirmTwoTransactions": { + "message": "You'll need to confirm 2 transactions on your hardware wallet:" }, "bridgeEnterAmount": { - "message": "Enter amount" + "message": "Select amount" + }, + "bridgeExplorerLinkViewOn": { + "message": "View on $1" + }, + "bridgeFetchNewQuotes": { + "message": "Fetch a new one?" }, "bridgeFrom": { "message": "Bridge from" }, + "bridgeFromTo": { + "message": "Bridge $1 $2 to $3", + "description": "Tells a user that they need to confirm on their hardware wallet a bridge. $1 is amount of source token, $2 is the source network, and $3 is the destination network" + }, + "bridgeGasFeesSplit": { + "message": "Any network fee quoted on the previous screen includes both transactions and will be split." + }, "bridgeNetCost": { "message": "Net cost" }, + "bridgeQuoteExpired": { + "message": "Your quote timed out." + }, "bridgeSelectNetwork": { "message": "Select network" }, "bridgeSelectTokenAndAmount": { "message": "Select token and amount" }, + "bridgeStepActionBridgeComplete": { + "message": "$1 received on $2", + "description": "$1 is the amount of the destination asset, $2 is the name of the destination network" + }, + "bridgeStepActionBridgePending": { + "message": "Receiving $1 on $2", + "description": "$1 is the amount of the destination asset, $2 is the name of the destination network" + }, + "bridgeStepActionSwapComplete": { + "message": "Swapped $1 for $2", + "description": "$1 is the amount of the source asset, $2 is the amount of the destination asset" + }, + "bridgeStepActionSwapPending": { + "message": "Swapping $1 for $2", + "description": "$1 is the amount of the source asset, $2 is the amount of the destination asset" + }, + "bridgeTerms": { + "message": "Terms" + }, "bridgeTimingMinutes": { "message": "$1 min", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, - "bridgeTimingTooltipText": { - "message": "This is the estimated time it will take for the bridging to be complete." - }, "bridgeTo": { "message": "Bridge to" }, - "bridgeTotalFeesTooltipText": { - "message": "This includes gas fees (paid to crypto miners) and relayer fees (paid to power complex services like bridging).\nFees are based on network traffic and transaction complexity. MetaMask does not profit from either fee." + "bridgeToChain": { + "message": "Bridge to $1" + }, + "bridgeTxDetailsBridging": { + "message": "Bridging" + }, + "bridgeTxDetailsDelayedDescription": { + "message": "Reach out to" + }, + "bridgeTxDetailsDelayedDescriptionSupport": { + "message": "MetaMask Support" + }, + "bridgeTxDetailsDelayedTitle": { + "message": "Has it been longer than 3 hours?" + }, + "bridgeTxDetailsNonce": { + "message": "Nonce" + }, + "bridgeTxDetailsStatus": { + "message": "Status" + }, + "bridgeTxDetailsTimestamp": { + "message": "Time stamp" + }, + "bridgeTxDetailsTimestampValue": { + "message": "$1 at $2", + "description": "$1 is the date, $2 is the time" + }, + "bridgeTxDetailsTokenAmountOnChain": { + "message": "$1 $2 on", + "description": "$1 is the amount of the token, $2 is the ticker symbol of the token" + }, + "bridgeTxDetailsTotalGasFee": { + "message": "Total gas fee" + }, + "bridgeTxDetailsYouReceived": { + "message": "You received" + }, + "bridgeTxDetailsYouSent": { + "message": "You sent" + }, + "bridgeValidationInsufficientGasMessage": { + "message": "You don't have enough $1 to pay the gas fee for this bridge. Enter a smaller amount or buy more $1." + }, + "bridgeValidationInsufficientGasTitle": { + "message": "More $1 needed for gas" + }, + "bridging": { + "message": "Bridging" }, "browserNotSupported": { "message": "Your browser is not supported..." @@ -903,6 +987,9 @@ "builtAroundTheWorld": { "message": "MetaMask is designed and built around the world." }, + "bulletpoint": { + "message": "·" + }, "busy": { "message": "Busy" }, @@ -990,9 +1077,6 @@ "message": "Click here to connect your Ledger via WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "You can always add tokens manually." - }, "close": { "message": "Close" }, @@ -1057,8 +1141,8 @@ "confirmRecoveryPhrase": { "message": "Confirm Secret Recovery Phrase" }, - "confirmTitleApproveTransaction": { - "message": "Allowance request" + "confirmTitleApproveTransactionNFT": { + "message": "Withdrawal request" }, "confirmTitleDeployContract": { "message": "Deploy a contract" @@ -1099,8 +1183,8 @@ "confirmTitleTransaction": { "message": "Transaction request" }, - "confirmationAlertModalDetails": { - "message": "To protect your assets and login information, we suggest you reject the request." + "confirmationAlertDetails": { + "message": "To protect your assets, we suggest you reject the request." }, "confirmationAlertModalTitle": { "message": "This request is suspicious" @@ -1363,6 +1447,9 @@ "crossChainSwapsLink": { "message": "Swap across networks with MetaMask Portfolio" }, + "crossChainSwapsLinkNative": { + "message": "Swap across networks with Bridge" + }, "cryptoCompare": { "message": "CryptoCompare" }, @@ -1474,6 +1561,9 @@ "message": "Use $1 to customize the gas price. This can be confusing if you aren’t familiar. Interact at your own risk.", "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight" }, + "customSlippage": { + "message": "Custom" + }, "customSpendLimit": { "message": "Custom spend limit" }, @@ -2033,9 +2123,6 @@ "estimatedFeeTooltip": { "message": "Amount paid to process the transaction on network." }, - "estimatedTime": { - "message": "Estimated time" - }, "ethGasPriceFetchWarning": { "message": "Backup gas price is provided as the main gas estimation service is unavailable right now." }, @@ -2197,9 +2284,6 @@ "gasFee": { "message": "Gas fee" }, - "gasIsETH": { - "message": "Gas is $1 " - }, "gasLimit": { "message": "Gas limit" }, @@ -2285,6 +2369,9 @@ "gotIt": { "message": "Got it" }, + "grantExactAccess": { + "message": "Grant exact access" + }, "grantedToWithColon": { "message": "Granted to:" }, @@ -2413,6 +2500,12 @@ "holdToRevealUnlockedLabel": { "message": "hold to reveal circle unlocked" }, + "howQuotesWork": { + "message": "How quotes work" + }, + "howQuotesWorkExplanation": { + "message": "This quote has the best return of the quotes we searched. This is based on the swap rate, which includes bridging fees and a $1% MetaMask fee, minus gas fees. Gas fees depend on how busy the network is and how complex the transaction is." + }, "id": { "message": "ID" }, @@ -2761,9 +2854,6 @@ "learnScamRisk": { "message": "scams and security risks." }, - "learnToBridge": { - "message": "Learn to bridge" - }, "leaveMetaMask": { "message": "Leave MetaMask?" }, @@ -2855,8 +2945,8 @@ "loadingScreenSnapMessage": { "message": "Please complete the transaction on the Snap." }, - "loadingTokens": { - "message": "Loading tokens..." + "loadingTokenList": { + "message": "Loading token list" }, "localhost": { "message": "Localhost 8545" @@ -2877,6 +2967,12 @@ "low": { "message": "Low" }, + "lowEstimatedReturnTooltipMessage": { + "message": "You’ll pay more than $1% of your starting amount in fees. Check your receiving amount and network fees." + }, + "lowEstimatedReturnTooltipTitle": { + "message": "High cost" + }, "lowGasSettingToolTipMessage": { "message": "Use $1 to wait for a cheaper price. Time estimates are much less accurate as prices are somewhat unpredictable.", "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight" @@ -2959,9 +3055,6 @@ "metamaskNotificationsAreOff": { "message": "Wallet notifications are currently not active." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps is undergoing maintenance. Please check back later." }, @@ -3033,6 +3126,9 @@ "message": "+ $1 more networks", "description": "$1 is the number of networks" }, + "moreQuotes": { + "message": "More quotes" + }, "multichainAddEthereumChainConfirmationDescription": { "message": "You're adding this network to MetaMask and giving this site permission to use it." }, @@ -3109,10 +3205,6 @@ "message": "$1 is asking for your approval to:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "The native token on this network is $1. It is the token used for gas fees. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Edit network details" }, @@ -3160,6 +3252,9 @@ "networkFee": { "message": "Network fee" }, + "networkFees": { + "message": "Network fees" + }, "networkIsBusy": { "message": "Network is busy. Gas prices are high and estimates are less accurate." }, @@ -3394,15 +3489,15 @@ "noNetworksFound": { "message": "No networks found for the given search query" }, + "noOptionsAvailableMessage": { + "message": "This trade route isn't available right now. Try changing the amount, network, or token and we'll find the best option." + }, "noSnaps": { "message": "You don't have any snaps installed." }, "noThanks": { "message": "No thanks" }, - "noTransactions": { - "message": "You have no transactions" - }, "noWebcamFound": { "message": "Your computer's webcam was not found. Please try again." }, @@ -3901,6 +3996,14 @@ "pending": { "message": "Pending" }, + "pendingTransactionAlertMessage": { + "message": "This transaction won't go through until a previous transaction is complete. $1", + "description": "$1 represents the words 'how to cancel or speed up a transaction' in a hyperlink" + }, + "pendingTransactionAlertMessageHyperlink": { + "message": "Learn how to cancel or speed up a transaction.", + "description": "The text for the hyperlink in the pending transaction alert message" + }, "pendingTransactionInfo": { "message": "This transaction will not process until that one is complete." }, @@ -4158,12 +4261,6 @@ "permissionsPageEmptySubContent": { "message": "This is where you can see the permissions you've given to installed Snaps or connected sites." }, - "permissionsPageTourDescription": { - "message": "This is your control panel for managing permissions given to connected sites and installed Snaps." - }, - "permissionsPageTourTitle": { - "message": "Connected sites are now permissions" - }, "permitSimulationChange_approve": { "message": "Spending cap" }, @@ -4173,6 +4270,9 @@ "permitSimulationChange_listing": { "message": "You list" }, + "permitSimulationChange_nft_listing": { + "message": "Listing price" + }, "permitSimulationChange_receive": { "message": "You receive" }, @@ -4221,6 +4321,9 @@ "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity. $1", "description": "$1 is Learn more link" }, + "popularNetworks": { + "message": "Popular networks" + }, "portfolio": { "message": "Portfolio" }, @@ -4385,16 +4488,18 @@ "quoteRate": { "message": "Quote rate" }, - "quotedNetworkFee": { "message": "$1 network fee" }, "quotedReceiveAmount": { "message": "$1 receive amount" }, - "quotedReceivingAmount": { - "message": "$1 receiving" + "quotedTotalCost": { + "message": "$1 total cost" }, "rank": { "message": "Rank" }, + "rateIncludesMMFee": { + "message": "Rate includes $1% fee" + }, "reAddAccounts": { "message": "re-add any other accounts" }, @@ -4440,18 +4545,6 @@ "recoveryPhraseReminderTitle": { "message": "Protect your funds" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Improved signature requests" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Turn this on to see signature requests in an enhanced format." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Improved transaction requests" - }, - "redesignedTransactionsToggleDescription": { - "message": "Turn this on to see transactions requests in an enhanced format." - }, "refreshList": { "message": "Refresh list" }, @@ -4737,6 +4830,9 @@ "searchTokens": { "message": "Search tokens" }, + "searchTokensByNameOrAddress": { + "message": "Search tokens by name or address" + }, "secretRecoveryPhrase": { "message": "Secret Recovery Phrase" }, @@ -5047,6 +5143,9 @@ "signingInWith": { "message": "Signing in with" }, + "signingWith": { + "message": "Signing with" + }, "simulationApproveHeading": { "message": "Withdraw" }, @@ -5099,6 +5198,9 @@ "simulationsSettingSubHeader": { "message": "Estimate balance changes" }, + "singleNetwork": { + "message": "1 network" + }, "siweIssued": { "message": "Issued" }, @@ -5123,6 +5225,30 @@ "skipAccountSecurityDetails": { "message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets." }, + "slideBridgeDescription": { + "message": "Move across 9 chains, all within your wallet" + }, + "slideBridgeTitle": { + "message": "Ready to bridge?" + }, + "slideCashOutDescription": { + "message": "Sell your crypto for cash" + }, + "slideCashOutTitle": { + "message": "Cash out with MetaMask" + }, + "slideDebitCardDescription": { + "message": "Available in selected regions" + }, + "slideDebitCardTitle": { + "message": "MetaMask debit card" + }, + "slideFundWalletDescription": { + "message": "Get started by adding funds" + }, + "slideFundWalletTitle": { + "message": "Fund your wallet" + }, "smartContracts": { "message": "Smart contracts" }, @@ -5153,6 +5279,15 @@ "smartTransactions": { "message": "Smart Transactions" }, + "smartTransactionsEnabledDescription": { + "message": " and MEV protection. Now on by default." + }, + "smartTransactionsEnabledLink": { + "message": "Higher success rates" + }, + "smartTransactionsEnabledTitle": { + "message": "Transactions just got smarter" + }, "snapAccountCreated": { "message": "Account created" }, @@ -5328,6 +5463,9 @@ "solanaSupportToggleTitle": { "message": "Enable \"Add a new Solana account (Beta)\"" }, + "someNetworks": { + "message": "$1 networks" + }, "somethingDoesntLookRight": { "message": "Something doesn't look right? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -6055,9 +6193,6 @@ "message": "You're now using $1", "description": "$1 represents the network name" }, - "switchedTo": { - "message": "You're now using" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Switching networks will cancel all pending confirmations" }, @@ -6094,9 +6229,6 @@ "themeDescription": { "message": "Choose your preferred MetaMask theme." }, - "thingsToKeep": { - "message": "Keep in mind:" - }, "thirdPartySoftware": { "message": "Third-party software notice", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -6127,17 +6259,8 @@ "message": "To: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "This allows you to select a network for each site instead of a single selected network for all sites. This feature will prevent you from switching networks manually, which may break your user experience on certain sites." - }, - "toggleRequestQueueField": { - "message": "Select networks for each site" - }, - "toggleRequestQueueOff": { - "message": "Off" - }, - "toggleRequestQueueOn": { - "message": "On" + "toggleDecodeDescription": { + "message": "We use 4byte.directory and Sourcify services to decode and display more readable transaction data. This helps you understand the outcome of pending and past transactions, but can result in your IP address being shared." }, "token": { "message": "Token" @@ -6178,9 +6301,6 @@ "tokenScamSecurityRisk": { "message": "token scams and security risks" }, - "tokenShowUp": { - "message": "Your tokens may not automatically show up in your wallet. " - }, "tokenStandard": { "message": "Token standard" }, @@ -6212,9 +6332,6 @@ "total": { "message": "Total" }, - "totalFees": { - "message": "Total fees" - }, "totalVolume": { "message": "Total volume" }, @@ -6278,6 +6395,9 @@ "transactionFailed": { "message": "Transaction Failed" }, + "transactionFailedBannerMessage": { + "message": "This transaction would have cost you extra fees, so we stopped it. Your money is still in your wallet." + }, "transactionFee": { "message": "Transaction fee" }, @@ -6496,9 +6616,6 @@ "use4ByteResolution": { "message": "Decode smart contracts" }, - "use4ByteResolutionDescription": { - "message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared." - }, "useMultiAccountBalanceChecker": { "message": "Batch account balance requests" }, @@ -6662,7 +6779,7 @@ "message": "Welcome back!" }, "welcomeExploreDescription": { - "message": "Store, send and spend crypto currencies and assets." + "message": "Store, send, and spend crypto currencies and assets." }, "welcomeExploreTitle": { "message": "Explore decentralized apps" @@ -6686,22 +6803,27 @@ "whatsThis": { "message": "What's this?" }, + "willApproveAmountForBridging": { + "message": "This will approve $1 for bridging." + }, + "willApproveAmountForBridgingHardware": { + "message": "You’ll need to confirm two transactions on your hardware wallet." + }, "withdrawing": { "message": "Withdrawing" }, "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, - "xOfYPending": { - "message": "$1 of $2 pending", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Yes" }, "you": { "message": "You" }, + "youDeclinedTheTransaction": { + "message": "You declined the transaction." + }, "youNeedToAllowCameraAccess": { "message": "You need to allow camera access to use this feature." }, @@ -6723,6 +6845,9 @@ "yourNFTmayBeAtRisk": { "message": "Your NFT may be at risk" }, + "yourNetworks": { + "message": "Your networks" + }, "yourPrivateSeedPhrase": { "message": "Your Secret Recovery Phrase" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 92ad7c646a36..70ea7e149663 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -421,18 +421,12 @@ "alertMessageNoGasPrice": { "message": "We can’t move forward with this transaction until you manually update the fee." }, - "alertMessagePendingTransactions": { - "message": "This transaction won’t go through until a previous transaction is complete. Learn how to cancel or speed up a transaction." - }, "alertMessageSignInDomainMismatch": { "message": "The site making the request is not the site you’re signing into. This could be an attempt to steal your login credentials." }, "alertMessageSignInWrongAccount": { "message": "This site is asking you to sign in using the wrong account." }, - "alertMessageSigningOrSubmitting": { - "message": "This transaction will only go through once your previous transaction is complete." - }, "alertModalAcknowledge": { "message": "I have acknowledged the risk and still want to proceed" }, @@ -2647,9 +2641,6 @@ "loadingScreenSnapMessage": { "message": "Please complete the transaction on the Snap." }, - "loadingTokens": { - "message": "Loading tokens..." - }, "localhost": { "message": "Localhost 8545" }, @@ -3150,9 +3141,6 @@ "noThanks": { "message": "No thanks" }, - "noTransactions": { - "message": "You have no transactions" - }, "noWebcamFound": { "message": "Your computer's webcam was not found. Please try again." }, @@ -4158,12 +4146,6 @@ "recoveryPhraseReminderTitle": { "message": "Protect your funds" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Improved signature requests" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Turn this on to see signature requests in an enhanced format." - }, "refreshList": { "message": "Refresh list" }, @@ -5756,18 +5738,6 @@ "toggleEthSignOn": { "message": "ON (Not recommended)" }, - "toggleRequestQueueDescription": { - "message": "This allows you to select a network for each site instead of a single selected network for all sites. This feature will prevent you from switching networks manually, which may break your user experience on certain sites." - }, - "toggleRequestQueueField": { - "message": "Select networks for each site" - }, - "toggleRequestQueueOff": { - "message": "Off" - }, - "toggleRequestQueueOn": { - "message": "On" - }, "token": { "message": "Token" }, @@ -6310,10 +6280,6 @@ "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, - "xOfYPending": { - "message": "$1 of $2 pending", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Yes" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index f2afe012534a..963742e8a605 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Agregando red" }, - "addingTokens": { - "message": "Agregando tokens" - }, "additionalNetworks": { "message": "Redes adicionales" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "A veces, los atacantes imitan sitios web haciendo pequeños cambios en la dirección del sitio. Asegúrese de estar interactuando con el sitio deseado antes de continuar." }, + "alertMessageChangeInSimulationResults": { + "message": "Se actualizaron los cambios estimados para esta transacción. Revíselos detenidamente antes de proceder." + }, "alertMessageGasEstimateFailed": { "message": "No podemos proporcionar una tarifa exacta y esta estimación podría ser alta. Le sugerimos que ingrese un límite de gas personalizado, pero existe el riesgo de que la transacción aún falle." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "No podemos seguir adelante con esta transacción hasta que actualice manualmente la tarifa." }, - "alertMessagePendingTransactions": { - "message": "Esta transacción no se realizará hasta que se complete una transacción anterior. Aprenda cómo cancelar o acelerar una transacción." - }, "alertMessageSignInDomainMismatch": { "message": "El sitio que realiza la solicitud no es el sitio en el que está iniciando sesión. Esto podría ser un intento de robar sus credenciales de inicio de sesión." }, "alertMessageSignInWrongAccount": { "message": "Este sitio le pide que inicie sesión con la cuenta incorrecta." }, - "alertMessageSigningOrSubmitting": { - "message": "Esta transacción solo se realizará una vez que se complete la transacción anterior." - }, "alertModalAcknowledge": { "message": "Soy consciente del riesgo y aun así deseo continuar" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Revisar todas las alertas" }, + "alertReasonChangeInSimulationResults": { + "message": "Los resultados cambiaron" + }, "alertReasonGasEstimateFailed": { "message": "Tarifa inexacta" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Detectar automáticamente tokens en su monedero, mostrar NFT y recibir actualizaciones de saldo de cuenta por lotes" }, - "attemptSendingAssets": { - "message": "Puede perder sus activos si intenta enviarlos desde otra red. Transfiera fondos de forma segura entre redes mediante el uso de un puente." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Puede perder sus activos si intenta enviarlos desde otra red. Transfiera fondos de forma segura entre redes usando un puente, como $1" - }, "attemptToCancelSwapForFree": { "message": "Intente cancelar el intercambio de forma gratuita" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Puente" }, - "bridgeDontSend": { - "message": "Puente, no enviar" + "bridgeCalculatingAmount": { + "message": "Calculando..." + }, + "bridgeEnterAmount": { + "message": "Ingrese el monto" }, "bridgeFrom": { "message": "Puentear desde" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Seleccionar red" }, + "bridgeSelectTokenAndAmount": { + "message": "Seleccione token y monto" + }, "bridgeTo": { "message": "Puentear hacia" }, @@ -945,9 +942,6 @@ "message": "Haga clic aquí para conectar su Ledger a través de WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Siempre puede agregar tókenes manualmente." - }, "close": { "message": "Cerrar" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Confirmar frase secreta de recuperación" }, - "confirmTitleApproveTransaction": { - "message": "Solicitud de asignación" - }, "confirmTitleDeployContract": { "message": "Implementar un contrato" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Solicitud de transacción" }, - "confirmationAlertModalDetails": { - "message": "Para proteger sus activos e información de inicio de sesión, le sugerimos que rechace la solicitud." - }, "confirmationAlertModalTitle": { "message": "Esta solicitud es sospechosa" }, @@ -1160,6 +1148,14 @@ "message": "Conectado con $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 redes conectadas", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Conectado con $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Conectando" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Opciones de compra con tarjeta de débito o crédito" + }, "decimal": { "message": "Decimales del token" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "El límite de gasto no puede superar $1 dígitos decimales. Elimine los dígitos decimales para continuar." }, + "editSpendingCapSpecialCharError": { + "message": "Ingrese solo números" + }, "enableAutoDetect": { "message": " Activar autodetección" }, @@ -1904,10 +1906,42 @@ "message": "Código: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Comuníquese con soporte", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Describa lo sucedido", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "No se puede mostrar su información. No se preocupe, su monedero y sus fondos están a salvo.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Mensaje de error", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Describa lo sucedido", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Compartir detalles como la forma de reproducir el error nos ayudará a solucionar el problema.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "¡Gracias! Lo revisaremos en breve.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask encontró un error", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Vuelva a intentarlo", + "description": "Button for try again" + }, "errorStack": { "message": "Pila:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Tipo de función" }, + "fundingMethod": { + "message": "Método de financiación" + }, "gas": { "message": "Gas" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Tarifa de gas" }, - "gasIsETH": { - "message": "El gas es $1 " - }, "gasLimit": { "message": "Límite de gas" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "estafas y riesgos en seguridad." }, - "learnToBridge": { - "message": "Aprenda a puentear" - }, "leaveMetaMask": { "message": "¿Dejar MetaMask?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Por favor, complete la transacción en el Snap." }, - "loadingTokens": { - "message": "Cargando tokens..." - }, "localhost": { "message": "Host local 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Las notificaciones del monedero no están activas actualmente." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps está en mantenimiento. Vuelva a comprobarlo más tarde." }, @@ -2992,10 +3017,6 @@ "message": "$1 solicita su aprobación para:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "El token nativo en esta red es de $1. Es el token utilizado para las tarifas de gas. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Editar detalles de la red" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "No, gracias" }, - "noTransactions": { - "message": "No tiene transacciones" - }, "noWebcamFound": { "message": "No se encontró la cámara web del equipo. Vuelva a intentarlo." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Su navegador está desactualizado. Si no actualiza su navegador, no podrá obtener los parches de seguridad y las nuevas funciones de MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Sobreescribir el encabezado Content-Security-Policy" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Esta opción es una solución alternativa para un problema conocido en Firefox, donde el encabezado Content-Security-Policy de una dapp puede impedir que la extensión se cargue correctamente. No se recomienda desactivar esta opción a menos que sea necesario para la compatibilidad específica de la página web." + }, "padlock": { "message": "Candado" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Aquí es donde puedes ver los permisos que has otorgado a los Snaps instalados o a los sitios conectados." }, - "permissionsPageTourDescription": { - "message": "Este es su panel de control para administrar los permisos otorgados a los sitios conectados y los Snaps instalados." - }, - "permissionsPageTourTitle": { - "message": "Los sitios conectados ahora tienen permisos" - }, "permitSimulationDetailInfo": { "message": "Le está dando permiso al gastador para gastar esta cantidad de tokens de su cuenta." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Proteja sus fondos." }, - "redesignedConfirmationsEnabledToggle": { - "message": "Solicitudes de firma mejoradas" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Active esta opción para ver las solicitudes de firma en un formato mejorado." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Solicitudes de transacción mejoradas" - }, - "redesignedTransactionsToggleDescription": { - "message": "Active esta opción para ver las solicitudes de transacciones en un formato mejorado." - }, "refreshList": { "message": "Actualizar lista" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Este es el sitio que solicita su firma." }, + "requestFromInfoSnap": { + "message": "Este es el Snap que solicita su firma." + }, "requestFromTransactionDescription": { "message": "Este es el sitio que le pide su confirmación." }, @@ -4417,6 +4426,10 @@ "message": "Solicitando para $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Solicitando para $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "solicitudes en espera de confirmación" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Usted recibe" }, + "simulationDetailsNoChanges": { + "message": "Sin cambios" + }, "simulationDetailsOutgoingHeading": { "message": "Envía" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Es probable que esta transacción falle" }, + "simulationDetailsUnavailable": { + "message": "No disponible" + }, "simulationErrorMessageV2": { "message": "No pudimos estimar el gas. Podría haber un error en el contrato y esta transacción podría fallar." }, @@ -5143,6 +5162,15 @@ "message": "Póngase en contacto con los creadores de $1 para obtener más ayuda.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Activar esta función le dará la opción de añadir una cuenta de Solana a su extensión MetaMask derivada de su frase secreta de recuperación existente. Esta es una característica Beta experimental, por lo que debe utilizarla bajo su propio riesgo." + }, + "solanaSupportToggleTitle": { + "message": "Activar \"Añadir una nueva cuenta de Solana (Beta)\"" + }, "somethingDoesntLookRight": { "message": "Algo no se ve bien, ¿cierto? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Token potencialmente falso" }, + "swapTokenVerifiedSources": { + "message": "Confirmado por fuentes de $1. Verificar en $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 permite hasta $2 decimales", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 ahora está activo en $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Ahora está usando" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Cambiar de red cancelará todas las confirmaciones pendientes" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Elija su tema MetaMask preferido." }, - "thingsToKeep": { - "message": "Tenga en cuenta:" - }, "thirdPartySoftware": { "message": "Aviso de software de terceros", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Sugerencias" }, + "tipsForUsingAWallet": { + "message": "Consejos para utilizar un monedero" + }, + "tipsForUsingAWalletDescription": { + "message": "Al añadir tokens se desbloquean más formas de utilizar Web3." + }, "to": { "message": "Para" }, @@ -5925,18 +5957,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Esto le permite seleccionar una red para cada sitio en lugar de una única red seleccionada para todos los sitios. Esta función evitará que cambie de red manualmente, lo que puede afectar su experiencia de usuario en ciertos sitios." - }, - "toggleRequestQueueField": { - "message": "Seleccionar redes para cada sitio" - }, - "toggleRequestQueueOff": { - "message": "Desactivado" - }, - "toggleRequestQueueOn": { - "message": "Activado" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Lista de tókenes" }, + "tokenMarketplace": { + "message": "Mercado de tokens" + }, "tokenScamSecurityRisk": { "message": "estafas de tokens y riesgos de seguridad" }, - "tokenShowUp": { - "message": "Es posible que sus tókenes no aparezcan automáticamente en su monedero. " - }, "tokenStandard": { "message": "Estándar de tokenes" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Decodificar contratos inteligentes" }, - "use4ByteResolutionDescription": { - "message": "Para mejorar la experiencia del usuario, personalizamos la pestaña de actividad con mensajes basados en los contratos inteligentes con los que interactúa. MetaMask usa un servicio llamado 4byte.directory para decodificar datos y mostrarle una versión de un contrato inteligente que es más fácil de leer. Esto ayuda a reducir sus posibilidades de aprobar acciones de contratos inteligentes maliciosos, pero puede resultar en que se comparta su dirección IP." - }, "useMultiAccountBalanceChecker": { "message": "Solicitudes de saldo de cuenta por lotes" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Según nuestros registros, es posible que el nombre de la red no coincida correctamente con este ID de cadena." }, - "xOfYPending": { - "message": "$1 de $2 están pendientes", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Sí" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 511ee6cbef71..4e82c875c760 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1086,9 +1086,6 @@ "loading": { "message": "Cargando..." }, - "loadingTokens": { - "message": "Cargando tokens..." - }, "localhost": { "message": "Host local 8545" }, @@ -1276,9 +1273,6 @@ "noConversionRateAvailable": { "message": "No hay tasa de conversión disponible" }, - "noTransactions": { - "message": "No tiene transacciones" - }, "noWebcamFound": { "message": "No se encontró la cámara web del equipo. Vuelva a intentarlo." }, @@ -2375,10 +2369,6 @@ "whatsThis": { "message": "¿Qué es esto?" }, - "xOfYPending": { - "message": "$1 de $2 están pendientes", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "Necesita permitir el acceso a la cámara para usar esta función." }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index 38125572b8ec..a2a85b36d987 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Laadimine..." }, - "loadingTokens": { - "message": "Lubade laadimine..." - }, "lock": { "message": "Logi välja" }, @@ -440,9 +437,6 @@ "noConversionRateAvailable": { "message": "Ühtegi vahetuskurssi pole saadaval" }, - "noTransactions": { - "message": "Teil ei ole tehinguid" - }, "noWebcamFound": { "message": "Teie arvuti veebikaamerat ei leitud. Proovige uuesti." }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index c1a4deb11ce4..3ec5211c2dfb 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "در حال بارکردن…" }, - "loadingTokens": { - "message": "در حال بارگیری رمزیاب ها..." - }, "localhost": { "message": "Localhost 8545 " }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "هیچ نرخ تغییر موجود نمیباشد" }, - "noTransactions": { - "message": "شما هیچ معامله ندارید" - }, "noWebcamFound": { "message": "وب کم کمپیوتر تان پیدا نشد. لطفًا دوباره کوشش کنید." }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index 89e274dd4466..c49ea583598d 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Ladataan..." }, - "loadingTokens": { - "message": "Käyttötunnuksia ladataan..." - }, "localhost": { "message": "Paikallisjoukko 8545" }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "Vaihtokurssi ei saatavilla" }, - "noTransactions": { - "message": "Sinulla ei ole tapahtumia" - }, "noWebcamFound": { "message": "Tietokoneesi verkkokameraa ei löytynyt. Ole hyvä ja yritä uudestaan." }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index 498c1878fd10..14f91bc5c16a 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -335,9 +335,6 @@ "loading": { "message": "Naglo-load..." }, - "loadingTokens": { - "message": "Naglo-load ng Mga Token..." - }, "lock": { "message": "Mag-log out" }, @@ -384,9 +381,6 @@ "noConversionRateAvailable": { "message": "Walang Presyo ng Palitan na Available" }, - "noTransactions": { - "message": "Wala kang mga transaksyon" - }, "noWebcamFound": { "message": "Hindi nakita ang webcam ng iyong computer. Pakisubukang muli." }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 99c3a6da2333..8d034bc4ee40 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Ajout de réseau" }, - "addingTokens": { - "message": "Ajouter des jetons" - }, "additionalNetworks": { "message": "Réseaux supplémentaires" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Les pirates informatiques imitent parfois les sites en modifiant légèrement l’adresse du site. Assurez-vous que vous interagissez avec le site voulu avant de continuer." }, + "alertMessageChangeInSimulationResults": { + "message": "Les modifications estimées pour cette transaction ont été mises à jour. Examinez-les attentivement avant de poursuivre." + }, "alertMessageGasEstimateFailed": { "message": "Nous ne sommes pas en mesure de déterminer le montant exact des frais et cette estimation peut être élevée. Nous vous suggérons d’entrer une limite de gaz personnalisée, mais la transaction pourrait quand même échouer." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Nous ne pouvons pas valider cette transaction tant que vous n’avez pas mis à jour manuellement les frais." }, - "alertMessagePendingTransactions": { - "message": "La transaction précédente doit être finalisée avant que celle-ci ne soit traitée. Découvrez comment vous pouvez annuler ou accélérer une transaction." - }, "alertMessageSignInDomainMismatch": { "message": "Le site auquel vous êtes en train de vous connecter n’est pas le site à l’origine de la demande. Il pourrait s’agir d’une tentative de vol de vos identifiants de connexion." }, "alertMessageSignInWrongAccount": { "message": "Ce site vous demande de vous connecter en utilisant le mauvais compte." }, - "alertMessageSigningOrSubmitting": { - "message": "La transaction précédente doit être finalisée avant que celle-ci ne soit traitée." - }, "alertModalAcknowledge": { "message": "Je suis conscient du risque et je souhaite quand même continuer" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Examiner toutes les alertes" }, + "alertReasonChangeInSimulationResults": { + "message": "Les résultats ont changé" + }, "alertReasonGasEstimateFailed": { "message": "Frais inexacts" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Détection automatique des jetons dans votre portefeuille, affichage des NFT et mise à jour du solde de plusieurs comptes" }, - "attemptSendingAssets": { - "message": "Si vous essayez d’envoyer des actifs directement d’un réseau à un autre, une perte permanente des actifs pourrait en résulter. Assurez-vous d’utiliser une passerelle." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Si vous essayez d’envoyer des actifs directement d’un réseau à un autre, vous pourriez les perdre définitivement. Utilisez une passerelle comme $1 pour transférer en toute sécurité des fonds d’un réseau à un autre" - }, "attemptToCancelSwapForFree": { "message": "Tentative d’annuler gratuitement le swap" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Pont" }, - "bridgeDontSend": { - "message": "Passerelle, ne pas envoyer" + "bridgeCalculatingAmount": { + "message": "Calcul en cours…" + }, + "bridgeEnterAmount": { + "message": "Saisissez le montant" }, "bridgeFrom": { "message": "Passerelle depuis" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Sélectionner un réseau" }, + "bridgeSelectTokenAndAmount": { + "message": "Sélectionnez le jeton et le montant" + }, "bridgeTo": { "message": "Passerelle vers" }, @@ -945,9 +942,6 @@ "message": "Cliquez ici pour connecter votre Ledger via WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Vous pouvez ajouter des jetons manuellement." - }, "close": { "message": "Fermer" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Confirmer la phrase secrète de récupération" }, - "confirmTitleApproveTransaction": { - "message": "Demande de provision" - }, "confirmTitleDeployContract": { "message": "Déployer un contrat" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Demande de transaction" }, - "confirmationAlertModalDetails": { - "message": "Pour protéger vos actifs et vos informations de connexion, nous vous suggérons de rejeter la demande." - }, "confirmationAlertModalTitle": { "message": "Cette demande est suspecte" }, @@ -1160,6 +1148,14 @@ "message": "Connecté avec $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 réseaux connectés", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Connecté avec $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Connexion…" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D’Cent" }, + "debitCreditPurchaseOptions": { + "message": "Options d’achat par carte de débit ou de crédit" + }, "decimal": { "message": "Nombre de décimales du jeton" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Le plafond des dépenses ne peut contenir plus de $1 chiffres décimaux. Supprimez les chiffres décimaux excédentaires pour continuer." }, + "editSpendingCapSpecialCharError": { + "message": "Saisissez uniquement des chiffres" + }, "enableAutoDetect": { "message": " Activer la détection automatique" }, @@ -1904,10 +1906,42 @@ "message": "Code : $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Contacter l’assistance", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Décrivez ce qui s’est passé", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Vos informations ne peuvent pas être affichées. Ne vous inquiétez pas, votre portefeuille et vos fonds sont en sécurité.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Message d’erreur", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Décrivez ce qui s’est passé", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Le fait de partager des détails tels que la façon dont nous pouvons reproduire le bogue nous aidera à résoudre le problème.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Merci. Nous y jetterons un coup d’œil prochainement.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask a rencontré une erreur", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Réessayez", + "description": "Button for try again" + }, "errorStack": { "message": "Stack :", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Type de fonction" }, + "fundingMethod": { + "message": "Mode de financement" + }, "gas": { "message": "Carburant" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Frais de gaz" }, - "gasIsETH": { - "message": "Le gaz est $1 " - }, "gasLimit": { "message": "Montant maximal des frais de transaction" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "hameçonnages et risques de sécurité." }, - "learnToBridge": { - "message": "Apprenez à établir une passerelle" - }, "leaveMetaMask": { "message": "Voulez-vous quitter MetaMask ?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Veuillez conclure la transaction sur le snap." }, - "loadingTokens": { - "message": "Chargement des jetons..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Les notifications du portefeuille ne sont actuellement pas activées." }, - "metamaskPortfolio": { - "message": "Portfolio MetaMask." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps est en cours de maintenance. Nous vous invitons à revenir plus tard." }, @@ -2992,10 +3017,6 @@ "message": "$1 vous demande votre approbation pour :", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Le jeton natif de ce réseau est $1. C’est le jeton utilisé pour les frais de gaz. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Modifier les détails du réseau" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Non merci" }, - "noTransactions": { - "message": "Aucune transaction" - }, "noWebcamFound": { "message": "La caméra de votre ordinateur n’a pas été trouvée. Veuillez réessayer." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Votre navigateur n’est pas à jour. Si vous ne mettez pas à jour votre navigateur, vous ne pourrez pas obtenir les correctifs de sécurité et profiter des nouvelles fonctionnalités de MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Remplacer l’en-tête Content-Security-Policy" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Cette option est une solution de contournement pour un problème connu dans Firefox, où l’en-tête Content-Security-Policy d’une dapp peut empêcher l’extension de se charger correctement. La désactivation de cette option n’est pas recommandée, à moins qu’elle ne soit nécessaire pour assurer la compatibilité d’une page web spécifique." + }, "padlock": { "message": "Cadenas" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Ici, vous pouvez voir les autorisations que vous avez accordées aux Snaps installés ou aux sites connectés." }, - "permissionsPageTourDescription": { - "message": "C’’est votre panneau de configuration pour gérer les autorisations accordées aux sites connectés et aux Snaps installés." - }, - "permissionsPageTourTitle": { - "message": "Les sites connectés sont maintenant des autorisations" - }, "permitSimulationDetailInfo": { "message": "Vous autorisez la dépenseur à dépenser ce nombre de jetons de votre compte." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Protégez vos fonds" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Demandes de signature améliorées" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Activez cette option pour afficher les demandes de signature dans un format amélioré." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Demandes de transaction améliorées" - }, - "redesignedTransactionsToggleDescription": { - "message": "Activez cette option pour afficher les demandes de transaction dans un format amélioré." - }, "refreshList": { "message": "Rafraîchir la liste" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "C'est le site qui vous demande votre signature." }, + "requestFromInfoSnap": { + "message": "Il s’agit du Snap qui demande votre signature." + }, "requestFromTransactionDescription": { "message": "Il s’agit du site qui demande votre confirmation." }, @@ -4417,6 +4426,10 @@ "message": "Demande de $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Demande pour $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "demandes en attente d’un accusé de réception" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Vous recevez" }, + "simulationDetailsNoChanges": { + "message": "Aucun changement" + }, "simulationDetailsOutgoingHeading": { "message": "Vous envoyez" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Cette transaction va probablement échouer" }, + "simulationDetailsUnavailable": { + "message": "Non disponible" + }, "simulationErrorMessageV2": { "message": "Nous n’avons pas pu estimer le prix de carburant. Par conséquent, il se peut qu’il y ait une erreur dans le contrat et que cette transaction échoue." }, @@ -5143,6 +5162,15 @@ "message": "L’interface utilisateur (IU) spécifiée par le snap n’est pas valide.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "En activant cette fonctionnalité, vous aurez la possibilité d’ajouter un compte Solana à votre extension MetaMask dérivée de votre phrase secrète de récupération existante. Toute utilisation de cette fonctionnalité bêta expérimentale se fait à vos risques et périls." + }, + "solanaSupportToggleTitle": { + "message": "Activer « Ajouter un nouveau compte Solana (Bêta) »" + }, "somethingDoesntLookRight": { "message": "On dirait que quelque chose ne va pas ? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Jeton potentiellement inauthentique" }, + "swapTokenVerifiedSources": { + "message": "Confirmé par $1 sources. Vérifier sur $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 accepte jusqu’à $2 décimales", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 est maintenant actif sur $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Vous êtes en train d’utiliser" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Le changement de réseau annulera toutes les confirmations en attente" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Choisissez votre thème MetaMask préféré." }, - "thingsToKeep": { - "message": "Les choses que vous devez garder à l’esprit :" - }, "thirdPartySoftware": { "message": "Avis sur les logiciels développés par des tiers", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Dons" }, + "tipsForUsingAWallet": { + "message": "Conseils pour l’utilisation d’un portefeuille" + }, + "tipsForUsingAWalletDescription": { + "message": "L’ajout de jetons permet de débloquer d’autres moyens d’utiliser le Web3." + }, "to": { "message": "Destinataire" }, @@ -5925,18 +5957,6 @@ "message": "Vers : $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Cette fonction vous permet de sélectionner un réseau pour chaque site au lieu d’un seul réseau pour tous les sites. Vous n’aurez donc pas à changer manuellement de réseau, ce qui pourrait nuire à l’expérience utilisateur sur certains sites." - }, - "toggleRequestQueueField": { - "message": "Sélectionnez les réseaux pour chaque site" - }, - "toggleRequestQueueOff": { - "message": "Désactiver" - }, - "toggleRequestQueueOn": { - "message": "Activer" - }, "token": { "message": "Jeton" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Listes de jetons" }, + "tokenMarketplace": { + "message": "Marché des jetons" + }, "tokenScamSecurityRisk": { "message": "les arnaques et les risques de piratage informatique" }, - "tokenShowUp": { - "message": "Il se peut que vos jetons n’apparaissent pas automatiquement dans votre portefeuille. " - }, "tokenStandard": { "message": "Jeton standard" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Décoder les contrats intelligents" }, - "use4ByteResolutionDescription": { - "message": "Pour améliorer l’expérience utilisateur, nous personnalisons les messages qui s’affichent dans l’onglet d’activité en fonction des contrats intelligents avec lesquels vous interagissez. MetaMask utilise un service appelé 4byte.directory pour décoder les données et vous montrer une version plus facile à lire des contrats intelligents. Ainsi vous aurez moins de chances d’approuver l’exécution de contrats intelligents malveillants, mais cela peut nécessiter le partage de votre adresse IP." - }, "useMultiAccountBalanceChecker": { "message": "Demandes d’informations concernant le solde de plusieurs comptes" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Selon nos informations, il se peut que le nom du réseau ne corresponde pas exactement à l’ID de chaîne." }, - "xOfYPending": { - "message": "$1 sur $2 en attente", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Oui" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 413bf21d586b..c9360ff612de 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "טוען..." }, - "loadingTokens": { - "message": "טוען טוקנים..." - }, "lock": { "message": "התנתקות" }, @@ -443,9 +440,6 @@ "noConversionRateAvailable": { "message": "אין שער המרה זמין" }, - "noTransactions": { - "message": "אין לך עסקאות" - }, "noWebcamFound": { "message": "מצלמת הרשת של מחשבך לא נמצאה. נא לנסות שוב." }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 92d8d0bc7fa8..00a5a9cbbdc9 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "नेटवर्क जोड़ रहे हैं" }, - "addingTokens": { - "message": "टोकन जोड़ना" - }, "additionalNetworks": { "message": "अतिरिक्त नेटवर्क" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "हमला करने वाले कभी-कभी साइट के एड्रेस में छोटे-छोटे बदलाव करके साइटों की नकल करते हैं। जारी रखने से पहले सुनिश्चित करें कि आप इच्छित साइट के साथ इंटरैक्ट कर रहे हैं।" }, + "alertMessageChangeInSimulationResults": { + "message": "इस ट्रांसेक्शन के लिए अनुमानित परिवर्तन अपडेट कर दिए गए हैं। आगे बढ़ने से पहले उनकी बारीकी से समीक्षा करें।" + }, "alertMessageGasEstimateFailed": { "message": "हम सटीक शुल्क प्रदान करने में असमर्थ हैं और यह अनुमान अधिक हो सकता है। हम आपको एक कस्टम गैस लिमिट दर्ज करने का सुझाव देते हैं, लेकिन जोखिम है कि ट्रांसेक्शन अभी भी विफल हो जाएगा।" }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "जब तक आप शुल्क को मैन्युअल रूप से अपडेट नहीं करते, हम इस ट्रांसेक्शन को आगे नहीं बढ़ा सकते।" }, - "alertMessagePendingTransactions": { - "message": "यह ट्रांसेक्शन तब तक नहीं होगा जब तक पिछला ट्रांसेक्शन पूरा न हो जाए। किसी ट्रांसेक्शन को रद्द करने या तेज़ करने का तरीका जानें।" - }, "alertMessageSignInDomainMismatch": { "message": "अनुरोध करने वाली साइट वह साइट नहीं है जिस पर आप साइन इन कर रहे हैं। यह आपके लॉगिन क्रेडेंशियल चुराने का प्रयास हो सकता है।" }, "alertMessageSignInWrongAccount": { "message": "यह साइट आपसे गलत अकाउंट का उपयोग करके साइन इन करने के लिए कह रही है।" }, - "alertMessageSigningOrSubmitting": { - "message": "यह ट्रांसेक्शन तभी पूरा होगा जब आपका पिछला ट्रांसेक्शन पूरा हो जाएगा।" - }, "alertModalAcknowledge": { "message": "मैंने जोखिम को स्वीकार कर लिया है और इसके बावजूद आगे बढ़ना चाहता/चाहती हूं" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "सभी एलर्ट की समीक्षा करें" }, + "alertReasonChangeInSimulationResults": { + "message": "परिणाम बदल गए हैं" + }, "alertReasonGasEstimateFailed": { "message": "गलत शुल्क" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "अपने वॉलेट में टोकन ऑटोडिटेक्ट करें, NFT प्रदर्शित करें, और बैच अकाउंट बैलेंस संबंधी अपडेट प्राप्त करें" }, - "attemptSendingAssets": { - "message": "अगर आप एसेट्स को सीधे एक नेटवर्क से दूसरे नेटवर्क पर भेजने की कोशिश करते हैं, तो ऐसा करने से आपको एसेट्स का नुकसान हो सकता है। ब्रिज का इस्तेमाल करके नेटवर्कों के बीच फंड्स को सुरक्षित तरीके से ट्रांसफ़र करें।" - }, - "attemptSendingAssetsWithPortfolio": { - "message": "अगर आप एसेट्स को सीधे एक नेटवर्क से दूसरे नेटवर्क पर भेजने की कोशिश करते हैं, तो ऐसा करने से आपको एसेट्स का नुकसान हो सकता है। ब्रिज, जैसे like $1, का इस्तेमाल करके नेटवर्कों के बीच फंड्स को सुरक्षित तरीके से ट्रांसफ़र करें।" - }, "attemptToCancelSwapForFree": { "message": "स्वैप को मुफ्त में कैंसिल करने की कोशिश करें" }, @@ -837,8 +828,11 @@ "bridge": { "message": "ब्रिज" }, - "bridgeDontSend": { - "message": "ब्रिज, न भेजें" + "bridgeCalculatingAmount": { + "message": "कैलकुलेट किया जा रहा है..." + }, + "bridgeEnterAmount": { + "message": "रकम डालें" }, "bridgeFrom": { "message": "इससे ब्रिज करें" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "नेटवर्क को चुनें" }, + "bridgeSelectTokenAndAmount": { + "message": "टोकन और रकम का चयन करें" + }, "bridgeTo": { "message": "इसपर ब्रिज करें" }, @@ -945,9 +942,6 @@ "message": "अपने Ledger को WebHID के ज़रिए कनेक्ट करने के लिए यहां क्लिक करें", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "आप कभी भी मैन्युअल रूप से टोकन जोड़ सकते हैं।" - }, "close": { "message": "बंद करें" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "सीक्रेट रिकवरी फ्रेज कन्फर्म करें" }, - "confirmTitleApproveTransaction": { - "message": "भत्ता का अनुरोध" - }, "confirmTitleDeployContract": { "message": "एक कॉन्ट्रैक्ट करें" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "ट्रांसेक्शन अनुरोध" }, - "confirmationAlertModalDetails": { - "message": "आपकी एसेट्स और लॉगिन जानकारी की सुरक्षा के लिए, हम आपको अनुरोध को रिजेक्ट करने का सुझाव देते हैं।" - }, "confirmationAlertModalTitle": { "message": "यह अनुरोध संदिग्ध है" }, @@ -1160,6 +1148,14 @@ "message": "$1 से कनेक्ट किए गए", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 नेटवर्क कनेक्ट किए गए", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "$1 से कनेक्ट किए गए", + "description": "$1 represents network name" + }, "connecting": { "message": "कनेक्ट किया जा रहा है" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "डेबिट या क्रेडिट कार्ड से खरीदारी के विकल्प" + }, "decimal": { "message": "टोकन डेसीमल" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "खर्च करने की सीमा $1 दशमलव अंक से अधिक नहीं हो सकती। जारी रखने के लिए दशमलव अंक हटाएं।" }, + "editSpendingCapSpecialCharError": { + "message": "केवल संख्या डालें" + }, "enableAutoDetect": { "message": " ऑटो डिटेक्ट इनेबल करें" }, @@ -1904,10 +1906,42 @@ "message": "कोड: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "सपोर्ट से कॉन्टेक्ट करें", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "वर्णन करें कि क्या हुआ", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "आपकी जानकारी नहीं दिखाई जा सकती। चिंता न करें, आपके वॉलेट और फंड सुरक्षित हैं।", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "गड़बड़ी संबंधी संदेश", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "वर्णन करें कि क्या हुआ", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "हम बग को कैसे फिर से उत्पन्न कर सकते हैं जैसे विवरण साझा करने से हमें समस्या को ठीक करने में मदद मिलेगी।", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "धन्यवाद! हम जल्द ही इस पर गौर करेंगे।", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask में कोई गड़बड़ी हुई", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "फिर से कोशिश करें", + "description": "Button for try again" + }, "errorStack": { "message": "स्टैक:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "फ़ंक्शन का प्रकार" }, + "fundingMethod": { + "message": "फंड करने की विधि" + }, "gas": { "message": "गैस" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "गैस फीस" }, - "gasIsETH": { - "message": "गैस $1 है " - }, "gasLimit": { "message": "गैस लिमिट" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "घोटाले और सुरक्षा जोखिम।" }, - "learnToBridge": { - "message": "ब्रिज करना सीखें" - }, "leaveMetaMask": { "message": "MetaMask से बाहर निकलें?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "कृपया Snap पर ट्रांजेक्शन पूरा करें।" }, - "loadingTokens": { - "message": "टोकन लोड हो रहे हैं..." - }, "localhost": { "message": "लोकलहोस्ट 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "वॉलेट नोटिफिकेशंस वर्तमान में सक्रिय नहीं हैं।" }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask स्वैप का रखरखाव किया जा रहा है। कृपया बाद में वापस देखें।" }, @@ -2992,10 +3017,6 @@ "message": "$1 निम्नलिखित के लिए आपका एप्रूवल मांग रहा है:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "इस नेटवर्क पर ओरिजिनल टोकन $1 है। यह गैस फ़ीस के लिए इस्तेमाल किया जाने वाला टोकन है।", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "नेटवर्क का ब्यौरा बदलें" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "जी नहीं, धन्यवाद" }, - "noTransactions": { - "message": "आपके पास कोई ट्रांसेक्शन नहीं है" - }, "noWebcamFound": { "message": "आपके कंप्यूटर का वेबकैम नहीं मिला। कृपया फिर से कोशिश करें।" }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "आपका ब्राउज़़र पुराना हो चुका है। यदि आप अपने ब्राउज़़र को अपडेट नहीं करते हैं, तो आप MetaMask से सुरक्षा पैच और नई फीचर्स प्राप्त नहीं कर पाएंगे।" }, + "overrideContentSecurityPolicyHeader": { + "message": "कंटेंट-सिक्योरिटी-पॉलिसी हेडर को ओवरराइड करें" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "यह विकल्प Firefox में एक ज्ञात समस्या के लिए एक समाधान है, जहां एक dapp का कंटेंट-सिक्योरिटी-पॉलिसी हेडर एक्सटेंशन को ठीक से लोड होने से रोक सकता है। जब तक विशिष्ट वेब पेज अनुकूलता के लिए आवश्यक न हो, इस विकल्प को बंद करने की सलाह नहीं दी जाती है।" + }, "padlock": { "message": "पैडलॉक" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "यहां पर आप इंस्टॉल किए गए Snaps या कनेक्टेड साइटों को दी गई अनुमतियां देख सकते हैं।" }, - "permissionsPageTourDescription": { - "message": "कनेक्टेड साइटों और इंस्टॉल किए गए Snaps को दी गई अनुमतियों को मैनेज करने के लिए यह आपका कंट्रोल पैनल है।" - }, - "permissionsPageTourTitle": { - "message": "कनेक्टेड साइटें अब अनुमतियां हैं" - }, "permitSimulationDetailInfo": { "message": "आप खर्च करने वाले को अपने अकाउंट से इतने सारे टोकन खर्च करने की अनुमति दे रहे हैं।" }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "अपने धन को सुरक्षित रखें" }, - "redesignedConfirmationsEnabledToggle": { - "message": "बेहतर सिग्नेचर अनुरोध" - }, - "redesignedConfirmationsToggleDescription": { - "message": "उन्नत फॉर्मेट में सिग्नेचर अनुरोध देखने के लिए इसे चालू करें।" - }, - "redesignedTransactionsEnabledToggle": { - "message": "बेहतर ट्रांसेक्शन के अनुरोध" - }, - "redesignedTransactionsToggleDescription": { - "message": "उन्नत फॉर्मेट में ट्रांसेक्शन के अनुरोध देखने के लिए इसे चालू करें।" - }, "refreshList": { "message": "लिस्ट रिफ्रेश करें" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "यह वह साइट है जो आपका सिग्नेचर मांग रही है।" }, + "requestFromInfoSnap": { + "message": "यह वह Snap है जो आपका सिग्नेचर मांग रहा है।" + }, "requestFromTransactionDescription": { "message": "यह वही साइट है जो आपका कन्फर्मेशन मांग रही है।" }, @@ -4417,6 +4426,10 @@ "message": "$1 के लिए अनुरोध कर रहे हैं", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "$1 के लिए अनुरोध कर रहे हैं", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "रिक्वेस्ट्स के स्वीकार किए जाने की प्रतीक्षा की जा रही है" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "आप पाते हैं" }, + "simulationDetailsNoChanges": { + "message": "कोई बदलाव नहीं" + }, "simulationDetailsOutgoingHeading": { "message": "आप भेजते हैं" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "ये ट्रांसेक्शन विफल हो सकता है" }, + "simulationDetailsUnavailable": { + "message": "अनुपलब्ध" + }, "simulationErrorMessageV2": { "message": "हम गैस का अनुमान नहीं लगा पाए। कॉन्ट्रैक्ट में कोई गड़बड़ी हो सकती है और यह ट्रांसेक्शन विफल हो सकता है।" }, @@ -5143,6 +5162,15 @@ "message": "अधिक सहायता के लिए $1 के निर्माताओं से कॉन्टेक्ट करें।", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "सोलाना" + }, + "solanaSupportToggleDescription": { + "message": "इस फीचर को चालू करने से आपको अपने मौजूदा सीक्रेट रिकवरी फ्रेज़ से प्राप्त MetaMask एक्सटेंशन में एक सोलाना अकाउंट जोड़ने का विकल्प मिलेगा। यह एक प्रायोगिक बीटा फीचर है, इसलिए आपको इसका उपयोग अपने जोखिम पर करना होगा।" + }, + "solanaSupportToggleTitle": { + "message": "\"एक नया सोलाना अकाउंट जोड़ें (बीटा)\" को चालू करें" + }, "somethingDoesntLookRight": { "message": "कुछ तो गड़बड़ है? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "संभावित रूप से अप्रामाणिक टोकन" }, + "swapTokenVerifiedSources": { + "message": "$1 स्रोतों द्वारा कन्फर्म की गई। $2 पर वेरीफ़ाई करें।", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1अनुमति देता है डेसीमल $2 तक की", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 अब $2 पर एक्टिव है", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "आप अब इस्तेमाल कर रहे हैं" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "नेटवर्क स्विच करने से सभी लंबित कन्फर्मेशन कैंसिल हो जाएंगे" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "अपनी पसंदीदा MetaMask थीम चुन लें" }, - "thingsToKeep": { - "message": "ध्यान रखने योग्य बातें:" - }, "thirdPartySoftware": { "message": "थर्ड-पार्टी सॉफ्टवेयर नोटिस", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "युक्तियां" }, + "tipsForUsingAWallet": { + "message": "वॉलेट का उपयोग करने के लिए टिप्स" + }, + "tipsForUsingAWalletDescription": { + "message": "टोकन जोड़ने से Web3 का उपयोग करने के और अधिक तरीके अनलॉक हो जाते हैं।" + }, "to": { "message": "प्रति" }, @@ -5925,18 +5957,6 @@ "message": "प्रति: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "ऐसा करके, आप सभी साइटों के लिए कोई सिंगल नेटवर्क चुनने के बजाय हरेक साइट के लिए एक नेटवर्क चुन सकते हैं। यह फीचर आपको मैन्युअल तरीके से नेटवर्क स्विच करने से रोकता है, इस वजह से कुछ साइटों पर आपका यूज़र अनुभव ख़राब हो सकता है।" - }, - "toggleRequestQueueField": { - "message": "हरेक साइट के लिए नेटवर्क चुनें" - }, - "toggleRequestQueueOff": { - "message": "बंद करें" - }, - "toggleRequestQueueOn": { - "message": "चालू करें" - }, "token": { "message": "टोकन" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "टोकन की सूचियां" }, + "tokenMarketplace": { + "message": "टोकन मार्केटप्लेस" + }, "tokenScamSecurityRisk": { "message": "टोकन घोटाले और सुरक्षा जोखिम" }, - "tokenShowUp": { - "message": "हो सकता है आपके वॉलेट में आपके टोकन ऑटोमेटिकली दिखाई नहीं दें। " - }, "tokenStandard": { "message": "टोकन स्टैंडर्ड" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "स्मार्ट कॉन्ट्रैक्ट्स को डीकोड करें" }, - "use4ByteResolutionDescription": { - "message": "यूज़र के अनुभव को बेहतर बनाने के लिए, आपके द्वारा इंटरैक्ट किए गए स्मार्ट कॉन्ट्रैक्ट्स के आधार पर हम एक्टिविटी टैब को मैसेज के साथ कस्टमाइज़ करते हैं। डेटा को डीकोड करने और आसानी से पढ़े जा सकने वाले स्मार्ट कॉन्ट्रैक्ट्स का एक वर्शन आपको दिखाने के लिए MetaMask एक सर्विस इस्तेमाल करता है जिसका नाम 4byte.directory है। इससे आपके द्वारा बुरी नीयत वाले स्मार्ट कॉन्ट्रैक्ट एक्शन को मंजूरी देने की संभावनाओं को कम करने में मदद मिलती है। हालांकि, इसमें आपका IP एड्रेस शेयर होने का खतरा हो सकता है।" - }, "useMultiAccountBalanceChecker": { "message": "अकाउंट के बैलेंस के रिक्वेस्ट्स को बैच करें" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "हमारे रिकॉर्ड के अनुसार, नेटवर्क का नाम इस चेन ID से ठीक से मेल नहीं खा सकता है।" }, - "xOfYPending": { - "message": "$2 में से $1 लंबित", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "हां" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index a4e2e37bde22..cecc6f26385b 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -152,9 +152,6 @@ "loading": { "message": "लोड हो रहा है ....." }, - "loadingTokens": { - "message": "टोकन लोड हो रहा है ....." - }, "localhost": { "message": "स्थानीयहोस्ट 8545" }, @@ -196,9 +193,6 @@ "next": { "message": "अगला" }, - "noTransactions": { - "message": "कोई लेन-देन नहीं" - }, "pastePrivateKey": { "message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:", "description": "For importing an account from a private key" diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 7f9334f49f5c..77b864172ae2 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Učitavanje..." }, - "loadingTokens": { - "message": "Učitavanje tokena..." - }, "lock": { "message": "Odjava" }, @@ -443,9 +440,6 @@ "noConversionRateAvailable": { "message": "Nijedan konverzijski tečaj nije dostupan" }, - "noTransactions": { - "message": "Nemate transkacija" - }, "noWebcamFound": { "message": "Mrežna kamera vašeg računala nije pronađena. Pokušajte ponovno." }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index 7309b04dbd05..29df50803fa9 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -260,9 +260,6 @@ "loading": { "message": "Telechaje..." }, - "loadingTokens": { - "message": "Telechaje Tokens..." - }, "lock": { "message": "Dekonekte" }, @@ -313,9 +310,6 @@ "noConversionRateAvailable": { "message": "Pa gen okenn Konvèsyon Disponib" }, - "noTransactions": { - "message": "Pa gen tranzaksyon" - }, "noWebcamFound": { "message": "Nou pakay jwenn webcam òdinatè ou. Tanpri eseye ankò." }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 7b2b429ae5ed..9255c752b4a7 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Betöltés..." }, - "loadingTokens": { - "message": "Tokenek betöltése..." - }, "lock": { "message": "Kilépés" }, @@ -443,9 +440,6 @@ "noConversionRateAvailable": { "message": "Nincs elérhető átváltási díj" }, - "noTransactions": { - "message": "Nincsenek tranzakciói" - }, "noWebcamFound": { "message": "Nem található számítógéped webkamerája. Kérünk, próbáld újra." }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index a474fc1afa1a..dc8d3419f73a 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Menambahkan Jaringan" }, - "addingTokens": { - "message": "Menambahkan token" - }, "additionalNetworks": { "message": "Jaringan tambahan" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Penyerang terkadang meniru situs dengan membuat perubahan kecil pada alamat situs. Pastikan Anda berinteraksi dengan situs yang dituju sebelum melanjutkan." }, + "alertMessageChangeInSimulationResults": { + "message": "Estimasi perubahan untuk transaksi ini telah diperbarui. Tinjau dengan saksama sebelum melanjutkan." + }, "alertMessageGasEstimateFailed": { "message": "Kami tidak dapat memberikan biaya akurat dan estimasi ini mungkin tinggi. Kami menyarankan Anda untuk memasukkan batas gas kustom, tetapi ada risiko transaksi tetap gagal." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Kami tidak dapat melanjutkan transaksi ini hingga Anda memperbarui biayanya secara manual." }, - "alertMessagePendingTransactions": { - "message": "Transaksi ini tidak akan dilanjutkan hingga transaksi sebelumnya selesai. Pelajari cara membatalkan atau mempercepat transaksi." - }, "alertMessageSignInDomainMismatch": { "message": "Situs yang membuat permintaan bukanlah situs yang Anda masuki. Ini dapat merupakan upaya untuk mencuri kredensial login Anda." }, "alertMessageSignInWrongAccount": { "message": "Situs ini meminta Anda masuk menggunakan akun yang salah." }, - "alertMessageSigningOrSubmitting": { - "message": "Transaksi ini hanya akan dilanjutkan setelah transaksi Anda sebelumnya selesai." - }, "alertModalAcknowledge": { "message": "Saya telah mengetahui risikonya dan tetap ingin melanjutkan" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Tinjau semua peringatan" }, + "alertReasonChangeInSimulationResults": { + "message": "Hasil telah berubah" + }, "alertReasonGasEstimateFailed": { "message": "Biaya tidak akurat" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Autodeteksi token di dompet Anda, tampilkan NFT, dan dapatkan pembaruan saldo akun secara batch" }, - "attemptSendingAssets": { - "message": "Aset Anda berpotensi hilang jika mencoba mengirimnya dari jaringan lain. Transfer dana secara aman antar jaringan menggunakan bridge." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Aset Anda berpotensi hilang jika mencoba mengirimnya melalui jaringan lain. Transfer dana antar jaringan dengan aman menggunakan bridge, seperti $1" - }, "attemptToCancelSwapForFree": { "message": "Mencoba membatalkan pertukaran secara gratis" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Bridge" }, - "bridgeDontSend": { - "message": "Bridge, jangan kirim" + "bridgeCalculatingAmount": { + "message": "Menghitung..." + }, + "bridgeEnterAmount": { + "message": "Masukkan jumlah" }, "bridgeFrom": { "message": "Bridge dari" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Pilih jaringan" }, + "bridgeSelectTokenAndAmount": { + "message": "Pilih token dan jumlah" + }, "bridgeTo": { "message": "Bridge ke" }, @@ -945,9 +942,6 @@ "message": "Klik di sini untuk menghubungkan Ledger Anda melalui WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Anda dapat menambahkan token secara manual setiap saat." - }, "close": { "message": "Tutup" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Konfirmasikan Frasa Pemulihan Rahasia" }, - "confirmTitleApproveTransaction": { - "message": "Permintaan izin" - }, "confirmTitleDeployContract": { "message": "Terapkan kontrak" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Permintaan transaksi" }, - "confirmationAlertModalDetails": { - "message": "Untuk melindungi aset dan informasi login Anda, sebaiknya tolak permintaan tersebut." - }, "confirmationAlertModalTitle": { "message": "Permintaan ini mencurigakan" }, @@ -1160,6 +1148,14 @@ "message": "Terhubung dengan $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 jaringan terhubung", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Terhubung dengan $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Menghubungkan" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Opsi pembelian kartu debit atau kredit" + }, "decimal": { "message": "Desimal token" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Batas penggunaan tidak boleh melebihi $1 digit desimal. Hapus digit desimal untuk melanjutkan." }, + "editSpendingCapSpecialCharError": { + "message": "Masukkan angka saja" + }, "enableAutoDetect": { "message": " Aktifkan deteksi otomatis" }, @@ -1904,10 +1906,42 @@ "message": "Kode: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Hubungi dukungan", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Jelaskan yang terjadi", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Informasi Anda tidak dapat ditampilkan. Jangan khawatir, dompet dan dana Anda aman.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Pesan kesalahan", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Jelaskan yang terjadi", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Berbagi detail seperti cara kami dapat mereproduksi bug akan membantu kami memperbaiki masalah tersebut.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Terima kasih! Kami akan segera memeriksanya.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask mengalami kesalahan", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Coba lagi", + "description": "Button for try again" + }, "errorStack": { "message": "Tumpukan:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Jenis fungsi" }, + "fundingMethod": { + "message": "Metode pendanaan" + }, "gas": { "message": "Gas" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Biaya gas" }, - "gasIsETH": { - "message": "Gas bernilai $1 " - }, "gasLimit": { "message": "Batas gas" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "penipuan dan risiko keamanan." }, - "learnToBridge": { - "message": "Pelajari bridge" - }, "leaveMetaMask": { "message": "Keluar dari MetaMask?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Selesaikan transaksi di Snap." }, - "loadingTokens": { - "message": "Memuat token..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Saat ini notifikasi dompet tidak aktif." }, - "metamaskPortfolio": { - "message": "Portfolio MetaMask." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swap sedang dalam pemeliharaan. Harap periksa kembali nanti." }, @@ -2992,10 +3017,6 @@ "message": "$1 meminta persetujuan Anda untuk:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Token asli di jaringan ini adalah $1. Ini merupakan token yang digunakan untuk biaya gas. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Edit detail jaringan" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Tidak, terima kasih" }, - "noTransactions": { - "message": "Anda tidak memiliki transaksi" - }, "noWebcamFound": { "message": "Webcam komputer Anda tidak ditemukan. Harap coba lagi." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Browser Anda sudah usang. Jika browser tidak diperbarui, Anda tidak akan bisa mendapatkan tambalan keamanan dan fitur baru dari MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Batalkan header Content-Security-Policy" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Opsi ini merupakan solusi sementara untuk masalah yang diketahui di Firefox, di mana header Content-Security-Policy dapp dapat mencegah ekstensi dimuat dengan benar. Menonaktifkan opsi ini tidak disarankan kecuali diperlukan untuk kompatibilitas halaman web tertentu." + }, "padlock": { "message": "Gembok" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Di sinilah Anda dapat melihat izin yang Anda berikan untuk Snap yang terinstal atau situs yang terhubung." }, - "permissionsPageTourDescription": { - "message": "Ini merupakan panel kontrol Anda untuk mengelola izin yang diberikan ke situs yang terhubung dan Snap yang terinstal." - }, - "permissionsPageTourTitle": { - "message": "Situs yang terhubung kini memiliki izin" - }, "permitSimulationDetailInfo": { "message": "Anda memberikan izin kepada pengguna untuk menggunakan token sebanyak ini dari akun." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Lindungi dana Anda" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Penyempurnaan permintaan tanda tangan" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Aktifkan ini untuk melihat permintaan tanda tangan dalam format yang disempurnakan." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Penyempurnaan permintaan transaksi" - }, - "redesignedTransactionsToggleDescription": { - "message": "Aktifkan ini untuk melihat permintaan transaksi dalam format yang disempurnakan." - }, "refreshList": { "message": "Segarkan daftar" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Ini merupakan situs yang meminta tanda tangan Anda." }, + "requestFromInfoSnap": { + "message": "Ini merupakan Snap yang meminta tanda tangan Anda." + }, "requestFromTransactionDescription": { "message": "Ini merupakan situs yang meminta konfirmasi Anda." }, @@ -4417,6 +4426,10 @@ "message": "Meminta untuk $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Meminta untuk $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "permintaan menunggu untuk disetujui" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Anda menerima" }, + "simulationDetailsNoChanges": { + "message": "Tidak ada perubahan" + }, "simulationDetailsOutgoingHeading": { "message": "Anda mengirim" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Transaksi ini kemungkinan besar akan gagal" }, + "simulationDetailsUnavailable": { + "message": "Tidak tersedia" + }, "simulationErrorMessageV2": { "message": "Kami tidak dapat memperkirakan gas. Tampaknya ada kesalahan dalam kontrak dan transaksi ini berpotensi gagal." }, @@ -5143,6 +5162,15 @@ "message": "Hubungi pembuat $1 untuk dukungan lebih lanjut.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Mengaktifkan fitur ini akan memberi Anda opsi untuk menambahkan Akun Solana ke Ekstensi MetaMask yang berasal dari Frasa Pemulihan Rahasia yang sudah ada. Ini merupakan fitur Beta eksperimental, jadi Anda harus menggunakannya dengan risiko yang ditanggung sendiri." + }, + "solanaSupportToggleTitle": { + "message": "Aktifkan \"Tambahkan akun Solana baru (Beta)\"" + }, "somethingDoesntLookRight": { "message": "Ada yang tidak beres? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Token berpotensi tidak autentik" }, + "swapTokenVerifiedSources": { + "message": "Dikonfirmasi oleh $1 sumber. Verifikasikan pada $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 memungkinkan hingga $2 desimal", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 kini telah aktif di $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Saat ini Anda menggunakan" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Mengalihkan jaringan akan membatalkan semua konfirmasi yang berstatus menunggu" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Pilih tema MetaMask yang Anda sukai." }, - "thingsToKeep": { - "message": "Ingatlah:" - }, "thirdPartySoftware": { "message": "Pemberitahuan perangkat lunak pihak ketiga", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Kiat" }, + "tipsForUsingAWallet": { + "message": "Tip menggunakan dompet" + }, + "tipsForUsingAWalletDescription": { + "message": "Menambahkan token membuka lebih banyak cara untuk menggunakan web3." + }, "to": { "message": "Untuk" }, @@ -5925,18 +5957,6 @@ "message": "Ke: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Hal ini memungkinkan Anda memilih jaringan untuk setiap situs, daripada satu jaringan yang dipilih untuk semua situs. Fitur ini akan mencegah Anda berpindah jaringan secara manual, yang dapat merusak pengalaman pengguna di situs tertentu." - }, - "toggleRequestQueueField": { - "message": "Pilih jaringan untuk setiap situs" - }, - "toggleRequestQueueOff": { - "message": "Nonaktif" - }, - "toggleRequestQueueOn": { - "message": "Aktif" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Daftar token" }, + "tokenMarketplace": { + "message": "Pasar token" + }, "tokenScamSecurityRisk": { "message": "penipuan dan risiko keamanan token" }, - "tokenShowUp": { - "message": "Token Anda mungkin tidak muncul secara otomatis di dompet Anda. " - }, "tokenStandard": { "message": "Standar token" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Uraikan kode kontrak cerdas" }, - "use4ByteResolutionDescription": { - "message": "Untuk meningkatkan pengalaman pengguna, kami menyesuaikan tab aktivitas dengan pesan berdasarkan kontrak cerdas yang berinteraksi dengan Anda. MetaMask menggunakan layanan yang disebut 4byte.directory untuk menguraikan kode data dan menampilkan versi kontrak cerdas yang lebih mudah dibaca. Ini membantu mengurangi peluang Anda untuk menyetujui tindakan kontrak cerdas yang berbahaya, tetapi dapat menyebabkan alamat IP Anda tersebar." - }, "useMultiAccountBalanceChecker": { "message": "Kelompokkan permintaan saldo akun" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Menurut catatan kami, nama jaringan mungkin tidak cocok dengan ID chain ini." }, - "xOfYPending": { - "message": "$1 dari $2 berstatus menunggu", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Ya" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 05cccdac0359..a88d710a6f81 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -252,9 +252,6 @@ "assetOptions": { "message": "Opzioni asset" }, - "attemptSendingAssets": { - "message": "Se si tenta di inviare risorse direttamente da una rete all'altra, ciò potrebbe comportare una perdita permanente della risorca coinvolta. Assicurati di usare un bridge." - }, "attributions": { "message": "Attribuzioni" }, @@ -369,9 +366,6 @@ "message": "Clicca qui per connettere il tuo Ledger tramite WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Clicca qui per aggiungere manualmente i token." - }, "close": { "message": "Chiudi" }, @@ -971,9 +965,6 @@ "loading": { "message": "Caricamento..." }, - "loadingTokens": { - "message": "Caricamento Tokens..." - }, "lock": { "message": "Disconnetti" }, @@ -1055,9 +1046,6 @@ "noConversionRateAvailable": { "message": "Tasso di conversione non disponibile" }, - "noTransactions": { - "message": "Nessuna Transazione" - }, "noWebcamFound": { "message": "La fotocamera del tuo computer non è stata trovata. Riprova." }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 80982f5b4144..3715f46215ab 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "ネットワークを追加中" }, - "addingTokens": { - "message": "トークンを追加しています" - }, "additionalNetworks": { "message": "他のネットワーク" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "攻撃者は、サイトのアドレスに若干の変更を加えてサイトを模倣することがあります。続行する前に、意図したサイトとやり取りしていることを確認してください。" }, + "alertMessageChangeInSimulationResults": { + "message": "このトランザクションの推定増減額が更新されました。先に進む前によく確認してください。" + }, "alertMessageGasEstimateFailed": { "message": "正確な手数料を提供できず、この見積もりは高い可能性があります。カスタムガスリミットの入力をお勧めしますが、それでもトランザクションが失敗するリスクがあります。" }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "手数料を手動で更新するまでこのトランザクションを進めることができません。" }, - "alertMessagePendingTransactions": { - "message": "前のトランザクションが完了するまでこのトランザクションを実行できません。トランザクションをキャンセルするか加速させる方法をご覧ください。" - }, "alertMessageSignInDomainMismatch": { "message": "要求元のサイトはサインインしようとしているサイトではありません。ログイン情報を盗もうとしている可能性があります。" }, "alertMessageSignInWrongAccount": { "message": "このサイトは正しくないアカウントでのサインインを求めています。" }, - "alertMessageSigningOrSubmitting": { - "message": "このトランザクションは、前のトランザクションが完了しないと実行されません。" - }, "alertModalAcknowledge": { "message": "リスクを承知したうえで続行します" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "すべてのアラートを確認する" }, + "alertReasonChangeInSimulationResults": { + "message": "結果が変更されました" + }, "alertReasonGasEstimateFailed": { "message": "不正確な手数料" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "ウォレットのトークンを自動検出し、NFTを表示して、アカウント残高の最新情報を一括で取得します" }, - "attemptSendingAssets": { - "message": "別のネットワークからアセットを送ろうとすると、アセットが失われる可能性があります。ネットワーク間で安全に資金を移動するには、必ずブリッジを使用してください。" - }, - "attemptSendingAssetsWithPortfolio": { - "message": "別のネットワークからアセットを送ろうとすると、アセットが失われる可能性があります。ネットワーク間で安全に資金を移動するには、必ず$1などのブリッジを使用してください。" - }, "attemptToCancelSwapForFree": { "message": "無料でスワップのキャンセルを試行" }, @@ -837,8 +828,11 @@ "bridge": { "message": "ブリッジ" }, - "bridgeDontSend": { - "message": "ブリッジを使用してください" + "bridgeCalculatingAmount": { + "message": "計算中..." + }, + "bridgeEnterAmount": { + "message": "金額を入力" }, "bridgeFrom": { "message": "ブリッジ元:" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "ネットワークを選択" }, + "bridgeSelectTokenAndAmount": { + "message": "トークンと金額を選択" + }, "bridgeTo": { "message": "ブリッジ先:" }, @@ -945,9 +942,6 @@ "message": "ここをクリックして、WebHIDでLedgerを接続します", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "トークンはいつでも手動で追加できます。" - }, "close": { "message": "閉じる" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "シークレットリカバリーフレーズの確認" }, - "confirmTitleApproveTransaction": { - "message": "許容額のリクエスト" - }, "confirmTitleDeployContract": { "message": "コントラクトを展開" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "トランザクションの要求" }, - "confirmationAlertModalDetails": { - "message": "資産とログイン情報を守るため、要求を拒否することをお勧めします。" - }, "confirmationAlertModalTitle": { "message": "この要求は不審です" }, @@ -1160,6 +1148,14 @@ "message": "$1と接続されました", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1個のネットワークを接続済み", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "$1と接続済み", + "description": "$1 represents network name" + }, "connecting": { "message": "接続中..." }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "デビットカードまたはクレジットカードの購入オプション" + }, "decimal": { "message": "トークンの小数桁数" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "使用限度は小数点以下$1桁を超えることができません。続けるには、小数点以下の桁を削除してください。" }, + "editSpendingCapSpecialCharError": { + "message": "数字のみで入力してください" + }, "enableAutoDetect": { "message": " 自動検出を有効にする" }, @@ -1904,10 +1906,42 @@ "message": "コード: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "サポートへのお問い合わせ", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "発生した問題についてご説明ください", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "情報は表示できませんが、ご心配なく。ウォレットと資金は安全です。", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "エラーメッセージ", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "発生した問題についてご説明ください", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "バグを再現する方法などの詳細をお知らせいただくと、問題の解決に役立ちます。", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "ありがとうございます。すぐに確認いたします。", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMaskにエラーが発生しました", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "再試行", + "description": "Button for try again" + }, "errorStack": { "message": "スタック:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "機能の種類" }, + "fundingMethod": { + "message": "入金方法" + }, "gas": { "message": "ガス" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "ガス代" }, - "gasIsETH": { - "message": "ガス代は$1です" - }, "gasLimit": { "message": "ガスリミット" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "詐欺やセキュリティのリスク。" }, - "learnToBridge": { - "message": "ブリッジの使い方" - }, "leaveMetaMask": { "message": "MetaMaskから離れますか?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Snapでトランザクションを完了させてください。" }, - "loadingTokens": { - "message": "トークンをロードしています..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "ウォレットの通知は現在無効になっています" }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio。" - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swapsはメンテナンス中です。後でもう一度確認してください。" }, @@ -2992,10 +3017,6 @@ "message": "$1が次の承認を求めています:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "このネットワークのネイティブトークンは$1です。ガス代にもこのトークンが使用されます。", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "ネットワークの詳細を編集" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "結構です" }, - "noTransactions": { - "message": "トランザクションがありません" - }, "noWebcamFound": { "message": "お使いのコンピューターのWebカメラが見つかりませんでした。もう一度お試しください。" }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "古いブラウザを使用しています。ブラウザをアップデートしないと、MetaMaskからセキュリティパッチや新機能を入手できなくなります。" }, + "overrideContentSecurityPolicyHeader": { + "message": "Content-Security-Policyヘッダーを上書き" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "このオプションは、DAppのContent-Security-Policyヘッダーによって拡張機能が正しく読み込まれない場合があるという、Firefoxの既知の問題に対する回避策です。特定のWebページの互換性に必要な場合を除き、このオプションを無効にすることはお勧めしません。" + }, "padlock": { "message": "南京錠" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "ここには、インストールされたSnapや接続されたサイトに付与したアクセス許可が表示されます。" }, - "permissionsPageTourDescription": { - "message": "これは、接続されたサイトやインストールされたSnapに付与したアクセス許可を管理するための、コントロールパネルです。" - }, - "permissionsPageTourTitle": { - "message": "「接続済みのサイト」が「アクセス許可」に変更されました" - }, "permitSimulationDetailInfo": { "message": "この数量のトークンをアカウントから転送する権限を使用者に付与しようとしています。" }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "資産を守りましょう" }, - "redesignedConfirmationsEnabledToggle": { - "message": "改善された署名要求" - }, - "redesignedConfirmationsToggleDescription": { - "message": "この機能をオンにすると、強化された形式で署名要求が表示されます。" - }, - "redesignedTransactionsEnabledToggle": { - "message": "改善されたトランザクション要求" - }, - "redesignedTransactionsToggleDescription": { - "message": "トランザクション要求を強化された形式で表示するには、この機能をオンにします。" - }, "refreshList": { "message": "リストを更新" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "これは署名を求めているサイトです。" }, + "requestFromInfoSnap": { + "message": "これは署名を求めているSnapです。" + }, "requestFromTransactionDescription": { "message": "これが承認を要求しているサイトです。" }, @@ -4417,6 +4426,10 @@ "message": "$1の要求", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "$1の要求", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "リクエストの承認待ち" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "受取額" }, + "simulationDetailsNoChanges": { + "message": "変更なし" + }, "simulationDetailsOutgoingHeading": { "message": "送金額" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "このトランザクションはおそらく失敗します" }, + "simulationDetailsUnavailable": { + "message": "利用不可" + }, "simulationErrorMessageV2": { "message": "ガス代を見積もることができませんでした。コントラクトにエラーがある可能性があり、このトランザクションは失敗するかもしれません。" }, @@ -5143,6 +5162,15 @@ "message": "今後のサポートは、$1の作成者にお問い合わせください。", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "この機能をオンにすると、既存のシークレットリカバリーフレーズで導出したSolanaアカウントをMetaMask Extensionに追加できるようになります。これは試験運用中のベータ機能であるため、自己責任でご使用ください。" + }, + "solanaSupportToggleTitle": { + "message": "「新規Solanaアカウントの追加 (ベータ)」を有効にする" + }, "somethingDoesntLookRight": { "message": "何か不審な点があれば、$1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "偽物のトークンの可能性" }, + "swapTokenVerifiedSources": { + "message": "$1個のソースで確定済み。$2で確認。", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1は小数点以下$2桁まで使用できます", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1が$2で有効になりました", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "次に切り替えました:" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "ネットワークを切り替えると、保留中の承認がすべてキャンセルされます" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "ご希望のMetaMaskテーマを選択してください。" }, - "thingsToKeep": { - "message": "留意点:" - }, "thirdPartySoftware": { "message": "サードパーティソフトウェアに関する通知", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "ヒント" }, + "tipsForUsingAWallet": { + "message": "ウォレット使用のヒント" + }, + "tipsForUsingAWalletDescription": { + "message": "トークンを追加すると、Web3の使用方法が増えます。" + }, "to": { "message": "移動先" }, @@ -5925,18 +5957,6 @@ "message": "移動先: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "これにより、選択した単一のネットワークをすべてのサイトで使用するのではなく、サイトごとにネットワークを選択できます。この機能により、特定のサイトでのユーザーエクスペリエンスの妨げとなる、ネットワークの手動切り替えが不要になります。" - }, - "toggleRequestQueueField": { - "message": "サイトごとにネットワークを選択する" - }, - "toggleRequestQueueOff": { - "message": "オフ" - }, - "toggleRequestQueueOn": { - "message": "オン" - }, "token": { "message": "トークン" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "トークンリスト:" }, + "tokenMarketplace": { + "message": "トークンマーケットプレイス" + }, "tokenScamSecurityRisk": { "message": "トークン関連の詐欺やセキュリティのリスク" }, - "tokenShowUp": { - "message": "トークンはウォレットに自動的に表示されない可能性があります。" - }, "tokenStandard": { "message": "トークン規格" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "スマートコントラクトのデコード" }, - "use4ByteResolutionDescription": { - "message": "ユーザーエクスペリエンスの向上のため、ユーザーがやり取りするスマートコントラクトに応じたメッセージで、アクティビティタブをカスタマイズします。MetaMaskは、4byte.directoryと呼ばれるサービスを利用してデータをデコードし、より読みやすいバージョンのスマートコントラクトを表示します。これにより、悪質なスマートコントラクトの操作を承認する可能性は減りますが、IPアドレスが公開されます。" - }, "useMultiAccountBalanceChecker": { "message": "アカウント残高の一括リクエスト" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "弊社の記録によると、ネットワーク名がこのチェーンIDと正しく一致していない可能性があります。" }, - "xOfYPending": { - "message": "$2件中$1件が保留中", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "はい" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 120651f0b759..148a820f5bfa 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "ಲೋಡ್ ಆಗುತ್ತಿದೆ..." }, - "loadingTokens": { - "message": "ಟೋಕನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..." - }, "localhost": { "message": "ಲೋಕಲ್‌ಹೋಸ್ಟ್ 8545" }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "ಯಾವುದೇ ಪರಿವರ್ತನೆ ದರ ಲಭ್ಯವಿಲ್ಲ" }, - "noTransactions": { - "message": "ನೀವು ಯಾವುದೇ ವಹಿವಾಟುಗಳನ್ನು ಹೊಂದಿಲ್ಲ" - }, "noWebcamFound": { "message": "ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ನ ವೆಬ್‌ಕ್ಯಾಮ್ ಕಂಡುಬಂದಿಲ್ಲ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 5fc7deb6a9ef..7e96106e099f 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "네트워크 추가" }, - "addingTokens": { - "message": "토큰 추가" - }, "additionalNetworks": { "message": "추가 네트워크" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "공격자는 사이트 주소를 약간 변경하여 유사 사이트를 만들기도 합니다. 계속하기 전에 정상적인 사이트와 상호 작용하고 있는지 확인하세요." }, + "alertMessageChangeInSimulationResults": { + "message": "이 트랜잭션의 예상 변경 사항이 업데이트되었습니다. 계속하기 전에 자세히 검토하세요." + }, "alertMessageGasEstimateFailed": { "message": "정확한 수수료를 제공할 수 없으며 예상 수수료가 높을 수 있습니다. 사용자 지정 가스 한도를 입력하는 것이 좋지만 트랜잭션이 여전히 실패할 위험이 있습니다." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "수수료를 직접 업데이트할 때까지는 이 트랜잭션을 진행할 수 없습니다." }, - "alertMessagePendingTransactions": { - "message": "이 트랜잭션은 이전 트랜잭션이 완료될 때까지 진행되지 않습니다. 트랜잭션을 취소하거나 속도를 올리는 법을 알아보세요." - }, "alertMessageSignInDomainMismatch": { "message": "요청을 보낸 사이트에 로그인되어 있지 않습니다. 이는 로그인 정보를 도용하려는 시도일 수 있습니다." }, "alertMessageSignInWrongAccount": { "message": "이 사이트에서 잘못된 계정으로 로그인하라고 요청합니다." }, - "alertMessageSigningOrSubmitting": { - "message": "이 트랜잭션은 이전 트랜잭션이 완료된 경우에만 진행됩니다." - }, "alertModalAcknowledge": { "message": "위험성을 인지했으며, 계속 진행합니다" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "모든 경고 검토하기" }, + "alertReasonChangeInSimulationResults": { + "message": "결과가 변경되었습니다" + }, "alertReasonGasEstimateFailed": { "message": "잘못된 수수료" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "지갑에서 토큰을 자동으로 감지하고, NFT를 표시하며, 계정 잔액을 일괄 업데이트하세요." }, - "attemptSendingAssets": { - "message": "다른 네트워크로 자산을 직접 전송하면 자산이 영구적으로 손실될 수 있습니다. 브릿지를 이용하여 네트워크 간에 자금을 안전하게 전송하세요." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "다른 네트워크에서 자산을 전송하려고 하면 자산이 손실될 수 있습니다. $1 같은 브릿지를 사용하여 네트워크 간에 자산을 안전하게 전송하세요." - }, "attemptToCancelSwapForFree": { "message": "무료 스왑 취소 시도" }, @@ -837,8 +828,11 @@ "bridge": { "message": "브리지" }, - "bridgeDontSend": { - "message": "전송하지 마세요, 브릿지 하세요" + "bridgeCalculatingAmount": { + "message": "계산 중..." + }, + "bridgeEnterAmount": { + "message": "금액 입력" }, "bridgeFrom": { "message": "브릿지 출처" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "네트워크 선택" }, + "bridgeSelectTokenAndAmount": { + "message": "토큰 및 금액 선택" + }, "bridgeTo": { "message": "브릿지 대상" }, @@ -945,9 +942,6 @@ "message": "WebHID를 통해 Ledger를 연결하려면 여기를 클릭하세요.", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "토큰은 언제든지 직접 추가할 수 있습니다." - }, "close": { "message": "닫기" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "비밀복구구문 컨펌" }, - "confirmTitleApproveTransaction": { - "message": "수당 청구" - }, "confirmTitleDeployContract": { "message": "계약 배포" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "트랜젝션 요청" }, - "confirmationAlertModalDetails": { - "message": "자산과 로그인 정보 보호를 위해 요청을 거부하는 것이 좋습니다." - }, "confirmationAlertModalTitle": { "message": "의심스러운 요청입니다" }, @@ -1160,6 +1148,14 @@ "message": "$1 계정과 연결됨", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 네트워크 연결됨", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "$1(으)로 연결됨", + "description": "$1 represents network name" + }, "connecting": { "message": "연결 중" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "직불카드 또는 신용카드 매수 옵션" + }, "decimal": { "message": "토큰 소수점" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "지출 한도는 소수점 이하 $1 자리를 초과할 수 없습니다. 계속하려면 소수점 이하 숫자를 제거하세요." }, + "editSpendingCapSpecialCharError": { + "message": "숫자만 입력" + }, "enableAutoDetect": { "message": " 자동 감지 활성화" }, @@ -1904,10 +1906,42 @@ "message": "코드: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "고객 지원 문의", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "오류에 대해 설명해 주세요", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "귀하의 정보를 표시할 수 없습니다. 걱정하지 마세요, 지갑과 자금은 안전합니다.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "오류 메시지", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "오류에 대해 설명해 주세요", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "버그를 재현할 수 있는 방법 등 자세한 정보를 알려주시면 문제를 해결하는 데 도움이 됩니다.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "감사합니다! 곧 확인하겠습니다.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask 오류 발생", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "다시 시도", + "description": "Button for try again" + }, "errorStack": { "message": "스택:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "기능 유형" }, + "fundingMethod": { + "message": "자금 조달 방법" + }, "gas": { "message": "가스" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "가스비" }, - "gasIsETH": { - "message": "가스는 $1입니다 " - }, "gasLimit": { "message": "가스 한도" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "사기 및 보안 위험" }, - "learnToBridge": { - "message": "브릿지 알아보기" - }, "leaveMetaMask": { "message": "MetaMask를 나가시겠습니까?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Snap에서 트랜잭션을 완료하세요." }, - "loadingTokens": { - "message": "토큰 불러오는 중..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "현재 지갑 알림이 꺼져 있습니다." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps 점검 중입니다. 나중에 다시 확인하세요." }, @@ -2992,10 +3017,6 @@ "message": "$1에서 다음 승인을 요청합니다:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "이 네트워크의 네이티브 토큰은 $1입니다. 이는 가스비 지불에 사용하는 토큰입니다. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "네트워크 세부 정보 편집" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "괜찮습니다" }, - "noTransactions": { - "message": "트랜잭션이 없습니다." - }, "noWebcamFound": { "message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도하세요." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "현재 사용 중인 브라우저가 최신 버전이 아닙니다. 브라우저를 업데이트하지 않으면 보안 패치와 MetaMask의 새 기능을 이용할 수 없습니다." }, + "overrideContentSecurityPolicyHeader": { + "message": "Content-Security-Policy 헤더 무시" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "이 옵션은 디앱의 Content-Security-Policy 헤더로 인해 확장 프로그램이 제대로 로드되지 않는 Firefox의 알려진 문제를 해결하기 위한 것입니다. 특정 웹 페이지와의 호환성을 위해 필요한 경우가 아니라면 이 옵션을 비활성화하지 않는 것이 좋습니다." + }, "padlock": { "message": "패드락" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "여기에서 설치된 Snap 또는 연결된 사이트의 권한을 확인할 수 있습니다." }, - "permissionsPageTourDescription": { - "message": "연결된 사이트 또는 설치된 Snap의 권한을 관리하기 위한 제어판입니다." - }, - "permissionsPageTourTitle": { - "message": "이제 연결된 사이트에 권한이 부여됩니다" - }, "permitSimulationDetailInfo": { "message": "내 계정에서 이만큼의 토큰을 사용할 수 있도록 승인합니다." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "자신의 자금을 지키세요" }, - "redesignedConfirmationsEnabledToggle": { - "message": "개선된 서명 요청" - }, - "redesignedConfirmationsToggleDescription": { - "message": "이 기능을 켜면 개선된 형식의 서명 요청을 확인할 수 있습니다." - }, - "redesignedTransactionsEnabledToggle": { - "message": "개선된 트랜잭션 요청" - }, - "redesignedTransactionsToggleDescription": { - "message": "이 기능을 켜면 개선된 형식의 트랜잭션 요청을 확인할 수 있습니다." - }, "refreshList": { "message": "새로 고침 목록" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "서명이 필요한 사이트입니다." }, + "requestFromInfoSnap": { + "message": "이는 서명을 요청하는 Snap입니다." + }, "requestFromTransactionDescription": { "message": "컨펌이 필요한 사이트입니다." }, @@ -4417,6 +4426,10 @@ "message": "$1 요청 중", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "$1 요청 중", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "확인 대기 중인 요청" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "받음:" }, + "simulationDetailsNoChanges": { + "message": "변경 사항 없음" + }, "simulationDetailsOutgoingHeading": { "message": "보냄:" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "이 트랜잭션은 실패할 가능성이 높습니다" }, + "simulationDetailsUnavailable": { + "message": "사용할 수 없음" + }, "simulationErrorMessageV2": { "message": "가스를 추정할 수 없었습니다. 계약에 오류가 있을 수 있으며 이 트랜잭션이 실패할 수 있습니다." }, @@ -5143,6 +5162,15 @@ "message": "$1 작성자에게 연락하여 향후 지원을 요청하세요.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "솔라나" + }, + "solanaSupportToggleDescription": { + "message": "이 기능을 켜면 기존 비밀복구구문에서 파생된 솔라나 계정을 MetaMask 확장 프로그램에 추가하는 옵션이 제공됩니다. 이는 실험적 베타 기능이므로 사용자의 책임하에 사용해야 합니다." + }, + "solanaSupportToggleTitle": { + "message": "'새 솔라나 계정 추가(베타)' 활성화" + }, "somethingDoesntLookRight": { "message": "무언가 잘못되었나요? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "잠재적 모조 토큰" }, + "swapTokenVerifiedSources": { + "message": "$1 소스에서 컨펌함. $2에서 확인하세요.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1은(는) 소수점 이하 $2까지 허용됩니다.", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 계정이 현재 $2 네트워크에서 활성화되어 있습니다", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "다음을 사용 중입니다:" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "네트워크를 전환하면 대기 중인 모든 컨펌 작업이 취소됩니다." }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "원하는 MetaMask 테마를 선택하세요." }, - "thingsToKeep": { - "message": "유의 사항:" - }, "thirdPartySoftware": { "message": "타사 소프트웨어 알림", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "팁" }, + "tipsForUsingAWallet": { + "message": "지갑 사용 팁" + }, + "tipsForUsingAWalletDescription": { + "message": "토큰을 추가하면 웹3를 더 다양한 방법으로 사용할 수 있습니다." + }, "to": { "message": "수신" }, @@ -5925,18 +5957,6 @@ "message": "수신: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "이 기능을 이용하면 모든 사이트에 한 가지 네트워크를 선택하여 사용하는 대신, 사이트별로 네트워크를 다르게 선택할 수 있습니다. 이 기능을 사용하면 수동으로 네트워크를 전환하지 않아도 되므로 특정 사이트에서 사용자 경험이 저해되지 않습니다." - }, - "toggleRequestQueueField": { - "message": "각 사이트별 네트워크 선택" - }, - "toggleRequestQueueOff": { - "message": "끄기" - }, - "toggleRequestQueueOn": { - "message": "켜기" - }, "token": { "message": "토큰" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "토큰 목록:" }, + "tokenMarketplace": { + "message": "토큰 마켓플레이스" + }, "tokenScamSecurityRisk": { "message": "토큰 사기 및 보안 위험" }, - "tokenShowUp": { - "message": "토큰이 지갑에 자동으로 표시되지 않을 수 있습니다. " - }, "tokenStandard": { "message": "토큰 표준" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "스마트 계약 디코딩" }, - "use4ByteResolutionDescription": { - "message": "인터렉션하는 스마트 계약에 따라 메시지를 이용하여 활동 탭을 사용자 맞춤하여 사용자 경험을 개선합니다. MetaMask는 4byte.directory라는 서비스를 통해 데이터를 디코딩하여 스마트 계약을 읽기 쉬운 버전으로 보여 줍니다. 이는 악의적인 스마트 계약을 승인할 가능성을 줄이는 데 도움이 되지만 IP 주소가 공유될 수 있습니다." - }, "useMultiAccountBalanceChecker": { "message": "일괄 계정 잔액 요청" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "기록에 따르면 네트워크 이름이 이 체인 ID와 일치하지 않습니다." }, - "xOfYPending": { - "message": "$1/$2개 보류 중", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "예" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index fe825ae6b798..3e5586f39f50 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Įkeliama..." }, - "loadingTokens": { - "message": "Įkeliami žetonai..." - }, "localhost": { "message": "Vietinis serveris 8545" }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "Nėra keitimo kurso" }, - "noTransactions": { - "message": "Neturite jokių operacijų" - }, "noWebcamFound": { "message": "Jūsų kompiuterio vaizdo kamera nerasta. Bandykite dar kartą." }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index 697af7849327..a1e166937385 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Notiek ielāde..." }, - "loadingTokens": { - "message": "Ielādē marķierus..." - }, "localhost": { "message": "Resursdators 8545" }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "Konversijas kurss nav pieejams" }, - "noTransactions": { - "message": "Jums nav neviena darījuma." - }, "noWebcamFound": { "message": "Jūsu datora tīmekļa kamera netika atrasta. Lūdzu, mēģiniet vēlreiz." }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index dc42e639ff2a..3605febbc7d7 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -374,9 +374,6 @@ "loading": { "message": "Memuatkan..." }, - "loadingTokens": { - "message": "Memuatkan Token..." - }, "lock": { "message": "Log keluar" }, @@ -433,9 +430,6 @@ "noConversionRateAvailable": { "message": "Tiada Kadar Penukaran yang Tersedia" }, - "noTransactions": { - "message": "Anda tiada transaksi" - }, "noWebcamFound": { "message": "Webcam komputer anda tidak dijumpai. Sila cuba semula." }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index cbebb9a14563..acf66091845f 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -149,9 +149,6 @@ "loading": { "message": "Bezig met laden..." }, - "loadingTokens": { - "message": "Tokens laden ..." - }, "lock": { "message": "Uitloggen" }, @@ -186,9 +183,6 @@ "next": { "message": "volgende" }, - "noTransactions": { - "message": "Geen transacties" - }, "pastePrivateKey": { "message": "Plak hier uw privésleutelstring:", "description": "For importing an account from a private key" diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index 45a101fc83a5..8459d3ef3a5f 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -369,9 +369,6 @@ "loading": { "message": "Laster inn ..." }, - "loadingTokens": { - "message": "Laster tokener ..." - }, "localhost": { "message": "Lokalvert 8545" }, @@ -434,9 +431,6 @@ "noConversionRateAvailable": { "message": "Ingen konverteringsrate tilgjengelig " }, - "noTransactions": { - "message": "Du har ingen transaksjoner" - }, "noWebcamFound": { "message": "Datamaskinens webkamera ble ikke funnet. Vennligst prøv igjen." }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 3c08d76cb186..3dd9ae28d896 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -700,9 +700,6 @@ "loading": { "message": "Nilo-load..." }, - "loadingTokens": { - "message": "Nilo-load ang Mga Token..." - }, "localhost": { "message": "Localhost 8545" }, @@ -822,9 +819,6 @@ "noConversionRateAvailable": { "message": "Hindi Available ang Rate ng Conversion" }, - "noTransactions": { - "message": "Wala kang transaksyon" - }, "noWebcamFound": { "message": "Hindi nakita ang webcam ng iyong computer. Pakisubukan ulit." }, @@ -1591,10 +1585,6 @@ "whatsThis": { "message": "Ano ito?" }, - "xOfYPending": { - "message": "$1 sa $2 ang nakabinbin", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "Kailangan mong payagan ang pag-access sa camera para magamit ang feature na ito." }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index d22673fa9f1f..f7f5111c9973 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Ładowanie..." }, - "loadingTokens": { - "message": "Ładowanie tokenów..." - }, "localhost": { "message": "Serwer lokalny 8545" }, @@ -443,9 +440,6 @@ "noConversionRateAvailable": { "message": "Brak kursu waluty" }, - "noTransactions": { - "message": "Nie ma transakcji" - }, "noWebcamFound": { "message": "Twoja kamera nie została znaleziona. Spróbuj ponownie." }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 18e1c97fb129..56988b88057b 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Adicionar rede" }, - "addingTokens": { - "message": "Adicionando tokens" - }, "additionalNetworks": { "message": "Redes adicionais" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Golpistas às vezes imitam os sites fazendo pequenas alterações em seus endereços. Certifique-se de estar interagindo com o site correto antes de continuar." }, + "alertMessageChangeInSimulationResults": { + "message": "As mudanças estimadas para esta transação foram atualizadas. Revise-as com atenção antes de prosseguir." + }, "alertMessageGasEstimateFailed": { "message": "Não conseguimos fornecer uma taxa precisa, e essa estimativa pode estar alta. Sugerimos que você informe um limite de gás personalizado, mas há o risco de a transação falhar mesmo assim." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Não podemos prosseguir com essa transação até você atualizar manualmente a taxa." }, - "alertMessagePendingTransactions": { - "message": "Essa transação não será processada até que a transação anterior seja concluída. Saiba como cancelar ou acelerar uma transação." - }, "alertMessageSignInDomainMismatch": { "message": "O site solicitante não é o mesmo em que você está entrando. Isso pode se tratar de uma tentativa de roubar suas credenciais de login." }, "alertMessageSignInWrongAccount": { "message": "Este site está pedindo que você entre usando a conta incorreta." }, - "alertMessageSigningOrSubmitting": { - "message": "Essa transação só será processada quando sua transação anterior for concluída." - }, "alertModalAcknowledge": { "message": "Eu reconheço o risco e ainda quero prosseguir" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Conferir todos os alertas" }, + "alertReasonChangeInSimulationResults": { + "message": "Os resultados mudaram" + }, "alertReasonGasEstimateFailed": { "message": "Taxa imprecisa" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Detecte automaticamente os tokens em sua carteira, exiba NFTs e receba atualizações de saldo de contas em lote" }, - "attemptSendingAssets": { - "message": "Você poderá perder seus ativos se tentar enviá-los a partir de outra rede. Transfira fundos entre redes com segurança usando uma ponte." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Você poderá perder seus ativos se tentar enviá-los a partir de outra rede. Transfira os fundos com segurança entre redes usando uma ponte, como $1" - }, "attemptToCancelSwapForFree": { "message": "Tentar cancelar a troca sem custo" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Ponte" }, - "bridgeDontSend": { - "message": "Usar uma ponte, não enviar" + "bridgeCalculatingAmount": { + "message": "Calculando..." + }, + "bridgeEnterAmount": { + "message": "Insira o valor" }, "bridgeFrom": { "message": "Ponte de" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Selecionar rede" }, + "bridgeSelectTokenAndAmount": { + "message": "Selecionar token e valor" + }, "bridgeTo": { "message": "Ponte para" }, @@ -945,9 +942,6 @@ "message": "Clique aqui para conectar seu Ledger por meio do WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Sempre é possível adicionar tokens manualmente." - }, "close": { "message": "Fechar" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Confirmar Frase de Recuperação Secreta" }, - "confirmTitleApproveTransaction": { - "message": "Solicitação de limite de gasto" - }, "confirmTitleDeployContract": { "message": "Implementar um contrato" }, @@ -1054,8 +1045,8 @@ "confirmTitleTransaction": { "message": "Solicitação de transação" }, - "confirmationAlertModalDetails": { - "message": "Para proteger seus ativos e informações de login, é recomendável que você recuse a solicitação." + "confirmationAlertDetails": { + "message": "Para proteger seus ativos, é recomendável que você recuse a solicitação." }, "confirmationAlertModalTitle": { "message": "Esta solicitação é suspeita" @@ -1160,6 +1151,14 @@ "message": "Conectado com $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 redes conectadas", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Conectado com $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Conectando" }, @@ -1493,6 +1492,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Opções de compra com cartão de débito ou crédito" + }, "decimal": { "message": "Decimal do token" }, @@ -1802,6 +1804,9 @@ "editSpendingCapError": { "message": "O limite de gastos não pode exceder $1 dígitos decimais. Remova os dígitos decimais para continuar." }, + "editSpendingCapSpecialCharError": { + "message": "Insira somente números" + }, "enableAutoDetect": { "message": " Ativar detecção automática" }, @@ -1904,10 +1909,42 @@ "message": "Código: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Falar com o suporte", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Descreva o que aconteceu", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Suas informações não podem ser exibidas. Não se preocupe, sua carteira e fundos estão seguros.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Mensagem de erro", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Descreva o que aconteceu", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Compartilhar detalhes como a forma de reproduzir o erro nos ajudará a corrigir o problema.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Obrigado! Verificaremos em breve.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "A MetaMask encontrou um erro", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Tentar novamente", + "description": "Button for try again" + }, "errorStack": { "message": "Lista:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2107,9 @@ "functionType": { "message": "Tipo de função" }, + "fundingMethod": { + "message": "Forma de financiamento" + }, "gas": { "message": "Gás" }, @@ -2083,9 +2123,6 @@ "gasFee": { "message": "Taxa de gás" }, - "gasIsETH": { - "message": "O gás é $1 " - }, "gasLimit": { "message": "Limite de gás" }, @@ -2647,9 +2684,6 @@ "learnScamRisk": { "message": "golpes e riscos de segurança." }, - "learnToBridge": { - "message": "Aprenda a usar uma ponte" - }, "leaveMetaMask": { "message": "Sair da MetaMask?" }, @@ -2741,9 +2775,6 @@ "loadingScreenSnapMessage": { "message": "Por favor, conclua a transação no Snap." }, - "loadingTokens": { - "message": "Carregando tokens..." - }, "localhost": { "message": "Host local 8545" }, @@ -2845,9 +2876,6 @@ "metamaskNotificationsAreOff": { "message": "As notificações de carteiras estão inativas no momento." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "O recurso de Trocas da MetaMask está em manutenção. Verifique novamente mais tarde." }, @@ -2992,10 +3020,6 @@ "message": "$1 solicita sua aprovação para:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "O token nativo dessa rede é $1. Esse é o token usado para taxas de gás.", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Editar detalhes da rede" }, @@ -3280,9 +3304,6 @@ "noThanks": { "message": "Não, obrigado" }, - "noTransactions": { - "message": "Você não tem transações" - }, "noWebcamFound": { "message": "A webcam do seu computador não foi encontrada. Tente novamente." }, @@ -3724,6 +3745,12 @@ "outdatedBrowserNotification": { "message": "Seu navegador está desatualizado. Se não o atualizar, você não conseguirá baixar patches de segurança e obter novos recursos da MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Substituir o cabeçalho Content-Security-Policy" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Esta opção é uma solução alternativa a um problema conhecido no Firefox, onde o cabeçalho Content-Security-Policy de um dapp (aplicativo descentralizado) pode impedir que a extensão seja carregada corretamente. Não é recomendável desativar esta opção, a menos que necessário para compatibilidade com páginas web específicas." + }, "padlock": { "message": "Cadeado" }, @@ -4032,12 +4059,6 @@ "permissionsPageEmptySubContent": { "message": "Aqui você pode ver as permissões que deu aos snaps instalados ou sites conectados." }, - "permissionsPageTourDescription": { - "message": "Este é o seu painel de controle para gerenciar as permissões dadas aos sites conectados e snaps instalados." - }, - "permissionsPageTourTitle": { - "message": "Sites conectados agora são permissões" - }, "permitSimulationDetailInfo": { "message": "Você está autorizando o consumidor a gastar esta quantidade de tokens de sua conta." }, @@ -4289,18 +4310,6 @@ "recoveryPhraseReminderTitle": { "message": "Proteja seus fundos" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Solicitações de assinatura aprimoradas" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Ative para ver as solicitações de assinatura em um formato aprimorado." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Solicitações de transação aprimoradas" - }, - "redesignedTransactionsToggleDescription": { - "message": "Ative esta opção para ver as solicitações de transação em formato aprimorado." - }, "refreshList": { "message": "Atualizar lista" }, @@ -4395,6 +4404,9 @@ "requestFromInfo": { "message": "Este é o site solicitando sua assinatura." }, + "requestFromInfoSnap": { + "message": "Este é o Snap solicitando sua assinatura." + }, "requestFromTransactionDescription": { "message": "Este é o site solicitando sua confirmação." }, @@ -4417,6 +4429,10 @@ "message": "Solicitando para $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Solicitando para $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "solicitações aguardando confirmação" }, @@ -4895,6 +4911,9 @@ "simulationDetailsIncomingHeading": { "message": "Você recebe" }, + "simulationDetailsNoChanges": { + "message": "Nenhuma alteração" + }, "simulationDetailsOutgoingHeading": { "message": "Você envia" }, @@ -4917,6 +4936,9 @@ "simulationDetailsTransactionReverted": { "message": "Essa transação provavelmente falhará" }, + "simulationDetailsUnavailable": { + "message": "Indisponível" + }, "simulationErrorMessageV2": { "message": "Não conseguimos estimar o preço do gás. Pode haver um erro no contrato, e essa transação poderá falhar." }, @@ -5143,6 +5165,15 @@ "message": "Contate os criadores de $1 para receber mais suporte.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Ativar este recurso lhe dará a opção de adicionar uma conta Solana à sua extensão da MetaMask derivada de sua Frase de Recuperação Secreta existente. Este é um recurso beta experimental, portanto seu uso será por sua conta e risco." + }, + "solanaSupportToggleTitle": { + "message": "Ative \"Adicionar uma nova conta Solana (Beta)\"" + }, "somethingDoesntLookRight": { "message": "Alguma coisa não parece certa? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5832,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Token potencialmente inautêntico" }, + "swapTokenVerifiedSources": { + "message": "Confirmado por $1 fontes. Verificar em $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 permite até $2 decimais", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5894,6 @@ "message": "$1 agora está ativa na $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Você alternou para" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "A alternância de redes cancelará todas as confirmações pendentes" }, @@ -5898,9 +5930,6 @@ "themeDescription": { "message": "Escolha o seu tema preferido para a MetaMask." }, - "thingsToKeep": { - "message": "Informações importantes:" - }, "thirdPartySoftware": { "message": "Aviso de software de terceiros", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5947,12 @@ "tips": { "message": "Dicas" }, + "tipsForUsingAWallet": { + "message": "Dicas para usar uma carteira" + }, + "tipsForUsingAWalletDescription": { + "message": "Adicionar tokens libera mais formas de usar a web3." + }, "to": { "message": "Para" }, @@ -5925,18 +5960,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Isso permite que você selecione uma rede para cada site em vez de uma única rede selecionada para todos eles. O recurso evitará que você alterne manualmente entre redes, o que pode atrapalhar sua experiência de usuário em determinados sites." - }, - "toggleRequestQueueField": { - "message": "Selecionar redes para cada site" - }, - "toggleRequestQueueOff": { - "message": "Não" - }, - "toggleRequestQueueOn": { - "message": "Sim" - }, "token": { "message": "Token" }, @@ -5970,12 +5993,12 @@ "tokenList": { "message": "Listas de tokens" }, + "tokenMarketplace": { + "message": "Marketplace de tokens" + }, "tokenScamSecurityRisk": { "message": "golpes e riscos de segurança envolvendo tokens" }, - "tokenShowUp": { - "message": "Seus tokens podem não aparecer automaticamente em sua carteira." - }, "tokenStandard": { "message": "Padrão de token" }, @@ -6288,9 +6311,6 @@ "use4ByteResolution": { "message": "Decodificar contratos inteligentes" }, - "use4ByteResolutionDescription": { - "message": "Para melhorar a experiência do usuário, personalizamos a guia de atividades com mensagens baseadas nos contratos inteligentes com os quais você interage. A MetaMask usa um serviço chamado 4byte.directory para decodificar os dados e exibir a você uma versão de um contrato inteligente que é mais fácil de ler. Isso ajuda a reduzir suas chances de aprovar ações de contratos inteligentes mal-intencionados, mas pode resultar no compartilhamento do seu endereço IP." - }, "useMultiAccountBalanceChecker": { "message": "Agrupar solicitações de saldo de contas" }, @@ -6484,10 +6504,6 @@ "wrongNetworkName": { "message": "De acordo com os nossos registros, o nome da rede pode não corresponder a esta ID de cadeia." }, - "xOfYPending": { - "message": "$1 de $2 pendente(s)", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Sim" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index ebf2af88c186..3c9c30147276 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1086,9 +1086,6 @@ "loading": { "message": "Carregando..." }, - "loadingTokens": { - "message": "Carregando tokens..." - }, "localhost": { "message": "Host local 8545" }, @@ -1276,9 +1273,6 @@ "noConversionRateAvailable": { "message": "Não há uma taxa de conversão disponível" }, - "noTransactions": { - "message": "Você não tem transações" - }, "noWebcamFound": { "message": "A webcam do seu computador não foi encontrada. Tente novamente." }, @@ -2375,10 +2369,6 @@ "whatsThis": { "message": "O que é isso?" }, - "xOfYPending": { - "message": "$1 de $2 pendente", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "Você precisa permitir o acesso à câmera para usar esse recurso." }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 912accba29be..db440fcfd264 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -375,9 +375,6 @@ "loading": { "message": "Se încarcă…" }, - "loadingTokens": { - "message": "Se încarcă token-urile..." - }, "lock": { "message": "Deconectați-vă" }, @@ -437,9 +434,6 @@ "noConversionRateAvailable": { "message": "Nici o rată de conversie disponibilă" }, - "noTransactions": { - "message": "Nu aveți tranzacții" - }, "noWebcamFound": { "message": "Webcam-ul computerului dvs. nu a fost găsit. Vă rugăm să încercați din nou." }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 43e617a0aecd..e246a8030dd8 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Добавление сети" }, - "addingTokens": { - "message": "Добавление токенов" - }, "additionalNetworks": { "message": "Дополнительные сети" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Злоумышленники иногда имитируют сайты, внося небольшие изменения в адрес сайта. Прежде чем продолжить, убедитесь, что вы взаимодействуете с нужным сайтом." }, + "alertMessageChangeInSimulationResults": { + "message": "Предполагаемые изменения для этой транзакции обновлены. Прежде чем продолжить, внимательно просмотрите их." + }, "alertMessageGasEstimateFailed": { "message": "Мы не можем указать точную сумму комиссии, и эта оценка может быть высокой. Мы предлагаем вам ввести индивидуальный лимит газа, но существует риск, что транзакция все равно не удастся." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Мы не сможем продолжить эту транзакцию, пока вы не обновите комиссию вручную." }, - "alertMessagePendingTransactions": { - "message": "Эта транзакция не будет выполнена, пока не завершится предыдущая транзакция. Узнайте, как отменить или ускорить транзакцию." - }, "alertMessageSignInDomainMismatch": { "message": "Сайт, отправляющий запрос, не является сайтом, на который вы входите. Это может быть попыткой украсть ваши учетные данные." }, "alertMessageSignInWrongAccount": { "message": "Этот сайт просит вас войти в систему, используя неправильный счет." }, - "alertMessageSigningOrSubmitting": { - "message": "Эта транзакция будет выполнена только после завершения вашей предыдущей транзакции." - }, "alertModalAcknowledge": { "message": "Я осознал(-а) риск и все еще хочу продолжить" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Просмотреть все оповещения" }, + "alertReasonChangeInSimulationResults": { + "message": "Результаты изменились" + }, "alertReasonGasEstimateFailed": { "message": "Неточная комиссия" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Автоматически обнаруживайте токены в своем кошельке, отображайте NFT и получайте пакетные обновления баланса счета" }, - "attemptSendingAssets": { - "message": "Вы можете потерять свои активы, если попытаетесь отправить их из другой сети. Безопасно переводите средства между сетями с помощью моста." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Попытка отправки активов напрямую из одной сети в другую может привести к необратимой потере активов. Следует использовать мост, такой как $1" - }, "attemptToCancelSwapForFree": { "message": "Бесплатная попытка отменить своп" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Мост" }, - "bridgeDontSend": { - "message": "Мост, не отправлять" + "bridgeCalculatingAmount": { + "message": "Расчет..." + }, + "bridgeEnterAmount": { + "message": "Введите сумму" }, "bridgeFrom": { "message": "Мост из" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Выбор сети" }, + "bridgeSelectTokenAndAmount": { + "message": "Выберите токен и сумму" + }, "bridgeTo": { "message": "Мост в" }, @@ -945,9 +942,6 @@ "message": "Нажмите здесь, чтобы подключить свой Ledger через WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Вы также можете добавлять токены вручную." - }, "close": { "message": "Закрыть" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Подтвердите секретную фразу для восстановления" }, - "confirmTitleApproveTransaction": { - "message": "Запрос квоты" - }, "confirmTitleDeployContract": { "message": "Развернуть контракт" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Запрос транзакции" }, - "confirmationAlertModalDetails": { - "message": "Чтобы защитить ваши активы и данные для входа, рекомендуем отклонить запрос." - }, "confirmationAlertModalTitle": { "message": "Этот запрос подозрительный" }, @@ -1160,6 +1148,14 @@ "message": "Подключен к $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "Подключены $1 сети(-ей)", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Подключено с помощью $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Подключение..." }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Варианты покупки с помощью дебетовой или кредитной карты" + }, "decimal": { "message": "Число десятичных знаков токена" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Лимит расходов не может иметь более $1 десятичных знаков. Удалите лишние десятичные знаки, чтобы продолжить." }, + "editSpendingCapSpecialCharError": { + "message": "Введите только цифры" + }, "enableAutoDetect": { "message": " Включить автоопределение" }, @@ -1904,10 +1906,42 @@ "message": "Код: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Связаться с поддержкой", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Опишите, что произошло", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Невозможно показать вашу информацию. Не волнуйтесь, ваш кошелек и средства в безопасности.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Сообщение об ошибке", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Опишите, что произошло", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Предоставление подробной информации, например, о том, как мы можем воспроизвести ошибку, поможет нам решить проблему.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Спасибо! Мы скоро рассмотрим это.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask обнаружил ошибку", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Повторить попытку", + "description": "Button for try again" + }, "errorStack": { "message": "Стек:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Тип функции" }, + "fundingMethod": { + "message": "Способ пополнения" + }, "gas": { "message": "Газ" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Плата за газ" }, - "gasIsETH": { - "message": "Газ стоит $1 " - }, "gasLimit": { "message": "Лимит газа" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "мошенничество и угрозы безопасности." }, - "learnToBridge": { - "message": "Научитесь создавать мост для" - }, "leaveMetaMask": { "message": "Выйти из MetaMask?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Завершите транзакцию в Snap." }, - "loadingTokens": { - "message": "Загрузка токенов..." - }, "localhost": { "message": "Локальный хост 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Уведомления кошелька в настоящее время неактивны." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "Сервис свопов MetaMask на техобслуживании. Зайдите позже." }, @@ -2992,10 +3017,6 @@ "message": "$1 запрашивает ваше одобрение на:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Нативный токен этой сети — $1. Этот токен используется для внесения платы за газ. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Изменить сведения о сети" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Нет, спасибо" }, - "noTransactions": { - "message": "У вас нет транзакций" - }, "noWebcamFound": { "message": "Веб-камера вашего компьютера не найдена. Попробуйте еще раз." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Ваш браузер устарел. Если вы не обновите его, то не сможете получать исправления безопасности и новые функции от MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Переопределить заголовок Content-Security-Policy" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Этот вариант является обходным решением известной проблемы в Firefox, когда заголовок Content-Security-Policy децентрализованного приложения может препятствовать правильной загрузке расширения. Отключать эту опцию не рекомендуется, если только это не требуется для совместимости конкретной веб-страницы." + }, "padlock": { "message": "Замок" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Здесь вы можете увидеть разрешения, которые вы предоставили установленным Snaps или подключенным сайтам." }, - "permissionsPageTourDescription": { - "message": "Это ваша панель управления для управления разрешениями, предоставленными подключенным сайтам и установленным Snaps." - }, - "permissionsPageTourTitle": { - "message": "Подключенные сайты теперь имеют разрешения" - }, "permitSimulationDetailInfo": { "message": "Вы даёте расходующему лицу разрешение потратить именно столько токенов из вашего аккаунта" }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Защитите свои средства" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Улучшенные запросы подписи" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Включите эту опцию, чтобы видеть запросы на подпись в расширенном формате." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Улучшенные запросы транзакций" - }, - "redesignedTransactionsToggleDescription": { - "message": "Включите эту опцию, чтобы видеть запросы транзакций в расширенном формате." - }, "refreshList": { "message": "Обновить список" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Это сайт, который запрашивает вашу подпись." }, + "requestFromInfoSnap": { + "message": "Это Snap, который запрашивает вашу подпись." + }, "requestFromTransactionDescription": { "message": "Это сайт, запрашивающий ваше подтверждение." }, @@ -4417,6 +4426,10 @@ "message": "Запрашивается для $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Запрашивается для $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "запросы, ожидающие подтверждения" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Вы получаете" }, + "simulationDetailsNoChanges": { + "message": "Изменений нет" + }, "simulationDetailsOutgoingHeading": { "message": "Вы отправляете" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Эта транзакция, скорее всего, не удастся" }, + "simulationDetailsUnavailable": { + "message": "Недоступно" + }, "simulationErrorMessageV2": { "message": "Мы не смогли оценить размер платы за газ. В контракте может быть ошибка, и эта транзакция может завершиться неудачно." }, @@ -5143,6 +5162,15 @@ "message": "Свяжитесь с авторами $1 для получения дополнительной поддержки.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Включение этой функции даст вам возможность добавить в ваше расширение MetaMask счет Solana, созданный на основе вашей существующей секретной фразы для восстановления. Это экспериментальная бета-функция, которая используется вами на ваш страх и риск." + }, + "solanaSupportToggleTitle": { + "message": "Включить «Добавить новый счет Solana (бета-версия)»" + }, "somethingDoesntLookRight": { "message": "Что-то не так? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Потенциально неаутентичный токен" }, + "swapTokenVerifiedSources": { + "message": "Подтверждено $1 источниками. Подтвердить на $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 позволяет использовать до $2 десятичных знаков", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 теперь активен в $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Сейчас вы используете" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "В случае смены сетей все ожидающие подтверждения будут отменены" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Выберите предпочитаемую тему MetaMask." }, - "thingsToKeep": { - "message": "Что нужно помнить:" - }, "thirdPartySoftware": { "message": "Уведомление о стороннем ПО", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Советы" }, + "tipsForUsingAWallet": { + "message": "Советы по использованию кошелька" + }, + "tipsForUsingAWalletDescription": { + "message": "Добавление токенов открывает больше способов использования web3." + }, "to": { "message": "Адресат" }, @@ -5925,18 +5957,6 @@ "message": "Адресат: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Это позволяет вам выбрать сеть для каждого сайта вместо одной выбранной сети для всех сайтов. Эта функция не позволит вам переключать сети вручную, что может снизить удобство использования некоторых сайтов." - }, - "toggleRequestQueueField": { - "message": "Выберите сети для каждого сайта" - }, - "toggleRequestQueueOff": { - "message": "Выкл." - }, - "toggleRequestQueueOn": { - "message": "Вкл." - }, "token": { "message": "Токен" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Списки токенов:" }, + "tokenMarketplace": { + "message": "Торговая площадка для токенов" + }, "tokenScamSecurityRisk": { "message": "мошенничество с токенами и угрозы безопасности" }, - "tokenShowUp": { - "message": "Ваши токены могут не отображаться автоматически в вашем кошельке. " - }, "tokenStandard": { "message": "Стандарт токена" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Расшифровать смарт-контракты" }, - "use4ByteResolutionDescription": { - "message": "Чтобы улучшить взаимодействие с пользователем, мы настраиваем вкладку действий, добавляя сообщения на основе смарт-контрактов, с которыми вы взаимодействуете. MetaMask использует службу под названием 4byte.directory для декодирования данных и показа версии смарт-контакта, которую легче читать. Это помогает снизить шансы того, что вы одобрите вредоносные действия смарт-контракта, но может привести к раскрытию вашего IP-адреса." - }, "useMultiAccountBalanceChecker": { "message": "Пакетные запросы баланса счета" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Согласно нашим записям, имя сети может не соответствовать этому ID блокчейна." }, - "xOfYPending": { - "message": "$1 из $2 ожидающих", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Да" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 829435f28ff7..a65bc68593f6 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -368,9 +368,6 @@ "loading": { "message": "Načítám..." }, - "loadingTokens": { - "message": "Načítám tokeny..." - }, "lock": { "message": "Odhlásit" }, @@ -421,9 +418,6 @@ "noConversionRateAvailable": { "message": "Nie je k dispozícii žiadna sadzba konverzie" }, - "noTransactions": { - "message": "Žádné transakce" - }, "noWebcamFound": { "message": "Webová kamera vášho počítača sa nenašla. Skúste znova." }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index cb82e0358212..ebc303e90c5c 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -375,9 +375,6 @@ "loading": { "message": "Nalaganje ..." }, - "loadingTokens": { - "message": "Nalaganje žetonov ..." - }, "lock": { "message": "Odjava" }, @@ -434,9 +431,6 @@ "noConversionRateAvailable": { "message": "Menjalni tečaj ni na voljo" }, - "noTransactions": { - "message": "Nimate transakcij" - }, "noWebcamFound": { "message": "Spletna kamera ni najdena. Poskusite znova kasneje." }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index e15ae23086b3..1bbcd0754b92 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -378,9 +378,6 @@ "loading": { "message": "Учитава се..." }, - "loadingTokens": { - "message": "Učitavanje tokena..." - }, "lock": { "message": "Odjavi se" }, @@ -437,9 +434,6 @@ "noConversionRateAvailable": { "message": "Nije dostupan kurs za konverziju" }, - "noTransactions": { - "message": "Nemate transakcije" - }, "noWebcamFound": { "message": "Nije pronađena veb kamera na vašem kompjuteru. Molimo vas pokušajte ponovo." }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 163cdebc426e..818f1eb498d6 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -375,9 +375,6 @@ "loading": { "message": "Läser in..." }, - "loadingTokens": { - "message": "Laddar tokens..." - }, "lock": { "message": "Logga ut" }, @@ -434,9 +431,6 @@ "noConversionRateAvailable": { "message": "Ingen omräkningskurs tillgänglig" }, - "noTransactions": { - "message": "Du har inga överföringar" - }, "noWebcamFound": { "message": "Din dators webbkamera hittades inte. Vänligen försök igen." }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index c1535d76cdd8..c33abcb51a18 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -372,9 +372,6 @@ "loading": { "message": "Inapakia..." }, - "loadingTokens": { - "message": "Inapakia Vianzio..." - }, "lock": { "message": "Toka kwenye akaunti" }, @@ -428,9 +425,6 @@ "noConversionRateAvailable": { "message": "Hakuna Kiwango cha Ubadilishaji" }, - "noTransactions": { - "message": "Huna miamala." - }, "noWebcamFound": { "message": "Kamera yako ya kumpyuta haikupatikana. Tafadhali jaribu tena." }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index d5d1929a2dc4..c5905e4e0bee 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -209,9 +209,6 @@ "loading": { "message": "ஏற்றுகிறது…" }, - "loadingTokens": { - "message": "டோக்கன்களை ஏற்றுகிறது ..." - }, "localhost": { "message": "லோக்கல் ஹோஸ்ட் 8545" }, @@ -256,9 +253,6 @@ "next": { "message": "அடுத்தது" }, - "noTransactions": { - "message": "பரிவர்த்தனைகள் இல்லை" - }, "off": { "message": "ஆஃப்" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index e6c074fe1264..96166c6fb6ba 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -194,9 +194,6 @@ "loading": { "message": "กำลังโหลด..." }, - "loadingTokens": { - "message": "กำลังโหลดโทเค็น..." - }, "lock": { "message": "ออกจากระบบ" }, @@ -235,9 +232,6 @@ "next": { "message": "ถัดไป" }, - "noTransactions": { - "message": "ยังไม่มีรายการธุรกรรม" - }, "on": { "message": "เปิด" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 5bc72667356e..5ad00e37e848 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Nagdaragdag ng Network" }, - "addingTokens": { - "message": "Nagdaragdag ng mga token" - }, "additionalNetworks": { "message": "Mga karagdagang network" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Minsan ginagaya ng mga umaatake ang mga site sa pamamagitan ng paggawa ng maliliit na pagbabago sa address ng site. Tiyaking nakikipag-ugnayan ka sa nilalayong site bago ka magpatuloy." }, + "alertMessageChangeInSimulationResults": { + "message": "Na-update na ang mga tinatayang pagbabago para sa transaksyong ito. Suriin ang mga ito nang mabuti bago magpatuloy." + }, "alertMessageGasEstimateFailed": { "message": "Hindi kami makapagbigay ng tumpak na bayad at ang pagtantiya na ito ay maaaring mataas. Iminumungkahi namin na maglagay ka ng naka-custom na gas limit, ngunit may panganib na mabigo pa rin ang transaksyon." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Hindi tayo makakapagpatuloy sa transaksyong ito hanggang sa manwal mong i-update ang bayad." }, - "alertMessagePendingTransactions": { - "message": "Hindi magpapatuloy ang transaksyong ito hanggang makumpleto ang naunang transaksyon. Alamin kung paano kanselahin o pabilisin ang transaksyon." - }, "alertMessageSignInDomainMismatch": { "message": "Ang site na humihiling ay hindi ang site kung saan ka nagsa-signin. Ito ay maaring isang pagtatangka para nakawin ang iyong mga kredensiyal sa pag-login." }, "alertMessageSignInWrongAccount": { "message": "Hinihingi sa iyo ng site na ito na mag-sign in gamit ang maling account." }, - "alertMessageSigningOrSubmitting": { - "message": "Magpapatuloy lamang ang transaksyong ito kapag nakumpleto mo na ang naunang transaksyon." - }, "alertModalAcknowledge": { "message": "Kinikilala ko ang panganib at nais ko pa rin magpatuloy" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Suriin ang lahat ng alerto" }, + "alertReasonChangeInSimulationResults": { + "message": "Nabago ang mga resulta" + }, "alertReasonGasEstimateFailed": { "message": "Hindi tumpak na bayad" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "I-autodetect ang mga token sa wallet mo, ipakita ang mga NFT, at kumuha ng naka-batch na mga update sa balanse ng account" }, - "attemptSendingAssets": { - "message": "Maaari mong mawala ang mga asset kung susubukan mo itong ipadala mula sa isang network papunta sa isa pa. Ligtas ng maglipat ng pondo sa pagitan ng mga network sa pamamagitan ng paggamit ng bridge." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Maaaring mawala ang iyong mga asset kung susubukan mong ipadala ito mula sa ibang network. Ilipat ang mga pondo nang ligtas sa pagitan ng mga network gamit ang isang bridge, tulad ng $1" - }, "attemptToCancelSwapForFree": { "message": "Subukang kanselahin ang swap nang libre" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Bridge" }, - "bridgeDontSend": { - "message": "Bridge, huwag ipadala" + "bridgeCalculatingAmount": { + "message": "Nagkakalkula..." + }, + "bridgeEnterAmount": { + "message": "Ilagay ang halaga" }, "bridgeFrom": { "message": "I-bridge mula sa" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Pumili ng network" }, + "bridgeSelectTokenAndAmount": { + "message": "Piliin ang token at halaga" + }, "bridgeTo": { "message": "I-bridge papunta sa" }, @@ -945,9 +942,6 @@ "message": "Mag-click dito upang ikonekta ang iyong Ledger sa pamamagitan ng WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Maaari ka ring magdagdag ng mga token nang manu-mano." - }, "close": { "message": "Isara" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Kumpirmahin ang Lihim na Parirala sa Pagbawi" }, - "confirmTitleApproveTransaction": { - "message": "Hiling sa allowance" - }, "confirmTitleDeployContract": { "message": "Mag-deploy ng kontrata" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Hiling na Transaksyon" }, - "confirmationAlertModalDetails": { - "message": "Para protektahan ang iyong mga asset at impormasyon sa pag-login, iminumungkahi namin na tanggihan mo ang kahilingan." - }, "confirmationAlertModalTitle": { "message": "Kahina-hinala ang kahilingang ito" }, @@ -1160,6 +1148,14 @@ "message": "Nakakonekta sa $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 (na) network ang konektado", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Konektado sa $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Kumokonekta" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Mga opsyon sa pagbili gamit ang debit o credit card" + }, "decimal": { "message": "Decimal na token" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Ang cap sa paggastos ay hindi maaaring lumampas sa $1 (na) decimal digit. Alisin ang mga decimal digit para magpatuloy." }, + "editSpendingCapSpecialCharError": { + "message": "Maglagay lang ng mga numero" + }, "enableAutoDetect": { "message": " Paganahin ang autodetect" }, @@ -1904,10 +1906,42 @@ "message": "Code: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Makipag-ugnayan sa Suporta", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Ilarawan kung ano ang nangyari", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Hindi puwedeng ipakita ang impormasyon mo. Huwag mag-alala, ligtas ang wallet at mga pondo mo.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Mensahe ng error", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Ilarawan kung ano ang nangyari", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Matutulungan mo kaming ayusin ang problema kapag nagbahagi ka ng mga detalye gaya ng kung paano namin mare-reproduce ang bug.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Salamat! Susuriin namin ito sa lalong madaling panahon.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "Nagkaroon ng error sa MetaMask", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Subukan ulit", + "description": "Button for try again" + }, "errorStack": { "message": "Stack:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Uri ng Function" }, + "fundingMethod": { + "message": "Paraan ng pagpopondo" + }, "gas": { "message": "Gas" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Bayad sa gas" }, - "gasIsETH": { - "message": "Ang gas ay $1 " - }, "gasLimit": { "message": "Limitasyon ng gas" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "mga panloloko at panganib sa seguridad." }, - "learnToBridge": { - "message": "Matutong mag-bridge" - }, "leaveMetaMask": { "message": "Umalis sa MetaMask?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Mangyaring kumpletuhin ang transaksyon sa Snap." }, - "loadingTokens": { - "message": "Nilo-load ang Mga Token..." - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Hindi active sa kasalukuyan ang mga notipikasyon sa wallet." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "Kasalukuyang minementina ang MetaMask Swaps. Bumalik sa ibang pagkakataon." }, @@ -2992,10 +3017,6 @@ "message": "Ang $1 ay humihiling ng iyong pag-apruba para:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Ang native token sa network na ito ay $1. Ito ang token na ginagamit para sa mga gas fee. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "I-edit ang mga detalye ng network" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Salamat na lang" }, - "noTransactions": { - "message": "Wala kang transaksyon" - }, "noWebcamFound": { "message": "Hindi nakita ang webcam ng iyong computer. Pakisubukan ulit." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Hindi na updated ang iyong browser. Kapag hindi mo inupdate ang iyong browser, hindi ka makakakuha ng mga security patch at bagong feature mula sa MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Override Content-Security-Policy header" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Isang solusyon ang opsyon na ito para sa isang karaniwang isyu sa Firefox, kung saan pinipigilan ng Content-Security-Policy header ng isang dapp ang extension na mag-load nang tama. Hindi inirerekomenda ang hindi pagpapagana sa opsyon na ito maliban na lang kung kinakailangan para sa pagtutugma ng partikular na web page." + }, "padlock": { "message": "Padlock" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Dito mo makikita ang mga pahintulot na iyong binigay sa mga naka-install na Snap o konektadong site." }, - "permissionsPageTourDescription": { - "message": "Ito ang iyong control panel para pamahalaan ang mga permiso na ibinigay sa mga konektadong Snap." - }, - "permissionsPageTourTitle": { - "message": "Ang mga konektadong site ay pahintulot na ngayon" - }, "permitSimulationDetailInfo": { "message": "Binibigyan mo ang gumagastos ng permiso upang gumastos ng ganito karaming token mula sa iyong account." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Protektahan ang iyong pondo" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Pinahusay na hiling sa pirma" - }, - "redesignedConfirmationsToggleDescription": { - "message": "I-on ito upang makita ang mga hiling sa pirma sa isang pinahusay na format." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Pinahusay na mga hiling sa transaksyon" - }, - "redesignedTransactionsToggleDescription": { - "message": "I-on ito upang makita ang mga hiling sa transaksyon sa isang pinahusay na format." - }, "refreshList": { "message": "I-refresh ang listahan" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Ito ang site na humihiling ng iyong pirma." }, + "requestFromInfoSnap": { + "message": "Ito ang Snap na humihiling ng iyong pirma." + }, "requestFromTransactionDescription": { "message": "Ito ang site na humihiling ng iyong kumpirmasyon." }, @@ -4417,6 +4426,10 @@ "message": "Hinihiling para sa $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Humihiling ng $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "mga kahilingang hinihintay na tanggapin" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Natanggap mo" }, + "simulationDetailsNoChanges": { + "message": "Walang pagbabago" + }, "simulationDetailsOutgoingHeading": { "message": "Nagpadala ka" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Ang transaksyon na ito ay malamang na mabigo" }, + "simulationDetailsUnavailable": { + "message": "Hindi available" + }, "simulationErrorMessageV2": { "message": "Hindi namin nagawang tantyahin ang gas. Maaaring may error sa kontrata at maaaring mabigo ang transaksyong ito." }, @@ -5143,6 +5162,15 @@ "message": "Makipag-ugnayan sa mga tagalikha ng $1 para sa karagdagang suporta.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Kapag in-on ang feature na ito, magkakaroon ka ng opsyon na magdagdag ng Solana Account sa MetaMask extension mo mula sa dati mo nang Lihim na Parirala sa Pagbawi. Isa itong eksperimental na Beta feature kaya gamitin ito nang may pag-iingat." + }, + "solanaSupportToggleTitle": { + "message": "Paganahin ang \"Magdagdag ng bagong Solana account (Beta)\"" + }, "somethingDoesntLookRight": { "message": "Mayroon bang hindi tama? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Potensyal na hindi tunay na token" }, + "swapTokenVerifiedSources": { + "message": "Kinumpirma ng $1 (na) pinagmulan. I-verify sa $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "Ang $1 ay nagpapahintulot sa hanggang $2 na decimal", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "Ang $1 ay aktibo ngayon sa $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Lumipat ka sa" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Ang paglipat ng network ay magkakansela ng lahat ng nakabinbing kumpirmasyon" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Piliin ang mas gusto mong tema ng MetaMask." }, - "thingsToKeep": { - "message": "Mga bagay na dapat tandaan:" - }, "thirdPartySoftware": { "message": "Paunawa ng third-party na software", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Mga Tip" }, + "tipsForUsingAWallet": { + "message": "Mga tip sa paggamit ng wallet" + }, + "tipsForUsingAWalletDescription": { + "message": "Kapag nagdagdag ka ng mga token, magkakaroon ka ng mas maraming paraan para magamit ang web3." + }, "to": { "message": "Para kay/sa" }, @@ -5925,18 +5957,6 @@ "message": "Para kay/sa: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Pinahihintulutan ka nitong pumili ng network para sa bawat site sa halip na iisang piniling network para sa lahat ng site. Pipigilan ka ng feature na ito na magpalit ng network nang mano-mano, na maaaring makasira sa iyong karanasan bilang user sa ilang partikular na site." - }, - "toggleRequestQueueField": { - "message": "Piliin ang mga network para sa bawat site" - }, - "toggleRequestQueueOff": { - "message": "Naka-off" - }, - "toggleRequestQueueOn": { - "message": "Naka-on" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Mga listahan ng token" }, + "tokenMarketplace": { + "message": "Marketplace ng token" + }, "tokenScamSecurityRisk": { "message": "mga panloloko sa token at panganib sa seguridad" }, - "tokenShowUp": { - "message": "Maaaring hindi awtomatikong lumabas ang iyong mga token sa iyong wallet. " - }, "tokenStandard": { "message": "Pamantayan ng token" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "I-decode ang mga smart na kontrata" }, - "use4ByteResolutionDescription": { - "message": "Upang mapabuti ang karanasan ng user, kino-customize namin ang tab ng mga aktibidad gamit ang mga mensahe ayon sa mga smart na kontrata kung saan ka nakikipag-ugnayan. Gumagamit ang MetaMask ng serbisyong tinatawag na 4byte.directory para i-decode ang datos at ipakita sa iyo ang bersyon ng smart na kontrata na mas madaling basahin. Tumutulong ito na bawasan ang pagkakataon na aprubahan mo ang mga mapaminsalang aksyon sa smart na kontrata, ngunit maaaring magresulta sa pagbabahagi ng iyong IP address." - }, "useMultiAccountBalanceChecker": { "message": "Maramihang kahilingan sa balanse ng account" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Ayon sa aming mga talaan, ang pangalan ng network ay maaaring hindi tumugma nang tama sa ID ng chain na ito." }, - "xOfYPending": { - "message": "$1 ng $2 ang nakabinbin", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Oo" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index a71a64576142..43d474cdbf1b 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Ağ Ekleniyor" }, - "addingTokens": { - "message": "Token'leri ekleme" - }, "additionalNetworks": { "message": "İlave ağlar" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Saldırganlar bazen site adresinde küçük değişiklikler yaparak siteleri taklit edebilir. Devam etmeden önce planladığınız site ile etkileşim kurduğunuzdan emin olun." }, + "alertMessageChangeInSimulationResults": { + "message": "Bu işlem için tahmin edilen değişiklikler güncellendi. Devam etmeden önce dikkatle inceleyin." + }, "alertMessageGasEstimateFailed": { "message": "Kesin bir ücret sunamıyoruz ve bu tahmin yüksek olabilir. Özel bir gaz limiti girmenizi öneririz ancak işlemin yine de başarısız olma riski vardır." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Siz ücreti manuel olarak güncelleyene dek bu işleme devam edemiyoruz." }, - "alertMessagePendingTransactions": { - "message": "Önceki bir işlem tamamlanana dek bu işlem gerçekleşmeyecektir. Bir işlemi nasıl iptal edeceğinizi veya hızlandıracağınızı öğrenin." - }, "alertMessageSignInDomainMismatch": { "message": "Talepte bulunan site giriş yaptığınız site değil. Bu durum oturum açma bilgilerinizi çalma teşebbüsü olabilir." }, "alertMessageSignInWrongAccount": { "message": "Bu site sizden yanlış hesabı kullanarak giriş yapmanızı istiyor." }, - "alertMessageSigningOrSubmitting": { - "message": "Bu işlem sadece önceki işleminiz tamamlandıktan sonra gerçekleşecek." - }, "alertModalAcknowledge": { "message": "Riski anlıyor ve yine de ilerlemek istiyorum" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Tüm uyarıları incele" }, + "alertReasonChangeInSimulationResults": { + "message": "Sonuçlar değişti" + }, "alertReasonGasEstimateFailed": { "message": "Ücret yanlış" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Cüzdanınızdaki token'ler otomatik algılansın, NFT'ler gösterilsin ve toplu hesap bakiye güncellemeleri alınsın" }, - "attemptSendingAssets": { - "message": "Varlıklarınızı doğrudan bir ağdan diğerine göndermeye çalışırsanız onları kaybedebilirsiniz. Fonları köprü kullanarak ağlar arasında güvenli bir şekilde transfer edin." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Varlıkları doğrudan bir ağdan diğerine göndermeye çalışırsanız bu durum kalıcı varlık kaybına neden olabilir. $1 gibi bir köprü kullandığınızdan emin olun" - }, "attemptToCancelSwapForFree": { "message": "Swap işlemini ücretsiz iptal etme girişimi" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Köprü" }, - "bridgeDontSend": { - "message": "Köprü yap, gönderme" + "bridgeCalculatingAmount": { + "message": "Hesaplanıyor..." + }, + "bridgeEnterAmount": { + "message": "Miktar girin" }, "bridgeFrom": { "message": "Şuradan köprü:" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Ağ seç" }, + "bridgeSelectTokenAndAmount": { + "message": "Token ve miktar seçin" + }, "bridgeTo": { "message": "Şuraya köprü:" }, @@ -945,9 +942,6 @@ "message": "WebHID üzerinden Ledger'ınızı bağlamak için tıklayın", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Dilediğiniz zaman tokenleri manuel olarak ekleyebilirsiniz." - }, "close": { "message": "Kapat" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Gizli Kurtarma İfadesini Onayla" }, - "confirmTitleApproveTransaction": { - "message": "Ödenek talebi" - }, "confirmTitleDeployContract": { "message": "Bir sözleşme kurulumu gerçekleştirin" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "İşlem talebi" }, - "confirmationAlertModalDetails": { - "message": "Varlıklarınızı ve oturum açma bilgilerinizi korumak için talebi reddetmenizi öneririz." - }, "confirmationAlertModalTitle": { "message": "Bu talep şüphelidir" }, @@ -1160,6 +1148,14 @@ "message": "$1 ile bağlandı", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "$1 ağ bağlandı", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "$1 ile bağlandı", + "description": "$1 represents network name" + }, "connecting": { "message": "Bağlanıyor" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Banka kartı veya kredi kartı ile satın alma seçenekleri" + }, "decimal": { "message": "Token Ondalığı" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Harcama üst limiti $1 ondalık haneyi geçemez. Devam etmek için ondalık haneleri kaldırın." }, + "editSpendingCapSpecialCharError": { + "message": "Sadece rakam girin" + }, "enableAutoDetect": { "message": " Otomatik algılamayı etkinleştir" }, @@ -1904,10 +1906,42 @@ "message": "Kod: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Destek bölümüne ulaşın", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Ne olduğunu açıklayın", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Bilgileriniz gösterilemiyor. Endişelenmeyin, cüzdanınız ve paranız güvende.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Hata mesajı", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Ne olduğunu açıklayın", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Hatayı nasıl tekrarlayabileceğimiz gibi ayrıntıların paylaşılması sorunu çözmemize yardımcı olacaktır.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Teşekkürler! En kısa sürede ele alacağız.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask bir hata ile karşılaştı", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Tekrar deneyin", + "description": "Button for try again" + }, "errorStack": { "message": "Yığın:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "İşlev türü" }, + "fundingMethod": { + "message": "Para yatırma yöntemi" + }, "gas": { "message": "Gaz" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Gaz ücreti" }, - "gasIsETH": { - "message": "Gaz $1 " - }, "gasLimit": { "message": "Gaz limiti" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "dolandırıcılıklar ve güvenlik riskleri." }, - "learnToBridge": { - "message": "Köprü yapmayı öğren" - }, "leaveMetaMask": { "message": "MetaMask'ten ayrıl?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Lütfen işlemi Snap üzerinde tamamlayın." }, - "loadingTokens": { - "message": "Tokenler yükleniyor..." - }, "localhost": { "message": "Yerel Ana Bilgisayar 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Cüzdan bildirimleri şu anda aktif değil." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swap İşlemleri bakımda. Lütfen daha sonra tekrar kontrol edin." }, @@ -2992,10 +3017,6 @@ "message": "$1 sizden şunun için onay istiyor:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Bu ağdaki yerli token $1. Bu, gaz ücretleri için kullanılan tokendir. ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Ağ bilgilerini düzenle" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Hayır, istemiyorum" }, - "noTransactions": { - "message": "İşleminiz yok" - }, "noWebcamFound": { "message": "Bilgisayarınızın web kamerası bulunamadı. Lütfen tekrar deneyin." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Tarayıcınız güncel değil. Tarayıcınızı güncellemezseniz MetaMask'ten güvenlik yamalarını ve yeni özellikleri alamayacaksınız." }, + "overrideContentSecurityPolicyHeader": { + "message": "İçerik-Güvenlik-Politika başlığını geçersiz kıl" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Bu seçenek, Firefox'ta merkeziyetsiz bir uygulamanın İçerik-Güvenlik-Politika başlığının uzantının düzgün bir şekilde yüklenmesini önleyebildiği bilinen bir sorunun anlık çözümüdür. Belirli web sayfası uyumluluğu için gerekli olmadığı sürece bu seçeneğin devre dışı bırakılması önerilmez." + }, "padlock": { "message": "Asma Kilit" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Burada, yüklü Snap'lere veya bağlı sitelere verdiğiniz izinleri görebilirsiniz." }, - "permissionsPageTourDescription": { - "message": "Burası, bağlı sitelere ve yüklü Snap'lere verilen izinleri yönetebileceğiniz kontrol panelinizdir." - }, - "permissionsPageTourTitle": { - "message": "Bağlı siteler şimdi izinler oldu" - }, "permitSimulationDetailInfo": { "message": "Harcama yapan tarafa hesabınızdan bu kadar çok token'i harcama izni veriyorsunuz." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Paranızı koruyun" }, - "redesignedConfirmationsEnabledToggle": { - "message": "İmza talepleri iyileştirildi" - }, - "redesignedConfirmationsToggleDescription": { - "message": "İmza taleplerini geliştirilmiş bir biçimde görmek için bunu açın." - }, - "redesignedTransactionsEnabledToggle": { - "message": "İşlem talepleri iyileştirildi" - }, - "redesignedTransactionsToggleDescription": { - "message": "İşlem taleplerini gelişmiş bir biçimde görmek için bunu açın." - }, "refreshList": { "message": "Listeyi yenile" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "İmzanızı isteyen site budur." }, + "requestFromInfoSnap": { + "message": "İmzanızı isteyen Snap budur." + }, "requestFromTransactionDescription": { "message": "Bu site tarafından onayınız isteniyor." }, @@ -4417,6 +4426,10 @@ "message": "$1 için talep ediliyor", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "$1 için talep ediliyor", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "onaylanmayı bekleyen talepler" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Aldığınız" }, + "simulationDetailsNoChanges": { + "message": "Değişiklik yok" + }, "simulationDetailsOutgoingHeading": { "message": "Gönderdiğiniz" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Bu işlemin başarısız olması muhtemel" }, + "simulationDetailsUnavailable": { + "message": "Mevcut değil" + }, "simulationErrorMessageV2": { "message": "Gaz tahmini yapamadık. Sözleşmede bir hata olabilir ve bu işlem başarısız olabilir." }, @@ -5143,6 +5162,15 @@ "message": "Daha fazla destek için $1 oluşturucuları ile iletişime geçin.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Bu özelliği açtığınızda mevcut Gizli Kurtarma İfadenizden türetilen MetaMask Uzantınıza bir Solana Hesabı ekleme seçeneğiniz olacaktır. Bu, deneysel bir Beta özelliğidir, bu yüzden riski kendinize ait olarak kullanmalısınız." + }, + "solanaSupportToggleTitle": { + "message": "\"Yeni bir Solana hesabı ekle (Beta)\" seçeneğini etkinleştir" + }, "somethingDoesntLookRight": { "message": "Doğru görünmeyen bir şeyler mi var? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Sahte token olma potansiyeli var" }, + "swapTokenVerifiedSources": { + "message": "$1 kaynakları tarafından onaylandı. $2 üzerinde doğrulayın.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1, en fazla $2 ondalık basamağına izin verir", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1, $2 üzerinde aktif", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Şu anda kullandığınız ağ" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Ağ değiştirmek bekleyen tüm onayları iptal eder" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Tercih ettiğin MetaMask temasını seç." }, - "thingsToKeep": { - "message": "Unutulmaması gerekenler:" - }, "thirdPartySoftware": { "message": "Üçüncü taraf yazılım uyarısı", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "İpuçları" }, + "tipsForUsingAWallet": { + "message": "Cüzdan kullanma ipuçları" + }, + "tipsForUsingAWalletDescription": { + "message": "Token eklemek web3'ü kullanmanın daha fazla yolunun kilidini açar." + }, "to": { "message": "Kime" }, @@ -5925,18 +5957,6 @@ "message": "Alıcı: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Bu, tüm siteler için tek bir seçili ağ yerine her bir site için bir ağ seçebilmenize olanak sağlar. Bu özellik, manuel olarak ağ değiştirmenizi önleyebilir ve bu da belirli sitelerde kullanıcı deneyiminizi bozabilir." - }, - "toggleRequestQueueField": { - "message": "Her site için ağ seçin" - }, - "toggleRequestQueueOff": { - "message": "Kapalı" - }, - "toggleRequestQueueOn": { - "message": "Açık" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Token listeleri:" }, + "tokenMarketplace": { + "message": "Token pazar yeri" + }, "tokenScamSecurityRisk": { "message": "token dolandırıcılıkları ve güvenlik riskleri" }, - "tokenShowUp": { - "message": "Tokenleriniz cüzdanınızda otomatik olarak görünmeyebilir. " - }, "tokenStandard": { "message": "Token standardı" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Akıllı sözleşmelerin şifresini çöz" }, - "use4ByteResolutionDescription": { - "message": "Kullanıcı deneyiminizi iyileştirmek amacıyla aktivite sekmesini etkileşimde bulunduğunuz akıllı sözleşmelere bağlı mesajlarla kişiselleştiririz. MetaMask, verileri çözmek ve size okunması daha kolay olan bir akıllı sözleşme sürümü göstermek için 4byte.directory adlı bir hizmet kullanır. Böylece kötü amaçlı akıllı sözleşme eylemlerini onaylama ihtimalinizi düşürmeye yardımcı olur ancak IP adresinizin paylaşılmasına neden olabilir." - }, "useMultiAccountBalanceChecker": { "message": "Toplu hesap bakiyesi talepleri" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Kayıtlarımıza göre ağ adı bu zincir kimliği ile doğru şekilde eşleşmiyor olabilir." }, - "xOfYPending": { - "message": "$1 / $2 bekliyor", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Evet" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index b0c011690910..58787e9a2238 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -381,9 +381,6 @@ "loading": { "message": "Завантаження..." }, - "loadingTokens": { - "message": "Завантаження токенів…" - }, "localhost": { "message": "Локальний хост 8545" }, @@ -446,9 +443,6 @@ "noConversionRateAvailable": { "message": "Немає доступного обмінного курсу" }, - "noTransactions": { - "message": "У вас немає транзакцій" - }, "noWebcamFound": { "message": "Веб-камеру комп’ютера не знайдено. Повторіть спробу." }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 5fb6ee43bfb9..ce2c1c1330eb 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "Thêm mạng" }, - "addingTokens": { - "message": "Đang thêm token" - }, "additionalNetworks": { "message": "Mạng bổ sung" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "Kẻ tấn công đôi khi bắt chước các trang web bằng cách thực hiện những thay đổi nhỏ trong địa chỉ trang web. Đảm bảo bạn đang tương tác với trang web dự định trước khi tiếp tục." }, + "alertMessageChangeInSimulationResults": { + "message": "Các thay đổi ước tính cho giao dịch này đã được cập nhật. Hãy xem xét kỹ trước khi tiếp tục." + }, "alertMessageGasEstimateFailed": { "message": "Chúng tôi không thể cung cấp phí chính xác và ước tính này có thể cao. Chúng tôi khuyên bạn nên nhập hạn mức phí gas tùy chỉnh, nhưng vẫn có rủi ro giao dịch sẽ thất bại." }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "Chúng tôi không thể tiếp tục giao dịch này cho đến khi bạn cập nhật phí thủ công." }, - "alertMessagePendingTransactions": { - "message": "Giao dịch này sẽ không được thực hiện cho đến khi giao dịch trước đó hoàn tất. Tìm hiểu cách hủy hoặc đẩy nhanh giao dịch." - }, "alertMessageSignInDomainMismatch": { "message": "Trang web đưa ra yêu cầu không phải là trang web bạn đang đăng nhập. Đây có thể là một nỗ lực đánh cắp thông tin đăng nhập của bạn." }, "alertMessageSignInWrongAccount": { "message": "Trang web này yêu cầu bạn đăng nhập bằng tài khoản không đúng." }, - "alertMessageSigningOrSubmitting": { - "message": "Giao dịch này sẽ chỉ được thực hiện sau khi giao dịch trước đó của bạn hoàn tất." - }, "alertModalAcknowledge": { "message": "Tôi đã nhận thức được rủi ro và vẫn muốn tiếp tục" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "Xem lại tất cả cảnh báo" }, + "alertReasonChangeInSimulationResults": { + "message": "Kết quả đã thay đổi" + }, "alertReasonGasEstimateFailed": { "message": "Phí không chính xác" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "Tự động phát hiện token trong ví của bạn, hiển thị NFT và nhận hàng loạt thông tin cập nhật về số dư tài khoản" }, - "attemptSendingAssets": { - "message": "Bạn có thể bị mất tài sản nếu cố gắng gửi tài sản từ một mạng khác. Chuyển tiền an toàn giữa các mạng bằng cách sử dụng cầu nối." - }, - "attemptSendingAssetsWithPortfolio": { - "message": "Bạn có thể bị mất tài sản nếu cố gắng gửi tài sản từ một mạng khác. Chuyển tiền an toàn giữa các mạng bằng cách sử dụng cầu nối, chẳng hạn như $1" - }, "attemptToCancelSwapForFree": { "message": "Cố gắng hủy hoán đổi miễn phí" }, @@ -837,8 +828,11 @@ "bridge": { "message": "Cầu nối" }, - "bridgeDontSend": { - "message": "Cầu nối, không gửi" + "bridgeCalculatingAmount": { + "message": "Đang tính toán..." + }, + "bridgeEnterAmount": { + "message": "Nhập số tiền" }, "bridgeFrom": { "message": "Cầu nối từ" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "Chọn mạng" }, + "bridgeSelectTokenAndAmount": { + "message": "Chọn token và số tiền" + }, "bridgeTo": { "message": "Cầu nối đến" }, @@ -945,9 +942,6 @@ "message": "Nhấp vào đây để kết nối với ví Ledger của bạn qua WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "Bạn luôn có thể thêm token theo cách thủ công." - }, "close": { "message": "Đóng" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "Xác nhận Cụm từ khôi phục bí mật" }, - "confirmTitleApproveTransaction": { - "message": "Yêu cầu cho phép" - }, "confirmTitleDeployContract": { "message": "Triển khai hợp đồng" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "Yêu cầu giao dịch" }, - "confirmationAlertModalDetails": { - "message": "Để bảo vệ tài sản và thông tin đăng nhập của bạn, chúng tôi đề nghị bạn từ chối yêu cầu này." - }, "confirmationAlertModalTitle": { "message": "Yêu cầu này có dấu hiệu đáng ngờ" }, @@ -1160,6 +1148,14 @@ "message": "Đã kết nối với $1", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "Đã kết nối $1 mạng", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Đã kết nối với $1", + "description": "$1 represents network name" + }, "connecting": { "message": "Đang kết nối" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Tùy chọn mua bằng thẻ ghi nợ hoặc thẻ tín dụng" + }, "decimal": { "message": "Số thập phân của token" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "Hạn mức chi tiêu không được vượt quá $1 chữ số thập phân. Xóa bớt chữ số thập phân để tiếp tục." }, + "editSpendingCapSpecialCharError": { + "message": "Chỉ nhập số" + }, "enableAutoDetect": { "message": " Bật tự động phát hiện" }, @@ -1904,10 +1906,42 @@ "message": "Mã: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Liên hệ bộ phận hỗ trợ", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Mô tả những gì đã xảy ra", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Không thể hiển thị thông tin của bạn. Đừng lo, ví và tiền của bạn vẫn an toàn.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Thông báo lỗi", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Mô tả những gì đã xảy ra", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Chia sẻ thông tin chi tiết như cách chúng tôi có thể tái hiện lỗi để giúp chúng tôi khắc phục vấn đề.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Cảm ơn! Chúng tôi sẽ sớm tiến hành xem xét.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask đã gặp lỗi", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Thử lại", + "description": "Button for try again" + }, "errorStack": { "message": "Cụm:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "Loại chức năng" }, + "fundingMethod": { + "message": "Phương thức nạp tiền" + }, "gas": { "message": "Gas" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "Phí gas" }, - "gasIsETH": { - "message": "Gas là $1 " - }, "gasLimit": { "message": "Hạn mức phí gas" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "lừa đảo và nguy cơ bảo mật." }, - "learnToBridge": { - "message": "Tìm hiểu cách sử dụng cầu nối" - }, "leaveMetaMask": { "message": "Rời khỏi MetaMask?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "Vui lòng hoàn tất giao dịch trên Snap." }, - "loadingTokens": { - "message": "Đang tải token..." - }, "localhost": { "message": "Máy chủ cục bộ 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "Thông báo ví hiện không hoạt động." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "Tính năng Hoán đổi trên MetaMask đang được bảo trì. Vui lòng kiểm tra lại sau." }, @@ -2992,10 +3017,6 @@ "message": "$1 đang yêu cầu sự chấp thuận của bạn cho:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "Token gốc của mạng này là $1. Token này được dùng làm phí gas.", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "Chỉnh sửa thông tin mạng" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "Không, cảm ơn" }, - "noTransactions": { - "message": "Bạn không có giao dịch nào" - }, "noWebcamFound": { "message": "Không tìm thấy webcam trên máy tính của bạn. Vui lòng thử lại." }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "Trình duyệt của bạn đã cũ. Nếu không cập nhật trình duyệt, bạn sẽ không thể nhận các bản vá bảo mật và tính năng mới từ MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Ghi đè tiêu đề Nội dung-Bảo mật-Riêng tư" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "Tùy chọn này là giải pháp cho một vấn đề đã biết trên Firefox, nơi tiêu đề Nội dung-Bảo mật-Riêng tư của một dapp có thể ngăn tiện ích mở rộng tải đúng cách. Không nên tắt tùy chọn này trừ khi cần thiết để tương thích với trang web cụ thể." + }, "padlock": { "message": "Ổ khóa" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "Đây là nơi bạn có thể xem các quyền mà bạn đã cấp cho các Snap đã cài đặt hoặc các trang web đã kết nối." }, - "permissionsPageTourDescription": { - "message": "Đây là bảng điều khiển để bạn quản lý các quyền được cấp cho các trang web đã kết nối và các Snap đã cài đặt." - }, - "permissionsPageTourTitle": { - "message": "Các trang web đã kết nối hiện đã được cấp quyền" - }, "permitSimulationDetailInfo": { "message": "Bạn đang cấp cho người chi tiêu quyền chi tiêu số lượng token này từ tài khoản của bạn." }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "Bảo vệ tiền của bạn" }, - "redesignedConfirmationsEnabledToggle": { - "message": "Yêu cầu chữ ký được cải thiện" - }, - "redesignedConfirmationsToggleDescription": { - "message": "Bật tính năng này để xem các yêu cầu chữ ký ở định dạng nâng cao." - }, - "redesignedTransactionsEnabledToggle": { - "message": "Yêu cầu giao dịch được cải thiện" - }, - "redesignedTransactionsToggleDescription": { - "message": "Bật tính năng này để xem các yêu cầu giao dịch ở định dạng nâng cao." - }, "refreshList": { "message": "Làm mới danh sách" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "Đây là trang web yêu cầu chữ ký của bạn." }, + "requestFromInfoSnap": { + "message": "Đây là Snap yêu cầu chữ ký của bạn." + }, "requestFromTransactionDescription": { "message": "Đây là trang web yêu cầu xác nhận của bạn." }, @@ -4417,6 +4426,10 @@ "message": "Yêu cầu cho $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "Yêu cầu cho $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "yêu cầu đang chờ xác nhận" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "Bạn nhận được" }, + "simulationDetailsNoChanges": { + "message": "Không thay đổi" + }, "simulationDetailsOutgoingHeading": { "message": "Bạn gửi" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "Giao dịch này có khả năng thất bại" }, + "simulationDetailsUnavailable": { + "message": "Không khả dụng" + }, "simulationErrorMessageV2": { "message": "Chúng tôi không thể ước tính gas. Có thể đã xảy ra lỗi trong hợp đồng và giao dịch này có thể thất bại." }, @@ -5143,6 +5162,15 @@ "message": "Liên hệ với những người tạo ra $1 để được hỗ trợ thêm.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Bật tính năng này sẽ cung cấp cho bạn tùy chọn thêm Tài khoản Solana vào Tiện ích mở rộng MetaMask bắt nguồn từ Cụm từ khôi phục bí mật hiện có của bạn. Đây là một tính năng Beta thử nghiệm, nên bạn phải tự chịu rủi ro khi sử dụng nó." + }, + "solanaSupportToggleTitle": { + "message": "Bật \"Thêm tài khoản Solana mới (Beta)\"" + }, "somethingDoesntLookRight": { "message": "Có gì đó không ổn? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Token có dấu hiệu giả" }, + "swapTokenVerifiedSources": { + "message": "Được xác nhận bởi $1 nguồn. Xác minh trên $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 cho phép tối đa $2 số thập phân", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 hiện đang hoạt động trên $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "Bạn hiện đang sử dụng" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "Khi bạn chuyển mạng, mọi xác nhận đang chờ xử lý sẽ bị hủy" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "Chọn chủ đề MetaMask yêu thích của bạn." }, - "thingsToKeep": { - "message": "Những điều cần lưu ý:" - }, "thirdPartySoftware": { "message": "Thông báo phần mềm của bên thứ ba", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "Mẹo" }, + "tipsForUsingAWallet": { + "message": "Lời khuyên sử dụng ví" + }, + "tipsForUsingAWalletDescription": { + "message": "Việc thêm token sẽ mở ra nhiều cách sử dụng web3 hơn." + }, "to": { "message": "Đến" }, @@ -5925,18 +5957,6 @@ "message": "Đến: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "Tính năng này cho phép bạn chọn mạng cho từng trang web thay vì một mạng duy nhất được chọn cho tất cả các trang web. Tính năng này sẽ ngăn bạn chuyển đổi mạng theo cách thủ công, điều này có thể ảnh hưởng đến trải nghiệm người dùng của bạn trên một số trang web." - }, - "toggleRequestQueueField": { - "message": "Chọn mạng cho từng trang web" - }, - "toggleRequestQueueOff": { - "message": "Tắt" - }, - "toggleRequestQueueOn": { - "message": "Bật" - }, "token": { "message": "Token" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "Danh sách token" }, + "tokenMarketplace": { + "message": "Thị trường token" + }, "tokenScamSecurityRisk": { "message": "rủi ro về bảo mật và lừa đảo token" }, - "tokenShowUp": { - "message": "Các token có thể không tự động hiển thị trong ví của bạn. " - }, "tokenStandard": { "message": "Tiêu chuẩn token" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "Giải mã hợp đồng thông minh" }, - "use4ByteResolutionDescription": { - "message": "Để cải thiện trải nghiệm người dùng, chúng tôi sẽ tùy chỉnh thẻ hoạt động bằng các thông báo dựa trên các hợp đồng thông minh mà bạn tương tác. MetaMask sử dụng một dịch vụ có tên là 4byte.directory để giải mã dữ liệu và cho bạn thấy một phiên bản hợp đồng thông minh dễ đọc hơn. Điều này giúp giảm nguy cơ chấp thuận các hành động hợp đồng thông minh độc hại, nhưng có thể khiến địa chỉ IP của bạn bị chia sẻ." - }, "useMultiAccountBalanceChecker": { "message": "Xử lý hàng loạt yêu cầu số dư tài khoản" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "Theo hồ sơ của chúng tôi, tên mạng có thể không khớp chính xác với ID chuỗi này." }, - "xOfYPending": { - "message": "$1/$2 đang chờ xử lý", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "Có" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index ca1d0e3f7b48..ec32289d0c53 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -330,9 +330,6 @@ "addingCustomNetwork": { "message": "正在添加网络" }, - "addingTokens": { - "message": "正在添加代币" - }, "additionalNetworks": { "message": "其他网络" }, @@ -416,6 +413,9 @@ "alertMessageAddressMismatchWarning": { "message": "攻击者有时会通过对网站地址进行细微更改来模仿网站。在继续操作之前,请确保您是与目标网站进行交互。" }, + "alertMessageChangeInSimulationResults": { + "message": "此交易的预计更改已更新。继续之前请仔细审查。" + }, "alertMessageGasEstimateFailed": { "message": "我们无法提供准确的费用,估算可能较高。我们建议您输入自定义的燃料限制,但交易仍存在失败风险。" }, @@ -434,18 +434,12 @@ "alertMessageNoGasPrice": { "message": "您手动更新费用后,我们才能继续进行此交易。" }, - "alertMessagePendingTransactions": { - "message": "上一笔交易完成后,此交易才能继续进行。了解如何取消或加快交易。" - }, "alertMessageSignInDomainMismatch": { "message": "提出请求的网站不是您正在登录的网站。这可能试图窃取您的登录凭据。" }, "alertMessageSignInWrongAccount": { "message": "此网站要求您使用错误的账户登录。" }, - "alertMessageSigningOrSubmitting": { - "message": "您的上一笔交易完成后,此交易才能继续进行。" - }, "alertModalAcknowledge": { "message": "我已知晓风险并仍想继续" }, @@ -455,6 +449,9 @@ "alertModalReviewAllAlerts": { "message": "查看所有提醒" }, + "alertReasonChangeInSimulationResults": { + "message": "结果已发生变化" + }, "alertReasonGasEstimateFailed": { "message": "费用不准确" }, @@ -627,12 +624,6 @@ "assetsDescription": { "message": "自动检测钱包中的代币,显示 NFT,并获取批量账户余额更新" }, - "attemptSendingAssets": { - "message": "如果您试图将资产从一个网络直接发送到另一个网络,这可能会导致永久的资产损失。请务必使用跨链桥进行操作。" - }, - "attemptSendingAssetsWithPortfolio": { - "message": "如果您尝试将资产从一个网络发送到另一个网络,这可能会导致资产损失。请务必使用跨链桥在不同网络间安全转移资金,例如 $1" - }, "attemptToCancelSwapForFree": { "message": "尝试免费取消兑换" }, @@ -837,8 +828,11 @@ "bridge": { "message": "跨链桥" }, - "bridgeDontSend": { - "message": "跨链桥,不要发送" + "bridgeCalculatingAmount": { + "message": "正在计算......" + }, + "bridgeEnterAmount": { + "message": "输入金额" }, "bridgeFrom": { "message": "桥接自" @@ -846,6 +840,9 @@ "bridgeSelectNetwork": { "message": "选择网络" }, + "bridgeSelectTokenAndAmount": { + "message": "选择代币和金额" + }, "bridgeTo": { "message": "桥接至" }, @@ -945,9 +942,6 @@ "message": "点击这里以通过 WebHID 连接您的 Ledger", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "请点击这里,以手动添加代币。" - }, "close": { "message": "关闭" }, @@ -1012,9 +1006,6 @@ "confirmRecoveryPhrase": { "message": "确认私钥助记词" }, - "confirmTitleApproveTransaction": { - "message": "许可请求" - }, "confirmTitleDeployContract": { "message": "部署合约" }, @@ -1054,9 +1045,6 @@ "confirmTitleTransaction": { "message": "交易请求" }, - "confirmationAlertModalDetails": { - "message": "为了保护您的资产和登录信息,我们建议您拒绝该请求。" - }, "confirmationAlertModalTitle": { "message": "此请求可疑" }, @@ -1160,6 +1148,14 @@ "message": "与 $1 连接", "description": "$1 represents account name" }, + "connectedWithNetwork": { + "message": "已连接 $1 网络", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "已与 $1 连接", + "description": "$1 represents network name" + }, "connecting": { "message": "连接中……" }, @@ -1493,6 +1489,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "借记卡或信用卡购买选项" + }, "decimal": { "message": "代币小数" }, @@ -1802,6 +1801,9 @@ "editSpendingCapError": { "message": "支出上限不能超过 $1 小数位数。删除小数位数以继续。" }, + "editSpendingCapSpecialCharError": { + "message": "仅输入数字" + }, "enableAutoDetect": { "message": " 启用自动检测" }, @@ -1904,10 +1906,42 @@ "message": "代码:$1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "联系支持团队", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "请描述所发生的问题", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "您的信息不能显示。别担心,您的钱包和资金都很安全。", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "错误消息", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "请描述所发生的问题", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "分享如何重现错误等详细信息将有助于我们解决问题。", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "谢谢!我们将很快查看。", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask 遇到了一个错误", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "重试", + "description": "Button for try again" + }, "errorStack": { "message": "栈:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -2070,6 +2104,9 @@ "functionType": { "message": "功能类型" }, + "fundingMethod": { + "message": "充值方法" + }, "gas": { "message": "燃料" }, @@ -2083,9 +2120,6 @@ "gasFee": { "message": "燃料费" }, - "gasIsETH": { - "message": "燃料是 $1 " - }, "gasLimit": { "message": "燃料限制" }, @@ -2647,9 +2681,6 @@ "learnScamRisk": { "message": "欺诈和安全风险信息。" }, - "learnToBridge": { - "message": "了解跨链桥" - }, "leaveMetaMask": { "message": "要离开 MetaMask?" }, @@ -2741,9 +2772,6 @@ "loadingScreenSnapMessage": { "message": "请在Snap上完成交易。" }, - "loadingTokens": { - "message": "加载代币中……" - }, "localhost": { "message": "Localhost 8545" }, @@ -2845,9 +2873,6 @@ "metamaskNotificationsAreOff": { "message": "钱包通知目前未开启。" }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio。" - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps 正在进行维护。请稍后再查看。" }, @@ -2992,10 +3017,6 @@ "message": "$1 请求您的批准,以便:", "description": "$1 represents dapp name" }, - "nativeToken": { - "message": "此网络上的原生代币为 $1。它是用于燃料费的代币。 ", - "description": "$1 represents the name of the native token on the current network" - }, "nativeTokenScamWarningConversion": { "message": "编辑网络详情" }, @@ -3280,9 +3301,6 @@ "noThanks": { "message": "不,谢谢" }, - "noTransactions": { - "message": "您没有任何交易" - }, "noWebcamFound": { "message": "未找到您电脑的网络摄像头。请重试。" }, @@ -3724,6 +3742,12 @@ "outdatedBrowserNotification": { "message": "您的浏览器已过期。如果不更新浏览器,您将无法获取MetaMask的安全补丁和新功能。" }, + "overrideContentSecurityPolicyHeader": { + "message": "覆盖 Content-Security-Policy 标头" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "此选项是 Firefox 中已知问题的解决方法,其中 dapp(去中心化应用)的 Content-Security-Policy 标头可能会阻止扩展程序正确加载。除非特定网页兼容性需要,否则不建议禁用此选项。" + }, "padlock": { "message": "挂锁" }, @@ -4032,12 +4056,6 @@ "permissionsPageEmptySubContent": { "message": "您可以在此处查看您授予已安装 Snap 或已连接站点的许可。" }, - "permissionsPageTourDescription": { - "message": "这是您的控制面板,用于管理授予已连接站点和已安装 Snap 的许可。" - }, - "permissionsPageTourTitle": { - "message": "已连接的站点现已获得许可" - }, "permitSimulationDetailInfo": { "message": "您将授予该消费者许可从您的账户中支出这些代币。" }, @@ -4289,18 +4307,6 @@ "recoveryPhraseReminderTitle": { "message": "保护您的资金" }, - "redesignedConfirmationsEnabledToggle": { - "message": "批准的签名请求" - }, - "redesignedConfirmationsToggleDescription": { - "message": "开启此选项以查看增强格式的签名请求。" - }, - "redesignedTransactionsEnabledToggle": { - "message": "经过改进的交易请求" - }, - "redesignedTransactionsToggleDescription": { - "message": "开启此选项以查看增强格式的交易请求。" - }, "refreshList": { "message": "刷新列表" }, @@ -4395,6 +4401,9 @@ "requestFromInfo": { "message": "这是要求您签名的站点。" }, + "requestFromInfoSnap": { + "message": "这是要求您签名的 Snap。" + }, "requestFromTransactionDescription": { "message": "这是要求您确认的网站。" }, @@ -4417,6 +4426,10 @@ "message": "请求 $1", "description": "Name of Account" }, + "requestingForNetwork": { + "message": "请求 $1", + "description": "Name of Network" + }, "requestsAwaitingAcknowledgement": { "message": "待确认的请求" }, @@ -4895,6 +4908,9 @@ "simulationDetailsIncomingHeading": { "message": "您收到" }, + "simulationDetailsNoChanges": { + "message": "无变化" + }, "simulationDetailsOutgoingHeading": { "message": "您发送" }, @@ -4917,6 +4933,9 @@ "simulationDetailsTransactionReverted": { "message": "这笔交易可能会失败" }, + "simulationDetailsUnavailable": { + "message": "不可用" + }, "simulationErrorMessageV2": { "message": "我们无法估算燃料。合约中可能存在错误,这笔交易可能会失败。" }, @@ -5143,6 +5162,15 @@ "message": "联系 $1 的创建者以获得进一步支持。", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "启用此功能后,您可以选择将 Solana 账户添加到衍生自现有私钥助记词的 MetaMask Extension 中。这是一个实验性的测试版功能,因此您应自行承担使用风险。" + }, + "solanaSupportToggleTitle": { + "message": "启用“添加新的 Solana 账户(测试版)”" + }, "somethingDoesntLookRight": { "message": "有什么不对劲吗?$1", "description": "A false positive message for users to contact support. $1 is a link to the support page." @@ -5801,6 +5829,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "可能伪造的代币" }, + "swapTokenVerifiedSources": { + "message": "已经过 $1 来源确认。在 $2 上验证。", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 允许最多 $2 个小数位", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5859,9 +5891,6 @@ "message": "$1 现已在 $2 上激活", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "您现在使用的是" - }, "switchingNetworksCancelsPendingConfirmations": { "message": "切换网络将取消所有待处理的确认" }, @@ -5898,9 +5927,6 @@ "themeDescription": { "message": "选择您喜欢的MetaMask主题。" }, - "thingsToKeep": { - "message": "注意事项:" - }, "thirdPartySoftware": { "message": "第三方软件通告", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5918,6 +5944,12 @@ "tips": { "message": "提示" }, + "tipsForUsingAWallet": { + "message": "钱包使用提示" + }, + "tipsForUsingAWalletDescription": { + "message": "添加代币可解锁使用 Web3 的更多方法。" + }, "to": { "message": "至" }, @@ -5925,18 +5957,6 @@ "message": "至:$1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleRequestQueueDescription": { - "message": "这使您可以为每个网站选择网络,而不是为所有网站选择同一个网络。此功能将阻止您手动切换网络,这可能会破坏您在某些网站上的用户体验。" - }, - "toggleRequestQueueField": { - "message": "为每个网站选择网络" - }, - "toggleRequestQueueOff": { - "message": "关" - }, - "toggleRequestQueueOn": { - "message": "开" - }, "token": { "message": "代币" }, @@ -5970,12 +5990,12 @@ "tokenList": { "message": "代币列表:" }, + "tokenMarketplace": { + "message": "代币市场" + }, "tokenScamSecurityRisk": { "message": "代币欺诈和安全风险。" }, - "tokenShowUp": { - "message": "您的代币可能不会自动显示在您的钱包中。" - }, "tokenStandard": { "message": "代币标准" }, @@ -6288,9 +6308,6 @@ "use4ByteResolution": { "message": "对智能合约进行解码" }, - "use4ByteResolutionDescription": { - "message": "为了改善用户体验,我们根据与您交互的智能合约消息,自定义活动选项卡。MetaMask 使用名为 4byte.directory 的服务来对数据进行解码,并向您显示更方便阅读的智能合约版本。这有助于减少您批准恶意智能合约操作的机会,但可能导致您的 IP 地址被共享。" - }, "useMultiAccountBalanceChecker": { "message": "账户余额分批请求" }, @@ -6484,10 +6501,6 @@ "wrongNetworkName": { "message": "根据我们的记录,该网络名称可能与此链 ID 不匹配。" }, - "xOfYPending": { - "message": "$1 / $2 待处理", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "yes": { "message": "是" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 29953f67c941..7b4a425a03ad 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -699,9 +699,6 @@ "loading": { "message": "載入..." }, - "loadingTokens": { - "message": "載入代幣..." - }, "lock": { "message": "鎖定" }, @@ -815,9 +812,6 @@ "noConversionRateAvailable": { "message": "尚未有匯率比較值" }, - "noTransactions": { - "message": "尚未有交易" - }, "noWebcamFound": { "message": "無法搜尋到攝影鏡頭裝置。請再試一次" }, @@ -1375,10 +1369,6 @@ "whatsThis": { "message": "這是什麼?" }, - "xOfYPending": { - "message": "$1 之 $2 等待中", - "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" - }, "youNeedToAllowCameraAccess": { "message": "需要准許存取攝影鏡頭才能啟用此功能" }, diff --git a/app/images/arbitrum.svg b/app/images/arbitrum.svg index 9c19a48f2d72..f6fdd140c38e 100644 --- a/app/images/arbitrum.svg +++ b/app/images/arbitrum.svg @@ -1,40 +1,15 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/images/avax-token.svg b/app/images/avax-token.svg index 55473a0f2400..ea08a9c1a0ee 100644 --- a/app/images/avax-token.svg +++ b/app/images/avax-token.svg @@ -1,4 +1,5 @@ - - - + + + + diff --git a/app/images/b3.svg b/app/images/b3.svg new file mode 100644 index 000000000000..eb2c4dcfd9d2 --- /dev/null +++ b/app/images/b3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/base.svg b/app/images/base.svg index 22d89358f633..114aeb900f1b 100644 --- a/app/images/base.svg +++ b/app/images/base.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/app/images/bitcoin-logo.svg b/app/images/bitcoin-logo.svg index 4306787921c7..7b0c116bbf12 100644 --- a/app/images/bitcoin-logo.svg +++ b/app/images/bitcoin-logo.svg @@ -1,14 +1,5 @@ - - - - - - - - - - - - - + + + + diff --git a/app/images/bnb.svg b/app/images/bnb.svg index b1f1841b84e0..e2ba1e86cea1 100644 --- a/app/images/bnb.svg +++ b/app/images/bnb.svg @@ -1,3 +1,7 @@ - - + + + + + + diff --git a/app/images/eth_logo.svg b/app/images/eth_logo.svg index 5bffa07d0ff5..0b1e0ed3ad77 100644 --- a/app/images/eth_logo.svg +++ b/app/images/eth_logo.svg @@ -1,9 +1,17 @@ - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/app/images/hollow-circle.svg b/app/images/hollow-circle.svg new file mode 100644 index 000000000000..3490e8439b5e --- /dev/null +++ b/app/images/hollow-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/images/info-fox.svg b/app/images/info-fox.svg deleted file mode 100644 index 57660c1fe6bc..000000000000 --- a/app/images/info-fox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/images/ink-sepolia.svg b/app/images/ink-sepolia.svg new file mode 100644 index 000000000000..7e6dfe3ca85d --- /dev/null +++ b/app/images/ink-sepolia.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/ink.svg b/app/images/ink.svg new file mode 100644 index 000000000000..a42ada4be3dc --- /dev/null +++ b/app/images/ink.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/images/kaia.svg b/app/images/kaia.svg new file mode 100644 index 000000000000..88a10cd86e68 --- /dev/null +++ b/app/images/kaia.svg @@ -0,0 +1,9 @@ + + image + + + + + + diff --git a/app/images/klaytn.svg b/app/images/klaytn.svg deleted file mode 100644 index 4eb1ff5f107b..000000000000 --- a/app/images/klaytn.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/images/linea-logo-mainnet.svg b/app/images/linea-logo-mainnet.svg index bb56377bd349..c92953d73938 100644 --- a/app/images/linea-logo-mainnet.svg +++ b/app/images/linea-logo-mainnet.svg @@ -1,11 +1,11 @@ - - - - + + + + - + diff --git a/app/images/lisk.svg b/app/images/lisk.svg new file mode 100644 index 000000000000..3f90844a8b5f --- /dev/null +++ b/app/images/lisk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/images/lisk_sepolia.svg b/app/images/lisk_sepolia.svg new file mode 100644 index 000000000000..e38de3c9c07a --- /dev/null +++ b/app/images/lisk_sepolia.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/images/no-nfts.svg b/app/images/no-nfts.svg index 220eb4790d75..6b3197e9a3e1 100644 --- a/app/images/no-nfts.svg +++ b/app/images/no-nfts.svg @@ -1,10 +1,51 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/images/optimism.svg b/app/images/optimism.svg index 2e5ce3103d08..6ee446335bb2 100644 --- a/app/images/optimism.svg +++ b/app/images/optimism.svg @@ -1,5 +1,13 @@ - - - - + + + + + + + + + + + + diff --git a/app/images/pol-token.svg b/app/images/pol-token.svg index 10b192a9fa15..5c750420f80c 100644 --- a/app/images/pol-token.svg +++ b/app/images/pol-token.svg @@ -1,21 +1,4 @@ - - - - - - - - - - - - + + + diff --git a/app/images/slide-bridge-icon.svg b/app/images/slide-bridge-icon.svg new file mode 100644 index 000000000000..de2be9745504 --- /dev/null +++ b/app/images/slide-bridge-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/app/images/slide-card-icon.svg b/app/images/slide-card-icon.svg new file mode 100644 index 000000000000..32dd386ea6ce --- /dev/null +++ b/app/images/slide-card-icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/app/images/slide-fund-icon.svg b/app/images/slide-fund-icon.svg new file mode 100644 index 000000000000..80f8e3b42e79 --- /dev/null +++ b/app/images/slide-fund-icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/app/images/slide-sell-icon.svg b/app/images/slide-sell-icon.svg new file mode 100644 index 000000000000..daed4190a05a --- /dev/null +++ b/app/images/slide-sell-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/images/soneium.svg b/app/images/soneium.svg new file mode 100644 index 000000000000..f045afaef99a --- /dev/null +++ b/app/images/soneium.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/images/zk-sync.svg b/app/images/zk-sync.svg index 55021930f1da..ea60764b484c 100644 --- a/app/images/zk-sync.svg +++ b/app/images/zk-sync.svg @@ -1,14 +1,5 @@ - - - - - - - - - + + + + diff --git a/app/manifest/v2/_base.json b/app/manifest/v2/_base.json index f29b7458a9e5..2f41a7e987fa 100644 --- a/app/manifest/v2/_base.json +++ b/app/manifest/v2/_base.json @@ -66,6 +66,8 @@ "clipboardWrite", "http://*/*", "https://*/*", + "ws://*/*", + "wss://*/*", "activeTab", "webRequest", "webRequestBlocking", diff --git a/app/manifest/v2/brave.json b/app/manifest/v2/brave.json index d4d74959811c..3a512edd0ed4 100644 --- a/app/manifest/v2/brave.json +++ b/app/manifest/v2/brave.json @@ -1,5 +1,5 @@ { - "content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'", + "content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; font-src 'self';", "externally_connectable": { "matches": ["https://metamask.io/*"], "ids": ["*"] diff --git a/app/manifest/v2/chrome.json b/app/manifest/v2/chrome.json index e3d68547824a..6018b5b22640 100644 --- a/app/manifest/v2/chrome.json +++ b/app/manifest/v2/chrome.json @@ -1,5 +1,5 @@ { - "content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'", + "content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; font-src 'self';", "externally_connectable": { "matches": ["https://metamask.io/*"], "ids": ["*"] diff --git a/app/manifest/v3/_base.json b/app/manifest/v3/_base.json index 4d6ee38437d3..89758033f33a 100644 --- a/app/manifest/v3/_base.json +++ b/app/manifest/v3/_base.json @@ -50,7 +50,9 @@ "http://localhost:8545/", "file://*/*", "http://*/*", - "https://*/*" + "https://*/*", + "ws://*/*", + "wss://*/*" ], "icons": { "16": "images/icon-16.png", diff --git a/app/manifest/v3/chrome.json b/app/manifest/v3/chrome.json index 2308cc912b55..48b622891e0f 100644 --- a/app/manifest/v3/chrome.json +++ b/app/manifest/v3/chrome.json @@ -1,7 +1,7 @@ { "content_security_policy": { - "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';", - "sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none'; default-src 'none'; connect-src *;" + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none'; font-src 'self';", + "sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none'; default-src 'none'; connect-src *; font-src 'self';" }, "externally_connectable": { "matches": ["https://metamask.io/*"], diff --git a/app/scripts/background.js b/app/scripts/background.js index 90a52b6c0d19..700db8127a97 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -15,11 +15,7 @@ import log from 'loglevel'; import browser from 'webextension-polyfill'; import { storeAsStream } from '@metamask/obs-store'; import { isObject } from '@metamask/utils'; -import { ApprovalType } from '@metamask/controller-utils'; import PortStream from 'extension-port-stream'; - -import { providerErrors } from '@metamask/rpc-errors'; -import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; import { NotificationServicesController } from '@metamask/notification-services-controller'; import { @@ -29,9 +25,6 @@ import { EXTENSION_MESSAGES, PLATFORM_FIREFOX, MESSAGE_TYPE, - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES, - ///: END:ONLY_INCLUDE_IF } from '../../shared/constants/app'; import { REJECT_NOTIFICATION_CLOSE, @@ -53,9 +46,7 @@ import { FakeLedgerBridge, FakeTrezorBridge, } from '../../test/stub/keyring-bridge'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { getCurrentChainId } from '../../ui/selectors'; +import { getCurrentChainId } from '../../shared/modules/selectors/networks'; import { addNonceToCsp } from '../../shared/modules/add-nonce-to-csp'; import { checkURLForProviderInjection } from '../../shared/modules/provider-injection'; import migrations from './migrations'; @@ -286,12 +277,14 @@ function maybeDetectPhishing(theController) { // Determine the block reason based on the type let blockReason; + let blockedUrl = hostname; if (phishingTestResponse?.result && blockedRequestResponse.result) { blockReason = `${phishingTestResponse.type} and ${blockedRequestResponse.type}`; } else if (phishingTestResponse?.result) { blockReason = phishingTestResponse.type; } else { blockReason = blockedRequestResponse.type; + blockedUrl = details.initiator; } theController.metaMetricsController.trackEvent({ @@ -299,11 +292,12 @@ function maybeDetectPhishing(theController) { event: MetaMetricsEventName.PhishingPageDisplayed, category: MetaMetricsEventCategory.Phishing, properties: { - url: hostname, + url: blockedUrl, referrer: { - url: hostname, + url: blockedUrl, }, reason: blockReason, + requestDomain: blockedRequestResponse.result ? hostname : undefined, }, }); const querystring = new URLSearchParams({ hostname, href }); @@ -314,22 +308,21 @@ function maybeDetectPhishing(theController) { // blocking is better than tab redirection, as blocking will prevent // the browser from loading the page at all if (isManifestV2) { - if (details.type === 'sub_frame') { - // redirect the entire tab to the - // phishing warning page instead. - redirectTab(details.tabId, redirectHref); - // don't let the sub_frame load at all - return { cancel: true }; + // We can redirect `main_frame` requests directly to the warning page. + // For non-`main_frame` requests (e.g. `sub_frame` or WebSocket), we cancel them + // and redirect the whole tab asynchronously so that the user sees the warning. + if (details.type === 'main_frame') { + return { redirectUrl: redirectHref }; } - // redirect the whole tab - return { redirectUrl: redirectHref }; + redirectTab(details.tabId, redirectHref); + return { cancel: true }; } // redirect the whole tab (even if it's a sub_frame request) redirectTab(details.tabId, redirectHref); return {}; }, { - urls: ['http://*/*', 'https://*/*'], + urls: ['http://*/*', 'https://*/*', 'ws://*/*', 'wss://*/*'], }, isManifestV2 ? ['blocking'] : [], ); @@ -684,13 +677,9 @@ function emitDappViewedMetricEvent(origin) { return; } - const permissions = controller.controllerMessenger.call( - 'PermissionController:getPermissions', - origin, - ); const numberOfConnectedAccounts = - permissions?.eth_accounts?.caveats[0]?.value.length; - if (!numberOfConnectedAccounts) { + controller.getPermittedAccounts(origin).length; + if (numberOfConnectedAccounts === 0) { return; } @@ -747,6 +736,49 @@ function trackDappView(remotePort) { } } +/** + * Emit App Opened event + */ +function emitAppOpenedMetricEvent() { + const { metaMetricsId, participateInMetaMetrics } = + controller.metaMetricsController.state; + + // Skip if user hasn't opted into metrics + if (metaMetricsId === null && !participateInMetaMetrics) { + return; + } + + controller.metaMetricsController.trackEvent({ + event: MetaMetricsEventName.AppOpened, + category: MetaMetricsEventCategory.App, + }); +} + +/** + * This function checks if the app is being opened + * and emits an event only if no other UI instances are currently open. + * + * @param {string} environment - The environment type where the app is opening + */ +function trackAppOpened(environment) { + // List of valid environment types to track + const environmentTypeList = [ + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, + ]; + + // Check if any UI instances are currently open + const isFullscreenOpen = Object.values(openMetamaskTabsIDs).some(Boolean); + const isAlreadyOpen = + isFullscreenOpen || notificationIsOpen || openPopupCount > 0; + + // Only emit event if no UI is open and environment is valid + if (!isAlreadyOpen && environmentTypeList.includes(environment)) { + emitAppOpenedMetricEvent(); + } +} + /** * Initializes the MetaMask Controller with any initial state and default language. * Configures platform-specific error reporting strategy. @@ -771,7 +803,6 @@ export function setupController( // // MetaMask Controller // - controller = new MetamaskController({ infuraProjectId: process.env.INFURA_PROJECT_ID, // User confirmation callbacks: @@ -890,6 +921,9 @@ export function setupController( // communication with popup controller.isClientOpen = true; controller.setupTrustedCommunication(portStream, remotePort.sender); + trackAppOpened(processName); + + initializeRemoteFeatureFlags(); if (processName === ENVIRONMENT_TYPE_POPUP) { openPopupCount += 1; @@ -1023,8 +1057,8 @@ export function setupController( METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, updateBadge, ); - controller.appStateController.on( - METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.APP_STATE_UNLOCK_CHANGE, updateBadge, ); @@ -1048,11 +1082,6 @@ export function setupController( updateBadge, ); - controller.controllerMessenger.subscribe( - METAMASK_CONTROLLER_EVENTS.NOTIFICATIONS_STATE_CHANGE, - updateBadge, - ); - /** * Formats a count for display as a badge label. * @@ -1098,16 +1127,30 @@ export function setupController( } } + /** + * Initializes remote feature flags by making a request to fetch them from the clientConfigApi. + * This function is called when MM is during internal process. + * If the request fails, the error will be logged but won't interrupt extension initialization. + * + * @returns {Promise} A promise that resolves when the remote feature flags have been updated. + */ + async function initializeRemoteFeatureFlags() { + try { + // initialize the request to fetch remote feature flags + await controller.remoteFeatureFlagController.updateRemoteFeatureFlags(); + } catch (error) { + log.error('Error initializing remote feature flags:', error); + } + } + function getPendingApprovalCount() { try { let pendingApprovalCount = controller.appStateController.waitingForUnlock.length + controller.approvalController.getTotalApprovalCount(); - if (controller.preferencesController.getUseRequestQueue()) { - pendingApprovalCount += - controller.queuedRequestController.state.queuedRequestCount; - } + pendingApprovalCount += + controller.queuedRequestController.state.queuedRequestCount; return pendingApprovalCount; } catch (error) { console.error('Failed to get pending approval count:', error); @@ -1121,8 +1164,14 @@ export function setupController( controller.notificationServicesController.state; const snapNotificationCount = Object.values( - controller.notificationController.state.notifications, - ).filter((notification) => notification.readDate === null).length; + controller.notificationServicesController.state + .metamaskNotificationsList, + ).filter( + (notification) => + notification.type === + NotificationServicesController.Constants.TRIGGER_TYPES.SNAP && + notification.readDate === null, + ).length; const featureAnnouncementCount = isFeatureAnnouncementsEnabled ? controller.notificationServicesController.state.metamaskNotificationsList.filter( @@ -1140,7 +1189,9 @@ export function setupController( !notification.isRead && notification.type !== NotificationServicesController.Constants.TRIGGER_TYPES - .FEATURES_ANNOUNCEMENT, + .FEATURES_ANNOUNCEMENT && + notification.type !== + NotificationServicesController.Constants.TRIGGER_TYPES.SNAP, ).length : 0; @@ -1180,34 +1231,7 @@ export function setupController( REJECT_NOTIFICATION_CLOSE, ); - // Finally, resolve snap dialog approvals on Flask and reject all the others managed by the ApprovalController. - Object.values(controller.approvalController.state.pendingApprovals).forEach( - ({ id, type }) => { - switch (type) { - case ApprovalType.SnapDialogAlert: - case ApprovalType.SnapDialogPrompt: - case DIALOG_APPROVAL_TYPES.default: - controller.approvalController.accept(id, null); - break; - case ApprovalType.SnapDialogConfirmation: - controller.approvalController.accept(id, false); - break; - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation: - case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval: - case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect: - controller.approvalController.accept(id, false); - break; - ///: END:ONLY_INCLUDE_IF - default: - controller.approvalController.reject( - id, - providerErrors.userRejectedRequest(), - ); - break; - } - }, - ); + controller.rejectAllPendingApprovals(); } // Updates the snaps registry and check for newly blocked snaps to block if the user has at least one snap installed that isn't preinstalled. diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 289bc0a0d29c..f487cc3bd89f 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -42,6 +42,11 @@ export const SENTRY_BACKGROUND_STATE = { }, AuthenticationController: { isSignedIn: false, + sessionData: { + profile: true, + accessToken: false, + expiresIn: true, + }, }, NetworkOrderController: { orderedNetworkList: [], @@ -90,7 +95,6 @@ export const SENTRY_BACKGROUND_STATE = { termsOfUseLastAgreed: true, timeoutMinutes: true, trezorModel: true, - usedNetworks: true, }, MultichainBalancesController: { balances: false, @@ -98,15 +102,14 @@ export const SENTRY_BACKGROUND_STATE = { BridgeController: { bridgeState: { bridgeFeatureFlags: { - extensionConfig: false, - extensionSupport: false, - destNetworkAllowlist: [], - srcNetworkAllowlist: [], + extensionConfig: { + support: false, + chains: {}, + }, }, destTokens: {}, destTopAssets: [], - srcTokens: {}, - srcTopAssets: [], + destTokensLoadingStatus: true, quoteRequest: { walletAddress: false, srcTokenAddress: true, @@ -117,8 +120,10 @@ export const SENTRY_BACKGROUND_STATE = { srcTokenAmount: true, }, quotes: [], + quotesInitialLoadTime: true, quotesLastFetched: true, quotesLoadingStatus: true, + quoteFetchError: true, quotesRefreshCount: true, }, }, @@ -203,9 +208,6 @@ export const SENTRY_BACKGROUND_STATE = { allNfts: false, ignoredNfts: false, }, - NotificationController: { - notifications: false, - }, OnboardingController: { completedOnboarding: true, firstTimeFlowType: true, @@ -243,8 +245,6 @@ export const SENTRY_BACKGROUND_STATE = { preferences: { autoLockTimeLimit: true, hideZeroBalanceTokens: true, - redesignedConfirmationsEnabled: true, - redesignedTransactionsEnabled: false, isRedesignedConfirmationsDeveloperEnabled: false, showExtensionInFullSizeView: true, showFiatInTestnets: true, @@ -270,10 +270,13 @@ export const SENTRY_BACKGROUND_STATE = { useNonceField: true, usePhishDetect: true, useTokenDetection: true, - useRequestQueue: true, useTransactionSimulations: true, enableMV3TimestampSave: true, }, + RemoteFeatureFlagController: { + remoteFeatureFlags: true, + cacheTimestamp: false, + }, NotificationServicesPushController: { fcmToken: false, }, @@ -418,18 +421,20 @@ const flattenedBackgroundStateMask = Object.values( export const SENTRY_UI_STATE = { gas: true, history: true, - metamask: { - ...flattenedBackgroundStateMask, - // This property comes from the background but isn't in controller state - isInitialized: true, - // These properties are in the `metamask` slice but not in the background state + appState: { customNonceValue: true, isAccountMenuOpen: true, isNetworkMenuOpen: true, nextNonce: true, pendingTokens: false, welcomeScreenSeen: true, + slides: false, confirmationExchangeRates: true, + }, + metamask: { + ...flattenedBackgroundStateMask, + // This property comes from the background but isn't in controller state + isInitialized: true, useSafeChainsListValidation: true, watchEthereumAccountEnabled: false, bitcoinSupportEnabled: false, diff --git a/app/scripts/controllers/account-tracker-controller.test.ts b/app/scripts/controllers/account-tracker-controller.test.ts index 7456244fc5a4..c842db35c287 100644 --- a/app/scripts/controllers/account-tracker-controller.test.ts +++ b/app/scripts/controllers/account-tracker-controller.test.ts @@ -1,6 +1,6 @@ import EventEmitter from 'events'; import { ControllerMessenger } from '@metamask/base-controller'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { BlockTracker, Provider } from '@metamask/network-controller'; import { flushPromises } from '../../../test/lib/timer-helpers'; @@ -368,6 +368,23 @@ describe('AccountTrackerController', () => { }); }); + it('should gracefully handle unknown polling tokens', async () => { + await withController(({ controller, blockTrackerFromHookStub }) => { + jest.spyOn(controller, 'updateAccounts').mockResolvedValue(); + + const pollingToken = + controller.startPollingByNetworkClientId('mainnet'); + + controller.stopPollingByPollingToken('unknown-token'); + controller.stopPollingByPollingToken(pollingToken); + + expect(blockTrackerFromHookStub.removeListener).toHaveBeenCalledWith( + 'latest', + expect.any(Function), + ); + }); + }); + it('should not unsubscribe from the block tracker if called with one of multiple active polling tokens for a given networkClient', async () => { await withController(({ controller, blockTrackerFromHookStub }) => { jest.spyOn(controller, 'updateAccounts').mockResolvedValue(); @@ -391,14 +408,6 @@ describe('AccountTrackerController', () => { }).toThrow('pollingToken required'); }); }); - - it('should error if no matching pollingToken is found', async () => { - await withController(({ controller }) => { - expect(() => { - controller.stopPollingByPollingToken('potato'); - }).toThrow('pollingToken not found'); - }); - }); }); describe('stopAll', () => { diff --git a/app/scripts/controllers/account-tracker-controller.ts b/app/scripts/controllers/account-tracker-controller.ts index 5f509a1901bf..38b46183111f 100644 --- a/app/scripts/controllers/account-tracker-controller.ts +++ b/app/scripts/controllers/account-tracker-controller.ts @@ -7,11 +7,9 @@ * on each new block. */ -import EthQuery from '@metamask/eth-query'; import { v4 as random } from 'uuid'; import log from 'loglevel'; -import pify from 'pify'; import { Web3Provider } from '@ethersproject/providers'; import { Contract } from '@ethersproject/contracts'; import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'; @@ -24,7 +22,7 @@ import { NetworkControllerGetStateAction, Provider, } from '@metamask/network-controller'; -import { hasProperty, Hex } from '@metamask/utils'; +import { hasProperty, type Hex, type JsonRpcParams } from '@metamask/utils'; import { BaseController, ControllerGetStateAction, @@ -36,7 +34,7 @@ import { AccountsControllerSelectedEvmAccountChangeEvent, } from '@metamask/accounts-controller'; import { KeyringControllerAccountRemovedEvent } from '@metamask/keyring-controller'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { LOCALHOST_RPC_URL } from '../../../shared/constants/network'; import { SINGLE_CALL_BALANCES_ADDRESSES } from '../constants/contracts'; @@ -405,10 +403,8 @@ export default class AccountTrackerController extends BaseController< if (!pollingToken) { throw new Error('pollingToken required'); } - let found = false; this.#pollingTokenSets.forEach((tokenSet, key) => { if (tokenSet.has(pollingToken)) { - found = true; tokenSet.delete(pollingToken); if (tokenSet.size === 0) { this.#pollingTokenSets.delete(key); @@ -416,9 +412,6 @@ export default class AccountTrackerController extends BaseController< } } }); - if (!found) { - throw new Error('pollingToken not found'); - } } /** @@ -481,7 +474,7 @@ export default class AccountTrackerController extends BaseController< * AccountTrackerController. * * Once this AccountTrackerController accounts are up to date with those referenced by the passed addresses, each - * of these accounts are given an updated balance via EthQuery. + * of these accounts are given an updated balance via Provider. * * @param addresses - The array of hex addresses for accounts with which this AccountTrackerController accounts should be * in sync @@ -588,7 +581,7 @@ export default class AccountTrackerController extends BaseController< /** * Given a block, updates this AccountTrackerController currentBlockGasLimit and currentBlockGasLimitByChainId and then updates - * each local account's balance via EthQuery + * each local account's balance via Provider * * @private * @param blockNumber - the block number to update to. @@ -600,7 +593,7 @@ export default class AccountTrackerController extends BaseController< /** * Given a block, updates this AccountTrackerController currentBlockGasLimitByChainId, and then updates each local account's balance - * via EthQuery + * via Provider * * @private * @param networkClientId - optional network client ID to use instead of the globally selected network. @@ -616,10 +609,13 @@ export default class AccountTrackerController extends BaseController< this.#currentBlockNumberByChainId[chainId] = blockNumber; // block gasLimit polling shouldn't be in account-tracker shouldn't be here... - const currentBlock = await pify(new EthQuery(provider)).getBlockByNumber( - blockNumber, - false, - ); + const currentBlock = await provider.request< + JsonRpcParams, + { gasLimit: string } + >({ + method: 'eth_getBlockByNumber', + params: [blockNumber, false], + }); if (!currentBlock) { return; } @@ -729,7 +725,10 @@ export default class AccountTrackerController extends BaseController< // query balance try { - balance = await pify(new EthQuery(provider)).getBalance(address); + balance = await provider.request({ + method: 'eth_getBalance', + params: [address, 'latest'], + }); } catch (error) { if ( error && diff --git a/app/scripts/controllers/alert-controller.test.ts b/app/scripts/controllers/alert-controller.test.ts index de314c31f050..190879dc4334 100644 --- a/app/scripts/controllers/alert-controller.test.ts +++ b/app/scripts/controllers/alert-controller.test.ts @@ -163,6 +163,7 @@ describe('AlertController', () => { address: '0x1234567', options: {}, methods: [], + scopes: ['eip155'], type: 'eip155:eoa', metadata: { name: '', diff --git a/app/scripts/controllers/app-metadata.test.ts b/app/scripts/controllers/app-metadata.test.ts index cd9f87e238a0..faff2be5ab10 100644 --- a/app/scripts/controllers/app-metadata.test.ts +++ b/app/scripts/controllers/app-metadata.test.ts @@ -1,11 +1,8 @@ -import AppMetadataController from './app-metadata'; - -const EXPECTED_DEFAULT_STATE = { - currentAppVersion: '', - previousAppVersion: '', - previousMigrationVersion: 0, - currentMigrationVersion: 0, -}; +import { ControllerMessenger } from '@metamask/base-controller'; +import AppMetadataController, { + getDefaultAppMetadataControllerState, + type AppMetadataControllerOptions, +} from './app-metadata'; describe('AppMetadataController', () => { describe('constructor', () => { @@ -16,86 +13,142 @@ describe('AppMetadataController', () => { previousMigrationVersion: 1, currentMigrationVersion: 1, }; - const appMetadataController = new AppMetadataController({ - state: initState, - currentMigrationVersion: 1, - currentAppVersion: '1', - }); - expect(appMetadataController.store.getState()).toStrictEqual(initState); + withController( + { + state: initState, + currentMigrationVersion: 1, + currentAppVersion: '1', + }, + ({ controller }) => { + expect(controller.state).toStrictEqual(initState); + }, + ); }); - it('sets default state and does not modify it', async () => { - const appMetadataController = new AppMetadataController({ - state: {}, + it('sets default state and does not modify it', () => { + withController({ state: {} }, ({ controller }) => { + expect(controller.state).toStrictEqual( + getDefaultAppMetadataControllerState(), + ); }); - expect(appMetadataController.store.getState()).toStrictEqual( - EXPECTED_DEFAULT_STATE, - ); }); - it('sets default state and does not modify it if options version parameters match respective default values', async () => { - const appMetadataController = new AppMetadataController({ - state: {}, - currentMigrationVersion: 0, - currentAppVersion: '', - }); - expect(appMetadataController.store.getState()).toStrictEqual( - EXPECTED_DEFAULT_STATE, + it('sets default state and does not modify it if options version parameters match respective default values', () => { + withController( + { + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '', + }, + ({ controller }) => { + expect(controller.state).toStrictEqual( + getDefaultAppMetadataControllerState(), + ); + }, ); }); - it('updates the currentAppVersion state property if options.currentAppVersion does not match the default value', async () => { - const appMetadataController = new AppMetadataController({ - state: {}, - currentMigrationVersion: 0, - currentAppVersion: '1', - }); - expect(appMetadataController.store.getState()).toStrictEqual({ - ...EXPECTED_DEFAULT_STATE, - currentAppVersion: '1', - }); + it('updates the currentAppVersion state property if options.currentAppVersion does not match the default value', () => { + withController( + { + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '1', + }, + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentAppVersion: '1', + }); + }, + ); }); - it('updates the currentAppVersion and previousAppVersion state properties if options.currentAppVersion, currentAppVersion and previousAppVersion are all different', async () => { - const appMetadataController = new AppMetadataController({ - state: { - currentAppVersion: '2', - previousAppVersion: '1', + it('updates the currentAppVersion and previousAppVersion state properties if options.currentAppVersion, currentAppVersion and previousAppVersion are all different', () => { + withController( + { + state: { + currentAppVersion: '2', + previousAppVersion: '1', + }, + currentAppVersion: '3', + currentMigrationVersion: 0, }, - currentAppVersion: '3', - currentMigrationVersion: 0, - }); - expect(appMetadataController.store.getState()).toStrictEqual({ - ...EXPECTED_DEFAULT_STATE, - currentAppVersion: '3', - previousAppVersion: '2', - }); + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentAppVersion: '3', + previousAppVersion: '2', + }); + }, + ); }); - it('updates the currentMigrationVersion state property if the currentMigrationVersion param does not match the default value', async () => { - const appMetadataController = new AppMetadataController({ - state: {}, - currentMigrationVersion: 1, - }); - expect(appMetadataController.store.getState()).toStrictEqual({ - ...EXPECTED_DEFAULT_STATE, - currentMigrationVersion: 1, - }); + it('updates the currentMigrationVersion state property if the currentMigrationVersion param does not match the default value', () => { + withController( + { + state: {}, + currentMigrationVersion: 1, + }, + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentMigrationVersion: 1, + }); + }, + ); }); - it('updates the currentMigrationVersion and previousMigrationVersion state properties if the currentMigrationVersion param, the currentMigrationVersion state property and the previousMigrationVersion state property are all different', async () => { - const appMetadataController = new AppMetadataController({ - state: { - currentMigrationVersion: 2, - previousMigrationVersion: 1, + it('updates the currentMigrationVersion and previousMigrationVersion state properties if the currentMigrationVersion param, the currentMigrationVersion state property and the previousMigrationVersion state property are all different', () => { + withController( + { + state: { + currentMigrationVersion: 2, + previousMigrationVersion: 1, + }, + currentMigrationVersion: 3, }, - currentMigrationVersion: 3, - }); - expect(appMetadataController.store.getState()).toStrictEqual({ - ...EXPECTED_DEFAULT_STATE, - currentMigrationVersion: 3, - previousMigrationVersion: 2, - }); + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentMigrationVersion: 3, + previousMigrationVersion: 2, + }); + }, + ); }); }); }); + +type WithControllerOptions = Partial; + +type WithControllerCallback = ({ + controller, +}: { + controller: AppMetadataController; +}) => ReturnValue; + +type WithControllerArgs = + | [WithControllerCallback] + | [WithControllerOptions, WithControllerCallback]; + +function withController( + ...args: WithControllerArgs +): ReturnValue { + const [options = {}, fn] = args.length === 2 ? args : [{}, args[0]]; + + const controllerMessenger = new ControllerMessenger(); + + const messenger = controllerMessenger.getRestricted({ + name: 'AppMetadataController', + allowedActions: [], + allowedEvents: [], + }); + + return fn({ + controller: new AppMetadataController({ + messenger, + ...options, + }), + }); +} diff --git a/app/scripts/controllers/app-metadata.ts b/app/scripts/controllers/app-metadata.ts index 0d745730d0c0..495dd547a2d7 100644 --- a/app/scripts/controllers/app-metadata.ts +++ b/app/scripts/controllers/app-metadata.ts @@ -1,5 +1,22 @@ -import EventEmitter from 'events'; -import { ObservableStore } from '@metamask/obs-store'; +import { + BaseController, + ControllerGetStateAction, + ControllerStateChangeEvent, + RestrictedControllerMessenger, +} from '@metamask/base-controller'; + +// Unique name for the controller +const controllerName = 'AppMetadataController'; + +/** + * The options that AppMetadataController takes. + */ +export type AppMetadataControllerOptions = { + state?: Partial; + messenger: AppMetadataControllerMessenger; + currentMigrationVersion?: number; + currentAppVersion?: string; +}; /** * The state of the AppMetadataController @@ -12,19 +29,84 @@ export type AppMetadataControllerState = { }; /** - * The options that NetworkController takes. + * Function to get default state of the {@link AppMetadataController}. */ -export type AppMetadataControllerOptions = { - currentMigrationVersion?: number; - currentAppVersion?: string; - state?: Partial; -}; +export const getDefaultAppMetadataControllerState = + (): AppMetadataControllerState => ({ + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, + }); + +/** + * Returns the state of the {@link AppMetadataController}. + */ +export type AppMetadataControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + AppMetadataControllerState +>; + +/** + * Actions exposed by the {@link AppMetadataController}. + */ +export type AppMetadataControllerActions = AppMetadataControllerGetStateAction; + +/** + * Event emitted when the state of the {@link AppMetadataController} changes. + */ +export type AppMetadataControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + AppMetadataControllerState +>; + +export type AppMetadataControllerEvents = AppMetadataControllerStateChangeEvent; + +/** + * Actions that this controller is allowed to call. + */ +type AllowedActions = never; -const defaultState: AppMetadataControllerState = { - currentAppVersion: '', - previousAppVersion: '', - previousMigrationVersion: 0, - currentMigrationVersion: 0, +/** + * Events that this controller is allowed to subscribe. + */ +type AllowedEvents = never; + +/** + * Messenger type for the {@link AppMetadataController}. + */ +type AppMetadataControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + AppMetadataControllerActions | AllowedActions, + AppMetadataControllerEvents | AllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] +>; + +/** + * {@link AppMetadataController}'s metadata. + * + * This allows us to choose if fields of the state should be persisted or not + * using the `persist` flag; and if they can be sent to Sentry or not, using + * the `anonymous` flag. + */ +const controllerMetadata = { + currentAppVersion: { + persist: true, + anonymous: true, + }, + previousAppVersion: { + persist: true, + anonymous: true, + }, + previousMigrationVersion: { + persist: true, + anonymous: true, + }, + currentMigrationVersion: { + persist: true, + anonymous: true, + }, }; /** @@ -33,30 +115,34 @@ const defaultState: AppMetadataControllerState = { * run migration. * */ -export default class AppMetadataController extends EventEmitter { - /** - * Observable store containing controller data. - */ - store: ObservableStore; - +export default class AppMetadataController extends BaseController< + typeof controllerName, + AppMetadataControllerState, + AppMetadataControllerMessenger +> { /** * Constructs a AppMetadata controller. * * @param options - the controller options * @param options.state - Initial controller state. + * @param options.messenger - Messenger used to communicate with BaseV2 controller. * @param options.currentMigrationVersion * @param options.currentAppVersion */ constructor({ + state = {}, + messenger, currentAppVersion = '', currentMigrationVersion = 0, - state = {}, }: AppMetadataControllerOptions) { - super(); - - this.store = new ObservableStore({ - ...defaultState, - ...state, + super({ + name: controllerName, + metadata: controllerMetadata, + state: { + ...getDefaultAppMetadataControllerState(), + ...state, + }, + messenger, }); this.#maybeUpdateAppVersion(currentAppVersion); @@ -70,12 +156,12 @@ export default class AppMetadataController extends EventEmitter { * @param maybeNewAppVersion */ #maybeUpdateAppVersion(maybeNewAppVersion: string): void { - const oldCurrentAppVersion = this.store.getState().currentAppVersion; + const oldCurrentAppVersion = this.state.currentAppVersion; if (maybeNewAppVersion !== oldCurrentAppVersion) { - this.store.updateState({ - currentAppVersion: maybeNewAppVersion, - previousAppVersion: oldCurrentAppVersion, + this.update((state) => { + state.currentAppVersion = maybeNewAppVersion; + state.previousAppVersion = oldCurrentAppVersion; }); } } @@ -86,13 +172,12 @@ export default class AppMetadataController extends EventEmitter { * @param maybeNewMigrationVersion */ #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { - const oldCurrentMigrationVersion = - this.store.getState().currentMigrationVersion; + const oldCurrentMigrationVersion = this.state.currentMigrationVersion; if (maybeNewMigrationVersion !== oldCurrentMigrationVersion) { - this.store.updateState({ - previousMigrationVersion: oldCurrentMigrationVersion, - currentMigrationVersion: maybeNewMigrationVersion, + this.update((state) => { + state.previousMigrationVersion = oldCurrentMigrationVersion; + state.currentMigrationVersion = maybeNewMigrationVersion; }); } } diff --git a/app/scripts/controllers/app-state-controller.test.ts b/app/scripts/controllers/app-state-controller.test.ts index 4bc1cb63e390..727df9b853e1 100644 --- a/app/scripts/controllers/app-state-controller.test.ts +++ b/app/scripts/controllers/app-state-controller.test.ts @@ -1,4 +1,9 @@ import { ControllerMessenger } from '@metamask/base-controller'; +import type { + AcceptRequest, + AddApprovalRequest, +} from '@metamask/approval-controller'; +import { KeyringControllerQRKeyringStateChangeEvent } from '@metamask/keyring-controller'; import { Browser } from 'webextension-polyfill'; import { ENVIRONMENT_TYPE_POPUP, @@ -6,15 +11,18 @@ import { POLLING_TOKEN_ENVIRONMENT_TYPES, } from '../../../shared/constants/app'; import { AccountOverviewTabKey } from '../../../shared/constants/app-state'; +import { MINUTE } from '../../../shared/constants/time'; import { AppStateController } from './app-state-controller'; import type { - AllowedActions, - AllowedEvents, AppStateControllerActions, AppStateControllerEvents, - AppStateControllerState, + AppStateControllerOptions, } from './app-state-controller'; -import { PreferencesControllerState } from './preferences-controller'; +import type { + PreferencesControllerState, + PreferencesControllerGetStateAction, + PreferencesControllerStateChangeEvent, +} from './preferences-controller'; jest.mock('webextension-polyfill'); @@ -25,12 +33,6 @@ jest.mock('../../../shared/modules/mv3.utils', () => ({ }, })); -let appStateController: AppStateController; -let controllerMessenger: ControllerMessenger< - AppStateControllerActions | AllowedActions, - AppStateControllerEvents | AllowedEvents ->; - const extensionMock = { alarms: { getAll: jest.fn(() => Promise.resolve([])), @@ -43,691 +45,590 @@ const extensionMock = { } as unknown as jest.Mocked; describe('AppStateController', () => { - const createAppStateController = ( - initState: Partial = {}, - ): { - appStateController: AppStateController; - controllerMessenger: typeof controllerMessenger; - } => { - controllerMessenger = new ControllerMessenger(); - jest.spyOn(ControllerMessenger.prototype, 'call'); - const appStateMessenger = controllerMessenger.getRestricted({ - name: 'AppStateController', - allowedActions: [ - `ApprovalController:addRequest`, - `ApprovalController:acceptRequest`, - `PreferencesController:getState`, - ], - allowedEvents: [ - `PreferencesController:stateChange`, - `KeyringController:qrKeyringStateChange`, - ], - }); - controllerMessenger.registerActionHandler( - 'PreferencesController:getState', - jest.fn().mockReturnValue({ - preferences: { - autoLockTimeLimit: 0, - }, - }), - ); - controllerMessenger.registerActionHandler( - 'ApprovalController:addRequest', - jest.fn().mockReturnValue({ - catch: jest.fn(), - }), - ); - appStateController = new AppStateController({ - addUnlockListener: jest.fn(), - isUnlocked: jest.fn(() => true), - initState, - onInactiveTimeout: jest.fn(), - messenger: appStateMessenger, - extension: extensionMock, - }); - - return { appStateController, controllerMessenger }; - }; - - const createIsUnlockedMock = (isUnlocked: boolean) => { - return jest - .spyOn( - appStateController as unknown as { isUnlocked: () => boolean }, - 'isUnlocked', - ) - .mockReturnValue(isUnlocked); - }; - - beforeEach(() => { - ({ appStateController } = createAppStateController()); - }); - describe('setOutdatedBrowserWarningLastShown', () => { - it('sets the last shown time', () => { - ({ appStateController } = createAppStateController()); - const timestamp: number = Date.now(); + it('sets the last shown time', async () => { + await withController(({ controller }) => { + const timestamp: number = Date.now(); - appStateController.setOutdatedBrowserWarningLastShown(timestamp); + controller.setOutdatedBrowserWarningLastShown(timestamp); - expect( - appStateController.store.getState().outdatedBrowserWarningLastShown, - ).toStrictEqual(timestamp); + expect(controller.state.outdatedBrowserWarningLastShown).toStrictEqual( + timestamp, + ); + }); }); - it('sets outdated browser warning last shown timestamp', () => { - const lastShownTimestamp: number = Date.now(); - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); + it('sets outdated browser warning last shown timestamp', async () => { + await withController(({ controller }) => { + const lastShownTimestamp: number = Date.now(); - appStateController.setOutdatedBrowserWarningLastShown(lastShownTimestamp); + controller.setOutdatedBrowserWarningLastShown(lastShownTimestamp); - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - outdatedBrowserWarningLastShown: lastShownTimestamp, + expect(controller.state.outdatedBrowserWarningLastShown).toStrictEqual( + lastShownTimestamp, + ); }); - - updateStateSpy.mockRestore(); }); }); describe('getUnlockPromise', () => { it('waits for unlock if the extension is locked', async () => { - ({ appStateController } = createAppStateController()); - const isUnlockedMock = createIsUnlockedMock(false); - const waitForUnlockSpy = jest.spyOn(appStateController, 'waitForUnlock'); - - appStateController.getUnlockPromise(true); - expect(isUnlockedMock).toHaveBeenCalled(); - expect(waitForUnlockSpy).toHaveBeenCalledWith(expect.any(Function), true); + await withController(({ controller }) => { + const isUnlockedMock = jest + .spyOn(controller, 'isUnlocked') + .mockReturnValue(false); + expect(controller.waitingForUnlock).toHaveLength(0); + + controller.getUnlockPromise(true); + expect(isUnlockedMock).toHaveBeenCalled(); + expect(controller.waitingForUnlock).toHaveLength(1); + }); }); it('resolves immediately if the extension is already unlocked', async () => { - ({ appStateController } = createAppStateController()); - const isUnlockedMock = createIsUnlockedMock(true); + await withController(async ({ controller }) => { + const isUnlockedMock = jest + .spyOn(controller, 'isUnlocked') + .mockReturnValue(true); - await expect( - appStateController.getUnlockPromise(false), - ).resolves.toBeUndefined(); + await expect( + controller.getUnlockPromise(false), + ).resolves.toBeUndefined(); - expect(isUnlockedMock).toHaveBeenCalled(); + expect(isUnlockedMock).toHaveBeenCalled(); + }); }); - }); - describe('waitForUnlock', () => { - it('resolves immediately if already unlocked', async () => { - const emitSpy = jest.spyOn(appStateController, 'emit'); - const resolveFn: () => void = jest.fn(); - appStateController.waitForUnlock(resolveFn, false); - expect(emitSpy).toHaveBeenCalledWith('updateBadge'); - expect(controllerMessenger.call).toHaveBeenCalledTimes(1); + it('publishes an unlock change event when isUnlocked is set to false', async () => { + await withController(async ({ controller, controllerMessenger }) => { + jest.spyOn(controller, 'isUnlocked').mockReturnValue(false); + const unlockChangeSpy = jest.fn(); + controllerMessenger.subscribe( + 'AppStateController:unlockChange', + unlockChangeSpy, + ); + const unlockPromise = controller.getUnlockPromise(false); + + const timeoutPromise = new Promise((resolve) => + setTimeout(() => resolve('timeout'), 100), + ); + + const result = await Promise.race([unlockPromise, timeoutPromise]); + + expect(result).toBe('timeout'); + + expect(unlockChangeSpy).toHaveBeenCalled(); + }); }); it('creates approval request when waitForUnlock is called with shouldShowUnlockRequest as true', async () => { - createIsUnlockedMock(false); - - const resolveFn: () => void = jest.fn(); - appStateController.waitForUnlock(resolveFn, true); - - expect(controllerMessenger.call).toHaveBeenCalledTimes(2); - expect(controllerMessenger.call).toHaveBeenCalledWith( - 'ApprovalController:addRequest', - expect.objectContaining({ - id: expect.any(String), - origin: ORIGIN_METAMASK, - type: 'unlock', - }), - true, - ); + const addRequestMock = jest.fn().mockResolvedValue(undefined); + await withController({ addRequestMock }, async ({ controller }) => { + jest.spyOn(controller, 'isUnlocked').mockReturnValue(false); + + controller.getUnlockPromise(true); + + expect(addRequestMock).toHaveBeenCalled(); + expect(addRequestMock).toHaveBeenCalledWith( + { + id: expect.any(String), + origin: ORIGIN_METAMASK, + type: 'unlock', + }, + true, + ); + }); }); - }); - describe('handleUnlock', () => { - beforeEach(() => { - createIsUnlockedMock(false); - }); - afterEach(() => { - jest.clearAllMocks(); - }); it('accepts approval request revolving all the related promises', async () => { - const emitSpy = jest.spyOn(appStateController, 'emit'); - const resolveFn: () => void = jest.fn(); - appStateController.waitForUnlock(resolveFn, true); - - appStateController.handleUnlock(); - - expect(emitSpy).toHaveBeenCalled(); - expect(emitSpy).toHaveBeenCalledWith('updateBadge'); - expect(controllerMessenger.call).toHaveBeenCalled(); - expect(controllerMessenger.call).toHaveBeenCalledWith( - 'ApprovalController:acceptRequest', - expect.any(String), + let unlockListener: () => void; + const addRequestMock = jest.fn().mockResolvedValue(undefined); + await withController( + { + addRequestMock, + options: { + addUnlockListener: (listener) => { + unlockListener = listener; + }, + }, + }, + ({ controller, controllerMessenger }) => { + jest.spyOn(controller, 'isUnlocked').mockReturnValue(false); + const unlockChangeSpy = jest.fn(); + controllerMessenger.subscribe( + 'AppStateController:unlockChange', + unlockChangeSpy, + ); + + controller.getUnlockPromise(true); + + unlockListener(); + + expect(unlockChangeSpy).toHaveBeenCalled(); + expect(addRequestMock).toHaveBeenCalled(); + expect(addRequestMock).toHaveBeenCalledWith( + { + id: expect.any(String), + origin: ORIGIN_METAMASK, + type: 'unlock', + }, + true, + ); + }, ); }); }); describe('setDefaultHomeActiveTabName', () => { - it('sets the default home tab name', () => { - appStateController.setDefaultHomeActiveTabName( - AccountOverviewTabKey.Activity, - ); - expect(appStateController.store.getState().defaultHomeActiveTabName).toBe( - AccountOverviewTabKey.Activity, - ); + it('sets the default home tab name', async () => { + await withController(({ controller }) => { + controller.setDefaultHomeActiveTabName(AccountOverviewTabKey.Activity); + + expect(controller.state.defaultHomeActiveTabName).toBe( + AccountOverviewTabKey.Activity, + ); + }); }); }); describe('setConnectedStatusPopoverHasBeenShown', () => { - it('sets connected status popover as shown', () => { - appStateController.setConnectedStatusPopoverHasBeenShown(); - expect( - appStateController.store.getState().connectedStatusPopoverHasBeenShown, - ).toBe(true); + it('sets connected status popover as shown', async () => { + await withController(({ controller }) => { + controller.setConnectedStatusPopoverHasBeenShown(); + + expect(controller.state.connectedStatusPopoverHasBeenShown).toBe(true); + }); }); }); describe('setRecoveryPhraseReminderHasBeenShown', () => { - it('sets recovery phrase reminder as shown', () => { - appStateController.setRecoveryPhraseReminderHasBeenShown(); - expect( - appStateController.store.getState().recoveryPhraseReminderHasBeenShown, - ).toBe(true); + it('sets recovery phrase reminder as shown', async () => { + await withController(({ controller }) => { + controller.setRecoveryPhraseReminderHasBeenShown(); + + expect(controller.state.recoveryPhraseReminderHasBeenShown).toBe(true); + }); }); }); describe('setRecoveryPhraseReminderLastShown', () => { - it('sets the last shown time of recovery phrase reminder', () => { - const timestamp: number = Date.now(); - appStateController.setRecoveryPhraseReminderLastShown(timestamp); - - expect( - appStateController.store.getState().recoveryPhraseReminderLastShown, - ).toBe(timestamp); + it('sets the last shown time of recovery phrase reminder', async () => { + await withController(({ controller }) => { + const timestamp = Date.now(); + controller.setRecoveryPhraseReminderLastShown(timestamp); + + expect(controller.state.recoveryPhraseReminderLastShown).toBe( + timestamp, + ); + }); }); }); describe('setLastActiveTime', () => { - it('sets the last active time to the current time', () => { - const spy = jest.spyOn( - appStateController as unknown as { _resetTimer: () => void }, - '_resetTimer', - ); - appStateController.setLastActiveTime(); - - expect(spy).toHaveBeenCalled(); + it('sets the timer if timeoutMinutes is set', async () => { + await withController(({ controller, controllerMessenger }) => { + const timeout = Date.now(); + controllerMessenger.publish( + 'PreferencesController:stateChange', + { + preferences: { autoLockTimeLimit: timeout }, + } as unknown as PreferencesControllerState, + [], + ); + jest.spyOn(global, 'setTimeout'); + + controller.setLastActiveTime(); + + expect(setTimeout).toHaveBeenCalledWith( + expect.any(Function), + timeout * MINUTE, + ); + }); }); - it('sets the timer if timeoutMinutes is set', () => { - const timeout = Date.now(); - controllerMessenger.publish( - 'PreferencesController:stateChange', - { - preferences: { autoLockTimeLimit: timeout }, - } as unknown as PreferencesControllerState, - [], - ); - const spy = jest.spyOn( - appStateController as unknown as { _resetTimer: () => void }, - '_resetTimer', - ); - appStateController.setLastActiveTime(); + it("doesn't set the timer if timeoutMinutes is not set", async () => { + await withController(({ controller }) => { + jest.spyOn(global, 'setTimeout'); - expect(spy).toHaveBeenCalled(); + controller.setLastActiveTime(); + + expect(setTimeout).toHaveBeenCalledTimes(0); + }); }); }); describe('setBrowserEnvironment', () => { - it('sets the current browser and OS environment', () => { - appStateController.setBrowserEnvironment('Windows', 'Chrome'); - expect( - appStateController.store.getState().browserEnvironment, - ).toStrictEqual({ - os: 'Windows', - browser: 'Chrome', + it('sets the current browser and OS environment', async () => { + await withController(({ controller }) => { + controller.setBrowserEnvironment('Windows', 'Chrome'); + + expect(controller.state.browserEnvironment).toStrictEqual({ + os: 'Windows', + browser: 'Chrome', + }); }); }); }); describe('addPollingToken', () => { - it('adds a pollingToken for a given environmentType', () => { - const pollingTokenType = - POLLING_TOKEN_ENVIRONMENT_TYPES[ENVIRONMENT_TYPE_POPUP]; - appStateController.addPollingToken('token1', pollingTokenType); - expect(appStateController.store.getState()[pollingTokenType]).toContain( - 'token1', - ); + it('adds a pollingToken for a given environmentType', async () => { + await withController(({ controller }) => { + const pollingTokenType = + POLLING_TOKEN_ENVIRONMENT_TYPES[ENVIRONMENT_TYPE_POPUP]; + controller.addPollingToken('token1', pollingTokenType); + + expect(controller.state[pollingTokenType]).toContain('token1'); + }); }); }); describe('removePollingToken', () => { - it('removes a pollingToken for a given environmentType', () => { - const pollingTokenType = - POLLING_TOKEN_ENVIRONMENT_TYPES[ENVIRONMENT_TYPE_POPUP]; - appStateController.addPollingToken('token1', pollingTokenType); - appStateController.removePollingToken('token1', pollingTokenType); - expect( - appStateController.store.getState()[pollingTokenType], - ).not.toContain('token1'); + it('removes a pollingToken for a given environmentType', async () => { + await withController(({ controller }) => { + const pollingTokenType = + POLLING_TOKEN_ENVIRONMENT_TYPES[ENVIRONMENT_TYPE_POPUP]; + + controller.addPollingToken('token1', pollingTokenType); + controller.removePollingToken('token1', pollingTokenType); + + expect(controller.state[pollingTokenType]).not.toContain('token1'); + }); }); }); describe('clearPollingTokens', () => { - it('clears all pollingTokens', () => { - appStateController.addPollingToken('token1', 'popupGasPollTokens'); - appStateController.addPollingToken('token2', 'notificationGasPollTokens'); - appStateController.addPollingToken('token3', 'fullScreenGasPollTokens'); - appStateController.clearPollingTokens(); - - expect( - appStateController.store.getState().popupGasPollTokens, - ).toStrictEqual([]); - expect( - appStateController.store.getState().notificationGasPollTokens, - ).toStrictEqual([]); - expect( - appStateController.store.getState().fullScreenGasPollTokens, - ).toStrictEqual([]); + it('clears all pollingTokens', async () => { + await withController(({ controller }) => { + controller.addPollingToken('token1', 'popupGasPollTokens'); + controller.addPollingToken('token2', 'notificationGasPollTokens'); + controller.addPollingToken('token3', 'fullScreenGasPollTokens'); + controller.clearPollingTokens(); + + expect(controller.state.popupGasPollTokens).toStrictEqual([]); + expect(controller.state.notificationGasPollTokens).toStrictEqual([]); + expect(controller.state.fullScreenGasPollTokens).toStrictEqual([]); + }); }); }); describe('setShowTestnetMessageInDropdown', () => { - it('sets whether the testnet dismissal link should be shown in the network dropdown', () => { - appStateController.setShowTestnetMessageInDropdown(true); - expect( - appStateController.store.getState().showTestnetMessageInDropdown, - ).toBe(true); + it('sets whether the testnet dismissal link should be shown in the network dropdown', async () => { + await withController(({ controller }) => { + controller.setShowTestnetMessageInDropdown(true); - appStateController.setShowTestnetMessageInDropdown(false); - expect( - appStateController.store.getState().showTestnetMessageInDropdown, - ).toBe(false); + expect(controller.state.showTestnetMessageInDropdown).toBe(true); + + controller.setShowTestnetMessageInDropdown(false); + + expect(controller.state.showTestnetMessageInDropdown).toBe(false); + }); }); }); describe('setShowBetaHeader', () => { - it('sets whether the beta notification heading on the home page', () => { - appStateController.setShowBetaHeader(true); - expect(appStateController.store.getState().showBetaHeader).toBe(true); + it('sets whether the beta notification heading on the home page', async () => { + await withController(({ controller }) => { + controller.setShowBetaHeader(true); + + expect(controller.state.showBetaHeader).toBe(true); - appStateController.setShowBetaHeader(false); - expect(appStateController.store.getState().showBetaHeader).toBe(false); + controller.setShowBetaHeader(false); + + expect(controller.state.showBetaHeader).toBe(false); + }); }); }); describe('setCurrentPopupId', () => { - it('sets the currentPopupId in the appState', () => { - const popupId = 12345; + it('sets the currentPopupId in the appState', async () => { + await withController(({ controller }) => { + const popupId = 12345; + + controller.setCurrentPopupId(popupId); - appStateController.setCurrentPopupId(popupId); - expect(appStateController.store.getState().currentPopupId).toBe(popupId); + expect(controller.state.currentPopupId).toBe(popupId); + }); }); }); describe('getCurrentPopupId', () => { - it('retrieves the currentPopupId saved in the appState', () => { - const popupId = 54321; + it('retrieves the currentPopupId saved in the appState', async () => { + await withController(({ controller }) => { + const popupId = 54321; - appStateController.setCurrentPopupId(popupId); - expect(appStateController.getCurrentPopupId()).toBe(popupId); - }); - }); - - describe('setFirstTimeUsedNetwork', () => { - it('updates the array of the first time used networks', () => { - const chainId = '0x1'; + controller.setCurrentPopupId(popupId); - appStateController.setFirstTimeUsedNetwork(chainId); - expect(appStateController.store.getState().usedNetworks[chainId]).toBe( - true, - ); + expect(controller.getCurrentPopupId()).toBe(popupId); + }); }); }); describe('setLastInteractedConfirmationInfo', () => { - it('sets information about last confirmation user has interacted with', () => { - const lastInteractedConfirmationInfo = { - id: '123', - chainId: '0x1', - timestamp: new Date().getTime(), - }; - appStateController.setLastInteractedConfirmationInfo( - lastInteractedConfirmationInfo, - ); - expect(appStateController.getLastInteractedConfirmationInfo()).toBe( - lastInteractedConfirmationInfo, - ); + it('sets information about last confirmation user has interacted with', async () => { + await withController(({ controller }) => { + const lastInteractedConfirmationInfo = { + id: '123', + chainId: '0x1', + timestamp: new Date().getTime(), + }; - appStateController.setLastInteractedConfirmationInfo(undefined); - expect(appStateController.getLastInteractedConfirmationInfo()).toBe( - undefined, - ); + controller.setLastInteractedConfirmationInfo( + lastInteractedConfirmationInfo, + ); + + expect(controller.getLastInteractedConfirmationInfo()).toBe( + lastInteractedConfirmationInfo, + ); + + controller.setLastInteractedConfirmationInfo(undefined); + + expect(controller.getLastInteractedConfirmationInfo()).toBe(undefined); + }); }); }); describe('setSnapsInstallPrivacyWarningShownStatus', () => { - it('updates the status of snaps install privacy warning', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); + it('updates the status of snaps install privacy warning', async () => { + await withController(({ controller }) => { + controller.setSnapsInstallPrivacyWarningShownStatus(true); - appStateController.setSnapsInstallPrivacyWarningShownStatus(true); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - snapsInstallPrivacyWarningShown: true, + expect(controller.state.snapsInstallPrivacyWarningShown).toStrictEqual( + true, + ); }); - - updateStateSpy.mockRestore(); }); }); describe('institutional', () => { - it('set the interactive replacement token with a url and the old refresh token', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); - - const mockParams = { - url: 'https://example.com', - oldRefreshToken: 'old', - }; - - appStateController.showInteractiveReplacementTokenBanner(mockParams); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - interactiveReplacementToken: mockParams, + it('set the interactive replacement token with a url and the old refresh token', async () => { + await withController(({ controller }) => { + const mockParams = { + url: 'https://example.com', + oldRefreshToken: 'old', + }; + + controller.showInteractiveReplacementTokenBanner(mockParams); + + expect(controller.state.interactiveReplacementToken).toStrictEqual( + mockParams, + ); }); - - updateStateSpy.mockRestore(); }); - it('set the setCustodianDeepLink with the fromAddress and custodyId', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); - - const mockParams = { - fromAddress: '0x', - custodyId: 'custodyId', - }; + it('set the setCustodianDeepLink with the fromAddress and custodyId', async () => { + await withController(({ controller }) => { + const mockParams = { + fromAddress: '0x', + custodyId: 'custodyId', + }; - appStateController.setCustodianDeepLink(mockParams); + controller.setCustodianDeepLink(mockParams); - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - custodianDeepLink: mockParams, + expect(controller.state.custodianDeepLink).toStrictEqual(mockParams); }); - - updateStateSpy.mockRestore(); }); - it('set the setNoteToTraderMessage with a message', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); + it('set the setNoteToTraderMessage with a message', async () => { + await withController(({ controller }) => { + const mockParams = 'some message'; - const mockParams = 'some message'; + controller.setNoteToTraderMessage(mockParams); - appStateController.setNoteToTraderMessage(mockParams); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - noteToTraderMessage: mockParams, + expect(controller.state.noteToTraderMessage).toStrictEqual(mockParams); }); - - updateStateSpy.mockRestore(); }); }); describe('setSurveyLinkLastClickedOrClosed', () => { - it('set the surveyLinkLastClickedOrClosed time', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); + it('set the surveyLinkLastClickedOrClosed time', async () => { + await withController(({ controller }) => { + const mockParams = Date.now(); - const mockParams = Date.now(); + controller.setSurveyLinkLastClickedOrClosed(mockParams); - appStateController.setSurveyLinkLastClickedOrClosed(mockParams); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - surveyLinkLastClickedOrClosed: mockParams, + expect(controller.state.surveyLinkLastClickedOrClosed).toStrictEqual( + mockParams, + ); }); - - updateStateSpy.mockRestore(); }); }); describe('setOnboardingDate', () => { - it('set the onboardingDate', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); - - appStateController.setOnboardingDate(); + it('set the onboardingDate', async () => { + await withController(({ controller }) => { + const mockDateNow = 1620000000000; + jest.spyOn(Date, 'now').mockReturnValue(mockDateNow); - expect(updateStateSpy).toHaveBeenCalledTimes(1); + controller.setOnboardingDate(); - updateStateSpy.mockRestore(); + expect(controller.state.onboardingDate).toStrictEqual(mockDateNow); + }); }); }); describe('setLastViewedUserSurvey', () => { - it('set the lastViewedUserSurvey with id 1', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); + it('set the lastViewedUserSurvey with id 1', async () => { + await withController(({ controller }) => { + const mockParams = 1; - const mockParams = 1; + controller.setLastViewedUserSurvey(mockParams); - appStateController.setLastViewedUserSurvey(mockParams); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - lastViewedUserSurvey: mockParams, + expect(controller.state.lastViewedUserSurvey).toStrictEqual(mockParams); }); - - updateStateSpy.mockRestore(); }); }); describe('setNewPrivacyPolicyToastClickedOrClosed', () => { - it('set the newPrivacyPolicyToastClickedOrClosed to true', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); - - appStateController.setNewPrivacyPolicyToastClickedOrClosed(); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect( - appStateController.store.getState() - .newPrivacyPolicyToastClickedOrClosed, - ).toStrictEqual(true); + it('set the newPrivacyPolicyToastClickedOrClosed to true', async () => { + await withController(({ controller }) => { + controller.setNewPrivacyPolicyToastClickedOrClosed(); - updateStateSpy.mockRestore(); + expect( + controller.state.newPrivacyPolicyToastClickedOrClosed, + ).toStrictEqual(true); + }); }); }); describe('setNewPrivacyPolicyToastShownDate', () => { - it('set the newPrivacyPolicyToastShownDate', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); + it('set the newPrivacyPolicyToastShownDate', async () => { + await withController(({ controller }) => { + const mockParams = Date.now(); - const mockParams = Date.now(); + controller.setNewPrivacyPolicyToastShownDate(mockParams); - appStateController.setNewPrivacyPolicyToastShownDate(mockParams); - - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - newPrivacyPolicyToastShownDate: mockParams, + expect(controller.state.newPrivacyPolicyToastShownDate).toStrictEqual( + mockParams, + ); }); - expect( - appStateController.store.getState().newPrivacyPolicyToastShownDate, - ).toStrictEqual(mockParams); - - updateStateSpy.mockRestore(); }); }); describe('setTermsOfUseLastAgreed', () => { - it('set the termsOfUseLastAgreed timestamp', () => { - ({ appStateController } = createAppStateController()); - const updateStateSpy = jest.spyOn( - appStateController.store, - 'updateState', - ); - - const mockParams = Date.now(); + it('set the termsOfUseLastAgreed timestamp', async () => { + await withController(({ controller }) => { + const mockParams = Date.now(); - appStateController.setTermsOfUseLastAgreed(mockParams); + controller.setTermsOfUseLastAgreed(mockParams); - expect(updateStateSpy).toHaveBeenCalledTimes(1); - expect(updateStateSpy).toHaveBeenCalledWith({ - termsOfUseLastAgreed: mockParams, + expect(controller.state.termsOfUseLastAgreed).toStrictEqual(mockParams); }); - expect( - appStateController.store.getState().termsOfUseLastAgreed, - ).toStrictEqual(mockParams); - - updateStateSpy.mockRestore(); }); }); describe('onPreferencesStateChange', () => { - it('should update the timeoutMinutes with the autoLockTimeLimit', () => { - ({ appStateController, controllerMessenger } = - createAppStateController()); - const timeout = Date.now(); - - controllerMessenger.publish( - 'PreferencesController:stateChange', - { - preferences: { autoLockTimeLimit: timeout }, - } as unknown as PreferencesControllerState, - [], - ); - - expect(appStateController.store.getState().timeoutMinutes).toStrictEqual( - timeout, - ); + it('should update the timeoutMinutes with the autoLockTimeLimit', async () => { + await withController(({ controller, controllerMessenger }) => { + const timeout = Date.now(); + + controllerMessenger.publish( + 'PreferencesController:stateChange', + { + preferences: { autoLockTimeLimit: timeout }, + } as unknown as PreferencesControllerState, + [], + ); + + expect(controller.state.timeoutMinutes).toStrictEqual(timeout); + }); }); }); describe('isManifestV3', () => { - it('creates alarm when isManifestV3 is true', () => { + it('creates alarm when isManifestV3 is true', async () => { mockIsManifestV3.mockReturnValue(true); - ({ appStateController } = createAppStateController()); - - const timeout = Date.now(); - controllerMessenger.publish( - 'PreferencesController:stateChange', - { - preferences: { autoLockTimeLimit: timeout }, - } as unknown as PreferencesControllerState, - [], - ); - const spy = jest.spyOn( - appStateController as unknown as { _resetTimer: () => void }, - '_resetTimer', - ); - appStateController.setLastActiveTime(); - - expect(spy).toHaveBeenCalled(); - expect(extensionMock.alarms.clear).toHaveBeenCalled(); - expect(extensionMock.alarms.onAlarm.addListener).toHaveBeenCalled(); - }); - }); - - describe('AppStateController:getState', () => { - it('should return the current state of the property', () => { - expect( - appStateController.store.getState().recoveryPhraseReminderHasBeenShown, - ).toStrictEqual(false); - expect( - controllerMessenger.call('AppStateController:getState') - .recoveryPhraseReminderHasBeenShown, - ).toStrictEqual(false); + await withController(({ controller, controllerMessenger }) => { + const timeout = Date.now(); + controllerMessenger.publish( + 'PreferencesController:stateChange', + { + preferences: { autoLockTimeLimit: timeout }, + } as unknown as PreferencesControllerState, + [], + ); + controller.setLastActiveTime(); + + expect(extensionMock.alarms.clear).toHaveBeenCalled(); + expect(extensionMock.alarms.onAlarm.addListener).toHaveBeenCalled(); + }); }); }); +}); - describe('AppStateController:stateChange', () => { - it('subscribers will recieve the state when published', () => { - expect( - appStateController.store.getState().surveyLinkLastClickedOrClosed, - ).toStrictEqual(null); - const timeNow = Date.now(); - controllerMessenger.subscribe( - 'AppStateController:stateChange', - (state: Partial) => { - if (typeof state.surveyLinkLastClickedOrClosed === 'number') { - appStateController.setSurveyLinkLastClickedOrClosed( - state.surveyLinkLastClickedOrClosed, - ); - } - }, - ); - - controllerMessenger.publish( - 'AppStateController:stateChange', - { - surveyLinkLastClickedOrClosed: timeNow, - } as unknown as AppStateControllerState, - [], - ); - - expect( - appStateController.store.getState().surveyLinkLastClickedOrClosed, - ).toStrictEqual(timeNow); - expect( - controllerMessenger.call('AppStateController:getState') - .surveyLinkLastClickedOrClosed, - ).toStrictEqual(timeNow); - }); - - it('state will be published when there is state change', () => { - expect( - appStateController.store.getState().surveyLinkLastClickedOrClosed, - ).toStrictEqual(null); - const timeNow = Date.now(); - controllerMessenger.subscribe( - 'AppStateController:stateChange', - (state: Partial) => { - expect(state.surveyLinkLastClickedOrClosed).toStrictEqual(timeNow); - }, - ); - - appStateController.setSurveyLinkLastClickedOrClosed(timeNow); - - expect( - appStateController.store.getState().surveyLinkLastClickedOrClosed, - ).toStrictEqual(timeNow); - expect( - controllerMessenger.call('AppStateController:getState') - .surveyLinkLastClickedOrClosed, - ).toStrictEqual(timeNow); - }); +type WithControllerOptions = { + options?: Partial; + addRequestMock?: jest.Mock; +}; + +type WithControllerCallback = ({ + controller, + controllerMessenger, +}: { + controller: AppStateController; + controllerMessenger: ControllerMessenger< + | AppStateControllerActions + | AddApprovalRequest + | AcceptRequest + | PreferencesControllerGetStateAction, + | AppStateControllerEvents + | PreferencesControllerStateChangeEvent + | KeyringControllerQRKeyringStateChangeEvent + >; +}) => ReturnValue; + +type WithControllerArgs = + | [WithControllerCallback] + | [WithControllerOptions, WithControllerCallback]; + +async function withController( + ...args: WithControllerArgs +): Promise { + const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; + const { addRequestMock, options = {} } = rest; + + const controllerMessenger = new ControllerMessenger< + | AppStateControllerActions + | AddApprovalRequest + | AcceptRequest + | PreferencesControllerGetStateAction, + | AppStateControllerEvents + | PreferencesControllerStateChangeEvent + | KeyringControllerQRKeyringStateChangeEvent + >(); + const appStateMessenger = controllerMessenger.getRestricted({ + name: 'AppStateController', + allowedActions: [ + `ApprovalController:addRequest`, + `ApprovalController:acceptRequest`, + `PreferencesController:getState`, + ], + allowedEvents: [ + `PreferencesController:stateChange`, + `KeyringController:qrKeyringStateChange`, + ], + }); + controllerMessenger.registerActionHandler( + 'PreferencesController:getState', + jest.fn().mockReturnValue({ + preferences: { + autoLockTimeLimit: 0, + }, + }), + ); + controllerMessenger.registerActionHandler( + 'ApprovalController:addRequest', + addRequestMock || jest.fn().mockResolvedValue(undefined), + ); + + return fn({ + controller: new AppStateController({ + addUnlockListener: jest.fn(), + isUnlocked: jest.fn(() => true), + onInactiveTimeout: jest.fn(), + messenger: appStateMessenger, + extension: extensionMock, + ...options, + }), + controllerMessenger, }); -}); +} diff --git a/app/scripts/controllers/app-state-controller.ts b/app/scripts/controllers/app-state-controller.ts index 605f307ec0e4..828b2249c6c2 100644 --- a/app/scripts/controllers/app-state-controller.ts +++ b/app/scripts/controllers/app-state-controller.ts @@ -1,17 +1,19 @@ -import EventEmitter from 'events'; -import { ObservableStore } from '@metamask/obs-store'; import { v4 as uuid } from 'uuid'; import log from 'loglevel'; import { ApprovalType } from '@metamask/controller-utils'; import { KeyringControllerQRKeyringStateChangeEvent } from '@metamask/keyring-controller'; -import { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { + BaseController, + ControllerGetStateAction, + ControllerStateChangeEvent, + RestrictedControllerMessenger, +} from '@metamask/base-controller'; import { AcceptRequest, AddApprovalRequest, } from '@metamask/approval-controller'; import { Json } from '@metamask/utils'; import { Browser } from 'webextension-polyfill'; -import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import { MINUTE } from '../../../shared/constants/time'; import { AUTO_LOCK_TIMEOUT_ALARM } from '../../../shared/constants/alarms'; import { isManifestV3 } from '../../../shared/modules/mv3.utils'; @@ -26,7 +28,10 @@ import { import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences'; import { LastInteractedConfirmationInfo } from '../../../shared/types/confirm'; import { SecurityAlertResponse } from '../lib/ppom/types'; -import { AccountOverviewTabKey } from '../../../shared/constants/app-state'; +import { + AccountOverviewTabKey, + CarouselSlide, +} from '../../../shared/constants/app-state'; import type { Preferences, PreferencesControllerGetStateAction, @@ -62,7 +67,6 @@ export type AppStateControllerState = { hadAdvancedGasFeesSetPriorToMigration92_3: boolean; qrHardware: Json; nftsDropdownState: Json; - usedNetworks: Record; surveyLinkLastClickedOrClosed: number | null; signatureSecurityAlertResponses: Record; // States used for displaying the changed network toast @@ -75,6 +79,7 @@ export type AppStateControllerState = { interactiveReplacementToken?: { url: string; oldRefreshToken: string }; noteToTraderMessage?: string; custodianDeepLink?: { fromAddress: string; custodyId: string }; + slides: CarouselSlide[]; }; const controllerName = 'AppStateController'; @@ -82,10 +87,10 @@ const controllerName = 'AppStateController'; /** * Returns the state of the {@link AppStateController}. */ -export type AppStateControllerGetStateAction = { - type: 'AppStateController:getState'; - handler: () => AppStateControllerState; -}; +export type AppStateControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + AppStateControllerState +>; /** * Actions exposed by the {@link AppStateController}. @@ -95,7 +100,7 @@ export type AppStateControllerActions = AppStateControllerGetStateAction; /** * Actions that this controller is allowed to call. */ -export type AllowedActions = +type AllowedActions = | AddApprovalRequest | AcceptRequest | PreferencesControllerGetStateAction; @@ -103,20 +108,27 @@ export type AllowedActions = /** * Event emitted when the state of the {@link AppStateController} changes. */ -export type AppStateControllerStateChangeEvent = { - type: 'AppStateController:stateChange'; - payload: [AppStateControllerState, []]; +export type AppStateControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + AppStateControllerState +>; + +export type AppStateControllerUnlockChangeEvent = { + type: 'AppStateController:unlockChange'; + payload: []; }; /** * Events emitted by {@link AppStateController}. */ -export type AppStateControllerEvents = AppStateControllerStateChangeEvent; +export type AppStateControllerEvents = + | AppStateControllerStateChangeEvent + | AppStateControllerUnlockChangeEvent; /** * Events that this controller is allowed to subscribe. */ -export type AllowedEvents = +type AllowedEvents = | PreferencesControllerStateChangeEvent | KeyringControllerQRKeyringStateChangeEvent; @@ -138,27 +150,22 @@ type AppStateControllerInitState = Partial< AppStateControllerState, | 'qrHardware' | 'nftsDropdownState' - | 'usedNetworks' - | 'surveyLinkLastClickedOrClosed' | 'signatureSecurityAlertResponses' | 'switchedNetworkDetails' - | 'switchedNetworkNeverShowMessage' | 'currentExtensionPopupId' > >; -type AppStateControllerOptions = { +export type AppStateControllerOptions = { addUnlockListener: (callback: () => void) => void; isUnlocked: () => boolean; - initState?: AppStateControllerInitState; + state?: AppStateControllerInitState; onInactiveTimeout?: () => void; messenger: AppStateControllerMessenger; extension: Browser; }; -const getDefaultAppStateControllerState = ( - initState?: AppStateControllerInitState, -): AppStateControllerState => ({ +const getDefaultAppStateControllerState = (): AppStateControllerState => ({ timeoutMinutes: DEFAULT_AUTO_LOCK_TIME_LIMIT, connectedStatusPopoverHasBeenShown: true, defaultHomeActiveTabName: null, @@ -181,69 +188,226 @@ const getDefaultAppStateControllerState = ( newPrivacyPolicyToastClickedOrClosed: null, newPrivacyPolicyToastShownDate: null, hadAdvancedGasFeesSetPriorToMigration92_3: false, - ...initState, - qrHardware: {}, - nftsDropdownState: {}, - usedNetworks: { - '0x1': true, - '0x5': true, - '0x539': true, - }, surveyLinkLastClickedOrClosed: null, - signatureSecurityAlertResponses: {}, - switchedNetworkDetails: null, switchedNetworkNeverShowMessage: false, - currentExtensionPopupId: 0, + slides: [], + ...getInitialStateOverrides(), }); -export class AppStateController extends EventEmitter { - private readonly extension: AppStateControllerOptions['extension']; +function getInitialStateOverrides() { + return { + qrHardware: {}, + nftsDropdownState: {}, + signatureSecurityAlertResponses: {}, + switchedNetworkDetails: null, + currentExtensionPopupId: 0, + }; +} - private readonly onInactiveTimeout: () => void; +const controllerMetadata = { + timeoutMinutes: { + persist: true, + anonymous: true, + }, + connectedStatusPopoverHasBeenShown: { + persist: true, + anonymous: true, + }, + defaultHomeActiveTabName: { + persist: true, + anonymous: true, + }, + browserEnvironment: { + persist: true, + anonymous: true, + }, + popupGasPollTokens: { + persist: false, + anonymous: true, + }, + notificationGasPollTokens: { + persist: false, + anonymous: true, + }, + fullScreenGasPollTokens: { + persist: false, + anonymous: true, + }, + recoveryPhraseReminderHasBeenShown: { + persist: true, + anonymous: true, + }, + recoveryPhraseReminderLastShown: { + persist: true, + anonymous: true, + }, + outdatedBrowserWarningLastShown: { + persist: true, + anonymous: true, + }, + nftsDetectionNoticeDismissed: { + persist: true, + anonymous: true, + }, + showTestnetMessageInDropdown: { + persist: true, + anonymous: true, + }, + showBetaHeader: { + persist: true, + anonymous: true, + }, + showPermissionsTour: { + persist: true, + anonymous: true, + }, + showNetworkBanner: { + persist: true, + anonymous: true, + }, + showAccountBanner: { + persist: true, + anonymous: true, + }, + trezorModel: { + persist: true, + anonymous: true, + }, + currentPopupId: { + persist: false, + anonymous: true, + }, + onboardingDate: { + persist: true, + anonymous: true, + }, + lastViewedUserSurvey: { + persist: true, + anonymous: true, + }, + newPrivacyPolicyToastClickedOrClosed: { + persist: true, + anonymous: true, + }, + newPrivacyPolicyToastShownDate: { + persist: true, + anonymous: true, + }, + hadAdvancedGasFeesSetPriorToMigration92_3: { + persist: true, + anonymous: true, + }, + qrHardware: { + persist: false, + anonymous: true, + }, + nftsDropdownState: { + persist: false, + anonymous: true, + }, + surveyLinkLastClickedOrClosed: { + persist: true, + anonymous: true, + }, + signatureSecurityAlertResponses: { + persist: false, + anonymous: true, + }, + switchedNetworkDetails: { + persist: false, + anonymous: true, + }, + switchedNetworkNeverShowMessage: { + persist: true, + anonymous: true, + }, + currentExtensionPopupId: { + persist: false, + anonymous: true, + }, + lastInteractedConfirmationInfo: { + persist: true, + anonymous: true, + }, + termsOfUseLastAgreed: { + persist: true, + anonymous: true, + }, + snapsInstallPrivacyWarningShown: { + persist: true, + anonymous: true, + }, + interactiveReplacementToken: { + persist: true, + anonymous: true, + }, + noteToTraderMessage: { + persist: true, + anonymous: true, + }, + custodianDeepLink: { + persist: true, + anonymous: true, + }, + slides: { + persist: true, + anonymous: true, + }, +}; - store: ObservableStore; +export class AppStateController extends BaseController< + typeof controllerName, + AppStateControllerState, + AppStateControllerMessenger +> { + readonly #extension: AppStateControllerOptions['extension']; - private timer: NodeJS.Timeout | null; + readonly #onInactiveTimeout: () => void; - isUnlocked: () => boolean; + #timer: NodeJS.Timeout | null; - private readonly waitingForUnlock: { resolve: () => void }[]; + isUnlocked: () => boolean; - private readonly messagingSystem: AppStateControllerMessenger; + readonly waitingForUnlock: { resolve: () => void }[]; #approvalRequestId: string | null; - constructor(opts: AppStateControllerOptions) { - const { - addUnlockListener, - isUnlocked, - initState, - onInactiveTimeout, + constructor({ + state = {}, + messenger, + addUnlockListener, + isUnlocked, + onInactiveTimeout, + extension, + }: AppStateControllerOptions) { + super({ + name: controllerName, + metadata: controllerMetadata, + state: { + ...getDefaultAppStateControllerState(), + ...state, + ...getInitialStateOverrides(), + }, messenger, - extension, - } = opts; - super(); - - this.extension = extension; - this.onInactiveTimeout = onInactiveTimeout || (() => undefined); - this.store = new ObservableStore( - getDefaultAppStateControllerState(initState), - ); - this.timer = null; + }); + + this.#extension = extension; + this.#onInactiveTimeout = onInactiveTimeout || (() => undefined); + this.#timer = null; this.isUnlocked = isUnlocked; this.waitingForUnlock = []; - addUnlockListener(this.handleUnlock.bind(this)); + addUnlockListener(this.#handleUnlock.bind(this)); messenger.subscribe( 'PreferencesController:stateChange', ({ preferences }: { preferences: Partial }) => { - const currentState = this.store.getState(); + const currentState = this.state; if ( typeof preferences?.autoLockTimeLimit === 'number' && currentState.timeoutMinutes !== preferences.autoLockTimeLimit ) { - this._setInactiveTimeout(preferences.autoLockTimeLimit); + this.#setInactiveTimeout(preferences.autoLockTimeLimit); } }, ); @@ -251,24 +415,17 @@ export class AppStateController extends EventEmitter { messenger.subscribe( 'KeyringController:qrKeyringStateChange', (qrHardware: Json) => - this.store.updateState({ - qrHardware, + this.update((currentState) => { + // @ts-expect-error this is caused by a bug in Immer, not being able to handle recursive types like Json + currentState.qrHardware = qrHardware; }), ); const { preferences } = messenger.call('PreferencesController:getState'); if (typeof preferences.autoLockTimeLimit === 'number') { - this._setInactiveTimeout(preferences.autoLockTimeLimit); + this.#setInactiveTimeout(preferences.autoLockTimeLimit); } - this.messagingSystem = messenger; - this.messagingSystem.registerActionHandler( - 'AppStateController:getState', - () => this.store.getState(), - ); - this.store.subscribe((state: AppStateControllerState) => { - this.messagingSystem.publish('AppStateController:stateChange', state, []); - }); this.#approvalRequestId = null; } @@ -286,7 +443,7 @@ export class AppStateController extends EventEmitter { if (this.isUnlocked()) { resolve(); } else { - this.waitForUnlock(resolve, shouldShowUnlockRequest); + this.#waitForUnlock(resolve, shouldShowUnlockRequest); } }); } @@ -300,26 +457,26 @@ export class AppStateController extends EventEmitter { * @param shouldShowUnlockRequest - Whether the extension notification * popup should be opened. */ - waitForUnlock(resolve: () => void, shouldShowUnlockRequest: boolean): void { + #waitForUnlock(resolve: () => void, shouldShowUnlockRequest: boolean): void { this.waitingForUnlock.push({ resolve }); - this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); + this.messagingSystem.publish('AppStateController:unlockChange'); if (shouldShowUnlockRequest) { - this._requestApproval(); + this.#requestApproval(); } } /** * Drains the waitingForUnlock queue, resolving all the related Promises. */ - handleUnlock(): void { + #handleUnlock(): void { if (this.waitingForUnlock.length > 0) { while (this.waitingForUnlock.length > 0) { this.waitingForUnlock.shift()?.resolve(); } - this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); + this.messagingSystem.publish('AppStateController:unlockChange'); } - this._acceptApproval(); + this.#acceptApproval(); } /** @@ -330,8 +487,8 @@ export class AppStateController extends EventEmitter { setDefaultHomeActiveTabName( defaultHomeActiveTabName: AccountOverviewTabKey | null, ): void { - this.store.updateState({ - defaultHomeActiveTabName, + this.update((state) => { + state.defaultHomeActiveTabName = defaultHomeActiveTabName; }); } @@ -339,8 +496,8 @@ export class AppStateController extends EventEmitter { * Record that the user has seen the connected status info popover */ setConnectedStatusPopoverHasBeenShown(): void { - this.store.updateState({ - connectedStatusPopoverHasBeenShown: true, + this.update((state) => { + state.connectedStatusPopoverHasBeenShown = true; }); } @@ -348,38 +505,87 @@ export class AppStateController extends EventEmitter { * Record that the user has been shown the recovery phrase reminder. */ setRecoveryPhraseReminderHasBeenShown(): void { - this.store.updateState({ - recoveryPhraseReminderHasBeenShown: true, + this.update((state) => { + state.recoveryPhraseReminderHasBeenShown = true; }); } setSurveyLinkLastClickedOrClosed(time: number): void { - this.store.updateState({ - surveyLinkLastClickedOrClosed: time, + this.update((state) => { + state.surveyLinkLastClickedOrClosed = time; }); } setOnboardingDate(): void { - this.store.updateState({ - onboardingDate: Date.now(), + this.update((state) => { + state.onboardingDate = Date.now(); }); } setLastViewedUserSurvey(id: number) { - this.store.updateState({ - lastViewedUserSurvey: id, + this.update((state) => { + state.lastViewedUserSurvey = id; }); } setNewPrivacyPolicyToastClickedOrClosed(): void { - this.store.updateState({ - newPrivacyPolicyToastClickedOrClosed: true, + this.update((state) => { + state.newPrivacyPolicyToastClickedOrClosed = true; }); } setNewPrivacyPolicyToastShownDate(time: number): void { - this.store.updateState({ - newPrivacyPolicyToastShownDate: time, + this.update((state) => { + state.newPrivacyPolicyToastShownDate = time; + }); + } + + /** + * Updates slides by adding new slides that don't already exist in state + * + * @param slides - Array of new slides to add + */ + updateSlides(slides: CarouselSlide[]): void { + this.update((state) => { + const currentSlides = state.slides || []; + + // Updates the undismissable property for slides that already exist in state + const updatedCurrentSlides = currentSlides.map((currentSlide) => { + const matchingNewSlide = slides.find((s) => s.id === currentSlide.id); + if (matchingNewSlide) { + return { + ...currentSlide, + undismissable: matchingNewSlide.undismissable, + }; + } + return currentSlide; + }); + + // Adds new slides that don't already exist in state + const newSlides = slides.filter((newSlide) => { + return !currentSlides.some( + (currentSlide) => currentSlide.id === newSlide.id, + ); + }); + + state.slides = [...updatedCurrentSlides, ...newSlides]; + }); + } + + /** + * Marks a slide as dismissed by ID + * + * @param id - ID of the slide to dismiss + */ + removeSlide(id: string): void { + this.update((state) => { + const slides = state.slides || []; + state.slides = slides.map((slide) => { + if (slide.id === id) { + return { ...slide, dismissed: true }; + } + return slide; + }); }); } @@ -389,8 +595,8 @@ export class AppStateController extends EventEmitter { * @param lastShown - timestamp when user was last shown the reminder. */ setRecoveryPhraseReminderLastShown(lastShown: number): void { - this.store.updateState({ - recoveryPhraseReminderLastShown: lastShown, + this.update((state) => { + state.recoveryPhraseReminderLastShown = lastShown; }); } @@ -400,8 +606,8 @@ export class AppStateController extends EventEmitter { * @param lastAgreed - timestamp when user last accepted the terms of use */ setTermsOfUseLastAgreed(lastAgreed: number): void { - this.store.updateState({ - termsOfUseLastAgreed: lastAgreed, + this.update((state) => { + state.termsOfUseLastAgreed = lastAgreed; }); } @@ -412,8 +618,8 @@ export class AppStateController extends EventEmitter { * @param shown - shown status */ setSnapsInstallPrivacyWarningShownStatus(shown: boolean): void { - this.store.updateState({ - snapsInstallPrivacyWarningShown: shown, + this.update((state) => { + state.snapsInstallPrivacyWarningShown = shown; }); } @@ -423,8 +629,8 @@ export class AppStateController extends EventEmitter { * @param lastShown - Timestamp (in milliseconds) of when the user was last shown the warning. */ setOutdatedBrowserWarningLastShown(lastShown: number): void { - this.store.updateState({ - outdatedBrowserWarningLastShown: lastShown, + this.update((state) => { + state.outdatedBrowserWarningLastShown = lastShown; }); } @@ -432,7 +638,7 @@ export class AppStateController extends EventEmitter { * Sets the last active time to the current time. */ setLastActiveTime(): void { - this._resetTimer(); + this.#resetTimer(); } /** @@ -440,12 +646,12 @@ export class AppStateController extends EventEmitter { * * @param timeoutMinutes - The inactive timeout in minutes. */ - private _setInactiveTimeout(timeoutMinutes: number): void { - this.store.updateState({ - timeoutMinutes, + #setInactiveTimeout(timeoutMinutes: number): void { + this.update((state) => { + state.timeoutMinutes = timeoutMinutes; }); - this._resetTimer(); + this.#resetTimer(); } /** @@ -455,13 +661,13 @@ export class AppStateController extends EventEmitter { * timer will not be created. * */ - private _resetTimer(): void { - const { timeoutMinutes } = this.store.getState(); + #resetTimer(): void { + const { timeoutMinutes } = this.state; - if (this.timer) { - clearTimeout(this.timer); + if (this.#timer) { + clearTimeout(this.#timer); } else if (isManifestV3) { - this.extension.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM); + this.#extension.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM); } if (!timeoutMinutes) { @@ -478,21 +684,21 @@ export class AppStateController extends EventEmitter { const timeoutToSet = Number(timeoutMinutes); if (isManifestV3) { - this.extension.alarms.create(AUTO_LOCK_TIMEOUT_ALARM, { + this.#extension.alarms.create(AUTO_LOCK_TIMEOUT_ALARM, { delayInMinutes: timeoutToSet, periodInMinutes: timeoutToSet, }); - this.extension.alarms.onAlarm.addListener( + this.#extension.alarms.onAlarm.addListener( (alarmInfo: { name: string }) => { if (alarmInfo.name === AUTO_LOCK_TIMEOUT_ALARM) { - this.onInactiveTimeout(); - this.extension.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM); + this.#onInactiveTimeout(); + this.#extension.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM); } }, ); } else { - this.timer = setTimeout( - () => this.onInactiveTimeout(), + this.#timer = setTimeout( + () => this.#onInactiveTimeout(), timeoutToSet * MINUTE, ); } @@ -505,7 +711,9 @@ export class AppStateController extends EventEmitter { * @param browser */ setBrowserEnvironment(os: string, browser: string): void { - this.store.updateState({ browserEnvironment: { os, browser } }); + this.update((state) => { + state.browserEnvironment = { os, browser }; + }); } /** @@ -538,9 +746,8 @@ export class AppStateController extends EventEmitter { pollingToken: string, pollingTokenType: PollingTokenType, ) { - const currentTokens: string[] = this.store.getState()[pollingTokenType]; - this.store.updateState({ - [pollingTokenType]: [...currentTokens, pollingToken], + this.update((state) => { + state[pollingTokenType].push(pollingToken); }); } @@ -558,12 +765,12 @@ export class AppStateController extends EventEmitter { pollingTokenType.toString() !== POLLING_TOKEN_ENVIRONMENT_TYPES[ENVIRONMENT_TYPE_BACKGROUND] ) { - const currentTokens: string[] = this.store.getState()[pollingTokenType]; + const currentTokens: string[] = this.state[pollingTokenType]; if (this.#isValidPollingTokenType(pollingTokenType)) { - this.store.updateState({ - [pollingTokenType]: currentTokens.filter( + this.update((state) => { + state[pollingTokenType] = currentTokens.filter( (token: string) => token !== pollingToken, - ), + ); }); } } @@ -589,10 +796,10 @@ export class AppStateController extends EventEmitter { * clears all pollingTokens */ clearPollingTokens(): void { - this.store.updateState({ - popupGasPollTokens: [], - notificationGasPollTokens: [], - fullScreenGasPollTokens: [], + this.update((state) => { + state.popupGasPollTokens = []; + state.notificationGasPollTokens = []; + state.fullScreenGasPollTokens = []; }); } @@ -602,7 +809,9 @@ export class AppStateController extends EventEmitter { * @param showTestnetMessageInDropdown */ setShowTestnetMessageInDropdown(showTestnetMessageInDropdown: boolean): void { - this.store.updateState({ showTestnetMessageInDropdown }); + this.update((state) => { + state.showTestnetMessageInDropdown = showTestnetMessageInDropdown; + }); } /** @@ -611,7 +820,9 @@ export class AppStateController extends EventEmitter { * @param showBetaHeader */ setShowBetaHeader(showBetaHeader: boolean): void { - this.store.updateState({ showBetaHeader }); + this.update((state) => { + state.showBetaHeader = showBetaHeader; + }); } /** @@ -620,7 +831,9 @@ export class AppStateController extends EventEmitter { * @param showPermissionsTour */ setShowPermissionsTour(showPermissionsTour: boolean): void { - this.store.updateState({ showPermissionsTour }); + this.update((state) => { + state.showPermissionsTour = showPermissionsTour; + }); } /** @@ -629,7 +842,9 @@ export class AppStateController extends EventEmitter { * @param showNetworkBanner */ setShowNetworkBanner(showNetworkBanner: boolean): void { - this.store.updateState({ showNetworkBanner }); + this.update((state) => { + state.showNetworkBanner = showNetworkBanner; + }); } /** @@ -638,7 +853,9 @@ export class AppStateController extends EventEmitter { * @param showAccountBanner */ setShowAccountBanner(showAccountBanner: boolean): void { - this.store.updateState({ showAccountBanner }); + this.update((state) => { + state.showAccountBanner = showAccountBanner; + }); } /** @@ -647,7 +864,9 @@ export class AppStateController extends EventEmitter { * @param currentExtensionPopupId */ setCurrentExtensionPopupId(currentExtensionPopupId: number): void { - this.store.updateState({ currentExtensionPopupId }); + this.update((state) => { + state.currentExtensionPopupId = currentExtensionPopupId; + }); } /** @@ -659,14 +878,18 @@ export class AppStateController extends EventEmitter { setSwitchedNetworkDetails( switchedNetworkDetails: { origin: string; networkClientId: string } | null, ): void { - this.store.updateState({ switchedNetworkDetails }); + this.update((state) => { + state.switchedNetworkDetails = switchedNetworkDetails; + }); } /** * Clears the switched network details in state */ clearSwitchedNetworkDetails(): void { - this.store.updateState({ switchedNetworkDetails: null }); + this.update((state) => { + state.switchedNetworkDetails = null; + }); } /** @@ -678,9 +901,9 @@ export class AppStateController extends EventEmitter { setSwitchedNetworkNeverShowMessage( switchedNetworkNeverShowMessage: boolean, ): void { - this.store.updateState({ - switchedNetworkDetails: null, - switchedNetworkNeverShowMessage, + this.update((state) => { + state.switchedNetworkDetails = null; + state.switchedNetworkNeverShowMessage = switchedNetworkNeverShowMessage; }); } @@ -690,7 +913,9 @@ export class AppStateController extends EventEmitter { * @param trezorModel - The Trezor model. */ setTrezorModel(trezorModel: string | null): void { - this.store.updateState({ trezorModel }); + this.update((state) => { + state.trezorModel = trezorModel; + }); } /** @@ -699,24 +924,11 @@ export class AppStateController extends EventEmitter { * @param nftsDropdownState */ updateNftDropDownState(nftsDropdownState: Json): void { - this.store.updateState({ - nftsDropdownState, + this.update((state) => { + state.nftsDropdownState = nftsDropdownState; }); } - /** - * Updates the array of the first time used networks - * - * @param chainId - */ - setFirstTimeUsedNetwork(chainId: string): void { - const currentState = this.store.getState(); - const { usedNetworks } = currentState; - usedNetworks[chainId] = true; - - this.store.updateState({ usedNetworks }); - } - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) /** * Set the interactive replacement token with a url and the old refresh token @@ -732,11 +944,11 @@ export class AppStateController extends EventEmitter { url: string; oldRefreshToken: string; }): void { - this.store.updateState({ - interactiveReplacementToken: { + this.update((state) => { + state.interactiveReplacementToken = { url, oldRefreshToken, - }, + }; }); } @@ -754,14 +966,14 @@ export class AppStateController extends EventEmitter { fromAddress: string; custodyId: string; }): void { - this.store.updateState({ - custodianDeepLink: { fromAddress, custodyId }, + this.update((state) => { + state.custodianDeepLink = { fromAddress, custodyId }; }); } setNoteToTraderMessage(message: string): void { - this.store.updateState({ - noteToTraderMessage: message, + this.update((state) => { + state.noteToTraderMessage = message; }); } @@ -770,23 +982,17 @@ export class AppStateController extends EventEmitter { getSignatureSecurityAlertResponse( securityAlertId: string, ): SecurityAlertResponse { - return this.store.getState().signatureSecurityAlertResponses[ - securityAlertId - ]; + return this.state.signatureSecurityAlertResponses[securityAlertId]; } addSignatureSecurityAlertResponse( securityAlertResponse: SecurityAlertResponse, ): void { - const currentState = this.store.getState(); - const { signatureSecurityAlertResponses } = currentState; if (securityAlertResponse.securityAlertId) { - this.store.updateState({ - signatureSecurityAlertResponses: { - ...signatureSecurityAlertResponses, - [String(securityAlertResponse.securityAlertId)]: - securityAlertResponse, - }, + this.update((state) => { + state.signatureSecurityAlertResponses[ + String(securityAlertResponse.securityAlertId) + ] = securityAlertResponse; }); } } @@ -797,8 +1003,8 @@ export class AppStateController extends EventEmitter { * @param currentPopupId */ setCurrentPopupId(currentPopupId: number): void { - this.store.updateState({ - currentPopupId, + this.update((state) => { + state.currentPopupId = currentPopupId; }); } @@ -808,7 +1014,7 @@ export class AppStateController extends EventEmitter { getLastInteractedConfirmationInfo(): | LastInteractedConfirmationInfo | undefined { - return this.store.getState().lastInteractedConfirmationInfo; + return this.state.lastInteractedConfirmationInfo; } /** @@ -819,8 +1025,8 @@ export class AppStateController extends EventEmitter { setLastInteractedConfirmationInfo( lastInteractedConfirmationInfo: LastInteractedConfirmationInfo | undefined, ): void { - this.store.updateState({ - lastInteractedConfirmationInfo, + this.update((state) => { + state.lastInteractedConfirmationInfo = lastInteractedConfirmationInfo; }); } @@ -828,10 +1034,10 @@ export class AppStateController extends EventEmitter { * A getter to retrieve currentPopupId saved in the appState */ getCurrentPopupId(): number | undefined { - return this.store.getState().currentPopupId; + return this.state.currentPopupId; } - private _requestApproval(): void { + #requestApproval(): void { // If we already have a pending request this is a no-op if (this.#approvalRequestId) { return; @@ -854,12 +1060,7 @@ export class AppStateController extends EventEmitter { }); } - // Override emit method to provide strong typing for events - emit(event: string) { - return super.emit(event); - } - - private _acceptApproval(): void { + #acceptApproval(): void { if (!this.#approvalRequestId) { return; } diff --git a/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap b/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap index ebd3a938822e..8bbeab6356d9 100644 --- a/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap +++ b/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap @@ -2,11 +2,13 @@ exports[`BridgeStatusController constructor rehydrates the tx history state 1`] = ` { - "0xsrcTxHash1": { + "bridgeTxMetaId1": { "account": "0xaccount1", "estimatedProcessingTimeInSeconds": 15, "initialDestAssetBalance": undefined, - "pricingData": undefined, + "pricingData": { + "amountSent": "1.234", + }, "quote": { "bridgeId": "lifi", "bridges": [ @@ -102,17 +104,20 @@ exports[`BridgeStatusController constructor rehydrates the tx history state 1`] "status": "PENDING", }, "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC", + "txMetaId": "bridgeTxMetaId1", }, } `; exports[`BridgeStatusController startPollingForBridgeTxStatus sets the inital tx history state 1`] = ` { - "0xsrcTxHash1": { + "bridgeTxMetaId1": { "account": "0xaccount1", "estimatedProcessingTimeInSeconds": 15, "initialDestAssetBalance": undefined, - "pricingData": undefined, + "pricingData": { + "amountSent": "1.234", + }, "quote": { "bridgeId": "lifi", "bridges": [ @@ -208,6 +213,7 @@ exports[`BridgeStatusController startPollingForBridgeTxStatus sets the inital tx "status": "PENDING", }, "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC", + "txMetaId": "bridgeTxMetaId1", }, } `; diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts index 3890f27f7f65..0a8e331b2767 100644 --- a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts @@ -1,271 +1,19 @@ import { flushPromises } from '../../../../test/lib/timer-helpers'; import { Numeric } from '../../../../shared/modules/Numeric'; -import { - StatusTypes, - ActionTypes, - BridgeId, -} from '../../../../shared/types/bridge-status'; import BridgeStatusController from './bridge-status-controller'; import { BridgeStatusControllerMessenger } from './types'; import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from './constants'; import * as bridgeStatusUtils from './utils'; +import { + MockStatusResponse, + MockTxHistory, + getMockStartPollingForBridgeTxStatusArgs, +} from './mocks'; const EMPTY_INIT_STATE = { bridgeStatusState: DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, }; -const getMockQuote = ({ srcChainId = 42161, destChainId = 10 } = {}) => ({ - requestId: '197c402f-cb96-4096-9f8c-54aed84ca776', - srcChainId, - srcTokenAmount: '991250000000000', - srcAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - destChainId, - destTokenAmount: '990654755978612', - destAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: destChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.63', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - feeData: { - metabridge: { - amount: '8750000000000', - asset: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - }, - bridgeId: 'lifi', - bridges: ['across'], - steps: [ - { - action: 'bridge' as ActionTypes, - srcChainId, - destChainId, - protocol: { - name: 'across', - displayName: 'Across', - icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png', - }, - srcAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - destAsset: { - address: '0x0000000000000000000000000000000000000000', - chainId: destChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.63', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - srcAmount: '991250000000000', - destAmount: '990654755978612', - }, - ], -}); - -const getMockStartPollingForBridgeTxStatusArgs = ({ - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, -} = {}) => ({ - statusRequest: { - bridgeId: 'lifi', - srcTxHash, - bridge: 'across', - srcChainId, - destChainId, - quote: getMockQuote({ srcChainId, destChainId }), - refuel: false, - }, - quoteResponse: { - quote: getMockQuote({ srcChainId, destChainId }), - trade: { - chainId: srcChainId, - to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - from: account, - value: '0x038d7ea4c68000', - data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', - gasLimit: 282915, - }, - approval: null, - estimatedProcessingTimeInSeconds: 15, - }, - startTime: 1729964825189, - slippagePercentage: 0, - pricingData: undefined, - initialDestAssetBalance: undefined, - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', -}); - -const MockStatusResponse = { - getPending: ({ - srcTxHash = '0xsrcTxHash1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - status: 'PENDING' as StatusTypes, - srcChain: { - chainId: srcChainId, - txHash: srcTxHash, - amount: '991250000000000', - token: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2518.47', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - destChain: { - chainId: destChainId, - token: {}, - }, - }), - getComplete: ({ - srcTxHash = '0xsrcTxHash1', - destTxHash = '0xdestTxHash1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - status: 'COMPLETE' as StatusTypes, - isExpectedToken: true, - bridge: 'across' as BridgeId, - srcChain: { - chainId: srcChainId, - txHash: srcTxHash, - amount: '991250000000000', - token: { - address: '0x0000000000000000000000000000000000000000', - chainId: srcChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.7', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - destChain: { - chainId: destChainId, - txHash: destTxHash, - amount: '990654755978611', - token: { - address: '0x0000000000000000000000000000000000000000', - chainId: destChainId, - symbol: 'ETH', - decimals: 18, - name: 'ETH', - coinKey: 'ETH', - logoURI: - 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - priceUSD: '2478.63', - icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', - }, - }, - }), -}; - -const MockTxHistory = { - getInit: ({ - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - [srcTxHash]: { - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - }, - }), - getPending: ({ - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - [srcTxHash]: { - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - status: MockStatusResponse.getPending({ - srcTxHash, - srcChainId, - }), - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - }, - }), - getComplete: ({ - srcTxHash = '0xsrcTxHash1', - account = '0xaccount1', - srcChainId = 42161, - destChainId = 10, - } = {}) => ({ - [srcTxHash]: { - quote: getMockQuote({ srcChainId, destChainId }), - startTime: 1729964825189, - estimatedProcessingTimeInSeconds: 15, - slippagePercentage: 0, - account, - status: MockStatusResponse.getComplete({ srcTxHash }), - targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', - }, - }), -}; - const getMessengerMock = ({ account = '0xaccount1', srcChainId = 42161, @@ -298,10 +46,7 @@ const executePollingWithPendingStatus = async () => { const bridgeStatusController = new BridgeStatusController({ messenger: getMessengerMock(), }); - const startPollingByNetworkClientIdSpy = jest.spyOn( - bridgeStatusController, - 'startPollingByNetworkClientId', - ); + const startPollingSpy = jest.spyOn(bridgeStatusController, 'startPolling'); const fetchBridgeTxStatusSpy = jest.spyOn( bridgeStatusUtils, 'fetchBridgeTxStatus', @@ -319,7 +64,7 @@ const executePollingWithPendingStatus = async () => { return { bridgeStatusController, - startPollingByNetworkClientIdSpy, + startPollingSpy, fetchBridgeTxStatusSpy, }; }; @@ -398,12 +143,12 @@ describe('BridgeStatusController', () => { it('starts polling and updates the tx history when the status response is received', async () => { const { bridgeStatusController, - startPollingByNetworkClientIdSpy, + startPollingSpy, fetchBridgeTxStatusSpy, } = await executePollingWithPendingStatus(); // Assertions - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(startPollingSpy).toHaveBeenCalledTimes(1); expect(fetchBridgeTxStatusSpy).toHaveBeenCalled(); expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( MockTxHistory.getPending(), @@ -516,6 +261,7 @@ describe('BridgeStatusController', () => { // Start polling for 0xaccount2 bridgeStatusController.startPollingForBridgeTxStatus( getMockStartPollingForBridgeTxStatusArgs({ + txMetaId: 'bridgeTxMetaId2', srcTxHash: '0xsrcTxHash2', account: '0xaccount2', }), @@ -526,10 +272,10 @@ describe('BridgeStatusController', () => { // Check that both accounts have a tx history entry expect( bridgeStatusController.state.bridgeStatusState.txHistory, - ).toHaveProperty('0xsrcTxHash1'); + ).toHaveProperty('bridgeTxMetaId1'); expect( bridgeStatusController.state.bridgeStatusState.txHistory, - ).toHaveProperty('0xsrcTxHash2'); + ).toHaveProperty('bridgeTxMetaId2'); // Wipe the status for 1 account only bridgeStatusController.wipeBridgeStatus({ @@ -589,6 +335,7 @@ describe('BridgeStatusController', () => { getMockStartPollingForBridgeTxStatusArgs({ account: '0xaccount1', srcTxHash: '0xsrcTxHash1', + txMetaId: 'bridgeTxMetaId1', srcChainId: 42161, destChainId: 1, }), @@ -601,6 +348,7 @@ describe('BridgeStatusController', () => { getMockStartPollingForBridgeTxStatusArgs({ account: '0xaccount1', srcTxHash: '0xsrcTxHash2', + txMetaId: 'bridgeTxMetaId2', srcChainId: 10, destChainId: 123, }), @@ -610,20 +358,20 @@ describe('BridgeStatusController', () => { // Check we have a tx history entry for each chainId expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.srcChainId, ).toEqual(42161); expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.destChainId, ).toEqual(1); expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.srcChainId, ).toEqual(10); expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.destChainId, ).toEqual(123); @@ -684,6 +432,7 @@ describe('BridgeStatusController', () => { getMockStartPollingForBridgeTxStatusArgs({ account: '0xaccount1', srcTxHash: '0xsrcTxHash1', + txMetaId: 'bridgeTxMetaId1', srcChainId: 42161, destChainId: 1, }), @@ -696,6 +445,7 @@ describe('BridgeStatusController', () => { getMockStartPollingForBridgeTxStatusArgs({ account: '0xaccount1', srcTxHash: '0xsrcTxHash2', + txMetaId: 'bridgeTxMetaId2', srcChainId: 10, destChainId: 123, }), @@ -705,20 +455,20 @@ describe('BridgeStatusController', () => { // Check we have a tx history entry for each chainId expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.srcChainId, ).toEqual(42161); expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId1 .quote.destChainId, ).toEqual(1); expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.srcChainId, ).toEqual(10); expect( - bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + bridgeStatusController.state.bridgeStatusState.txHistory.bridgeTxMetaId2 .quote.destChainId, ).toEqual(123); diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.ts index 18010ae0de3d..841bf9868787 100644 --- a/app/scripts/controllers/bridge-status/bridge-status-controller.ts +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.ts @@ -3,10 +3,9 @@ import { StaticIntervalPollingController } from '@metamask/polling-controller'; import { Hex } from '@metamask/utils'; // eslint-disable-next-line import/no-restricted-paths import { - StartPollingForBridgeTxStatusArgs, - StatusRequest, StatusTypes, BridgeStatusControllerState, + StartPollingForBridgeTxStatusArgsSerialized, } from '../../../../shared/types/bridge-status'; import { decimalToPrefixedHex } from '../../../../shared/modules/conversion.utils'; import { @@ -15,7 +14,7 @@ import { REFRESH_INTERVAL_MS, } from './constants'; import { BridgeStatusControllerMessenger } from './types'; -import { fetchBridgeTxStatus } from './utils'; +import { fetchBridgeTxStatus, getStatusRequestWithSrcTxHash } from './utils'; const metadata: StateMetadata<{ bridgeStatusState: BridgeStatusControllerState; @@ -28,16 +27,19 @@ const metadata: StateMetadata<{ }, }; -type SrcTxHash = string; +/** The input to start polling for the {@link BridgeStatusController} */ +type BridgeStatusPollingInput = FetchBridgeTxStatusArgs; + +type SrcTxMetaId = string; export type FetchBridgeTxStatusArgs = { - statusRequest: StatusRequest; + bridgeTxMetaId: string; }; -export default class BridgeStatusController extends StaticIntervalPollingController< +export default class BridgeStatusController extends StaticIntervalPollingController()< typeof BRIDGE_STATUS_CONTROLLER_NAME, { bridgeStatusState: BridgeStatusControllerState }, BridgeStatusControllerMessenger > { - #pollingTokensBySrcTxHash: Record = {}; + #pollingTokensByTxMetaId: Record = {}; constructor({ messenger, @@ -123,115 +125,115 @@ export default class BridgeStatusController extends StaticIntervalPollingControl const historyItems = Object.values(bridgeStatusState.txHistory); const incompleteHistoryItems = historyItems .filter( - (historyItem) => historyItem.status.status !== StatusTypes.COMPLETE, + (historyItem) => + historyItem.status.status === StatusTypes.PENDING || + historyItem.status.status === StatusTypes.UNKNOWN, ) .filter((historyItem) => { // Check if we are already polling this tx, if so, skip restarting polling for that - const srcTxHash = historyItem.status.srcChain.txHash; - const pollingToken = this.#pollingTokensBySrcTxHash[srcTxHash]; + const srcTxMetaId = historyItem.txMetaId; + const pollingToken = this.#pollingTokensByTxMetaId[srcTxMetaId]; return !pollingToken; }); incompleteHistoryItems.forEach((historyItem) => { - const statusRequest = { - bridgeId: historyItem.quote.bridgeId, - srcTxHash: historyItem.status.srcChain.txHash, - bridge: historyItem.quote.bridges[0], - srcChainId: historyItem.quote.srcChainId, - destChainId: historyItem.quote.destChainId, - quote: historyItem.quote, - refuel: Boolean(historyItem.quote.refuel), - }; - - const hexSourceChainId = decimalToPrefixedHex(statusRequest.srcChainId); - const networkClientId = this.messagingSystem.call( - 'NetworkController:findNetworkClientIdByChainId', - hexSourceChainId, - ); + const bridgeTxMetaId = historyItem.txMetaId; - // We manually call startPollingByNetworkClientId() here rather than go through startPollingForBridgeTxStatus() + // We manually call startPolling() here rather than go through startPollingForBridgeTxStatus() // because we don't want to overwrite the existing historyItem in state - const options: FetchBridgeTxStatusArgs = { statusRequest }; - this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash] = - this.startPollingByNetworkClientId(networkClientId, options); + this.#pollingTokensByTxMetaId[bridgeTxMetaId] = this.startPolling({ + bridgeTxMetaId, + }); }); }; startPollingForBridgeTxStatus = ( - startPollingForBridgeTxStatusArgs: StartPollingForBridgeTxStatusArgs, + startPollingForBridgeTxStatusArgs: StartPollingForBridgeTxStatusArgsSerialized, ) => { const { + bridgeTxMeta, statusRequest, quoteResponse, startTime, slippagePercentage, - pricingData, initialDestAssetBalance, targetContractAddress, } = startPollingForBridgeTxStatusArgs; - const hexSourceChainId = decimalToPrefixedHex(statusRequest.srcChainId); - const { bridgeStatusState } = this.state; const { address: account } = this.#getSelectedAccount(); // Write all non-status fields to state so we can reference the quote in Activity list without the Bridge API // We know it's in progress but not the exact status yet + const txHistoryItem = { + txMetaId: bridgeTxMeta.id, + quote: quoteResponse.quote, + startTime, + estimatedProcessingTimeInSeconds: + quoteResponse.estimatedProcessingTimeInSeconds, + slippagePercentage, + pricingData: { + amountSent: quoteResponse.sentAmount.amount, + }, + initialDestAssetBalance, + targetContractAddress, + account, + status: { + // We always have a PENDING status when we start polling for a tx, don't need the Bridge API for that + // Also we know the bare minimum fields for status at this point in time + status: StatusTypes.PENDING, + srcChain: { + chainId: statusRequest.srcChainId, + txHash: statusRequest.srcTxHash, + }, + }, + }; this.update((_state) => { _state.bridgeStatusState = { ...bridgeStatusState, txHistory: { ...bridgeStatusState.txHistory, - [statusRequest.srcTxHash]: { - quote: quoteResponse.quote, - startTime, - estimatedProcessingTimeInSeconds: - quoteResponse.estimatedProcessingTimeInSeconds, - slippagePercentage, - pricingData, - initialDestAssetBalance, - targetContractAddress, - account, - status: { - // We always have a PENDING status when we start polling for a tx, don't need the Bridge API for that - // Also we know the bare minimum fields for status at this point in time - status: StatusTypes.PENDING, - srcChain: { - chainId: statusRequest.srcChainId, - txHash: statusRequest.srcTxHash, - }, - }, - }, + // Use the txMeta.id as the key so we can reference the txMeta in TransactionController + [bridgeTxMeta.id]: txHistoryItem, }, }; }); - const networkClientId = this.messagingSystem.call( - 'NetworkController:findNetworkClientIdByChainId', - hexSourceChainId, - ); - this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash] = - this.startPollingByNetworkClientId(networkClientId, { statusRequest }); + this.#pollingTokensByTxMetaId[bridgeTxMeta.id] = this.startPolling({ + bridgeTxMetaId: bridgeTxMeta.id, + }); }; - // This will be called after you call this.startPollingByNetworkClientId() - // The args passed in are the args you passed in to startPollingByNetworkClientId() - _executePoll = async ( - _networkClientId: string, - fetchBridgeTxStatusArgs: FetchBridgeTxStatusArgs, - ) => { - await this.#fetchBridgeTxStatus(fetchBridgeTxStatusArgs); + // This will be called after you call this.startPolling() + // The args passed in are the args you passed in to startPolling() + _executePoll = async (pollingInput: BridgeStatusPollingInput) => { + await this.#fetchBridgeTxStatus(pollingInput); }; #getSelectedAccount() { return this.messagingSystem.call('AccountsController:getSelectedAccount'); } - #fetchBridgeTxStatus = async ({ statusRequest }: FetchBridgeTxStatusArgs) => { + #fetchBridgeTxStatus = async ({ + bridgeTxMetaId, + }: FetchBridgeTxStatusArgs) => { const { bridgeStatusState } = this.state; try { // We try here because we receive 500 errors from Bridge API if we try to fetch immediately after submitting the source tx // Oddly mostly happens on Optimism, never on Arbitrum. By the 2nd fetch, the Bridge API responds properly. + // Also srcTxHash may not be available immediately for STX, so we don't want to fetch in those cases + const historyItem = bridgeStatusState.txHistory[bridgeTxMetaId]; + const srcTxHash = this.#getSrcTxHash(bridgeTxMetaId); + if (!srcTxHash) { + return; + } + + this.#updateSrcTxHash(bridgeTxMetaId, srcTxHash); + + const statusRequest = getStatusRequestWithSrcTxHash( + historyItem.quote, + srcTxHash, + ); const status = await fetchBridgeTxStatus(statusRequest); // No need to purge these on network change or account change, TransactionController does not purge either. @@ -240,13 +242,13 @@ export default class BridgeStatusController extends StaticIntervalPollingControl // First stab at this will not stop polling when you are on a different account this.update((_state) => { const bridgeHistoryItem = - _state.bridgeStatusState.txHistory[statusRequest.srcTxHash]; + _state.bridgeStatusState.txHistory[bridgeTxMetaId]; _state.bridgeStatusState = { ...bridgeStatusState, txHistory: { ...bridgeStatusState.txHistory, - [statusRequest.srcTxHash]: { + [bridgeTxMetaId]: { ...bridgeHistoryItem, status, }, @@ -254,8 +256,7 @@ export default class BridgeStatusController extends StaticIntervalPollingControl }; }); - const pollingToken = - this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash]; + const pollingToken = this.#pollingTokensByTxMetaId[bridgeTxMetaId]; if (status.status === StatusTypes.COMPLETE && pollingToken) { this.stopPollingByPollingToken(pollingToken); } @@ -264,43 +265,86 @@ export default class BridgeStatusController extends StaticIntervalPollingControl } }; + #getSrcTxHash = (bridgeTxMetaId: string): string | undefined => { + const { bridgeStatusState } = this.state; + // Prefer the srcTxHash from bridgeStatusState so we don't have to l ook up in TransactionController + // But it is possible to have bridgeHistoryItem in state without the srcTxHash yet when it is an STX + const srcTxHash = + bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain.txHash; + + if (srcTxHash) { + return srcTxHash; + } + + // Look up in TransactionController if txMeta has been updated with the srcTxHash + const txControllerState = this.messagingSystem.call( + 'TransactionController:getState', + ); + const txMeta = txControllerState.transactions.find( + (tx) => tx.id === bridgeTxMetaId, + ); + return txMeta?.hash; + }; + + #updateSrcTxHash = (bridgeTxMetaId: string, srcTxHash: string) => { + const { bridgeStatusState } = this.state; + if (bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain.txHash) { + return; + } + + this.update((_state) => { + _state.bridgeStatusState = { + ...bridgeStatusState, + txHistory: { + ...bridgeStatusState.txHistory, + [bridgeTxMetaId]: { + ...bridgeStatusState.txHistory[bridgeTxMetaId], + status: { + ...bridgeStatusState.txHistory[bridgeTxMetaId].status, + srcChain: { + ...bridgeStatusState.txHistory[bridgeTxMetaId].status.srcChain, + txHash: srcTxHash, + }, + }, + }, + }, + }; + }); + }; + // Wipes the bridge status for the given address and chainId - // Will match either source or destination chainId to the selectedChainId + // Will match only source chainId to the selectedChainId #wipeBridgeStatusByChainId = (address: string, selectedChainId: Hex) => { - const sourceTxHashesToDelete = Object.keys( + const sourceTxMetaIdsToDelete = Object.keys( this.state.bridgeStatusState.txHistory, - ).filter((sourceTxHash) => { + ).filter((txMetaId) => { const bridgeHistoryItem = - this.state.bridgeStatusState.txHistory[sourceTxHash]; + this.state.bridgeStatusState.txHistory[txMetaId]; const hexSourceChainId = decimalToPrefixedHex( bridgeHistoryItem.quote.srcChainId, ); - const hexDestChainId = decimalToPrefixedHex( - bridgeHistoryItem.quote.destChainId, - ); return ( bridgeHistoryItem.account === address && - (hexSourceChainId === selectedChainId || - hexDestChainId === selectedChainId) + hexSourceChainId === selectedChainId ); }); - sourceTxHashesToDelete.forEach((sourceTxHash) => { - const pollingToken = this.#pollingTokensBySrcTxHash[sourceTxHash]; + sourceTxMetaIdsToDelete.forEach((sourceTxMetaId) => { + const pollingToken = this.#pollingTokensByTxMetaId[sourceTxMetaId]; if (pollingToken) { this.stopPollingByPollingToken( - this.#pollingTokensBySrcTxHash[sourceTxHash], + this.#pollingTokensByTxMetaId[sourceTxMetaId], ); } }); this.update((_state) => { - _state.bridgeStatusState.txHistory = sourceTxHashesToDelete.reduce( - (acc, sourceTxHash) => { - delete acc[sourceTxHash]; + _state.bridgeStatusState.txHistory = sourceTxMetaIdsToDelete.reduce( + (acc, sourceTxMetaId) => { + delete acc[sourceTxMetaId]; return acc; }, _state.bridgeStatusState.txHistory, diff --git a/app/scripts/controllers/bridge-status/mocks.ts b/app/scripts/controllers/bridge-status/mocks.ts new file mode 100644 index 000000000000..0292fc7ecf9a --- /dev/null +++ b/app/scripts/controllers/bridge-status/mocks.ts @@ -0,0 +1,327 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import { + BridgeId, + StatusResponse, + StatusTypes, + ActionTypes, + StartPollingForBridgeTxStatusArgsSerialized, +} from '../../../../shared/types/bridge-status'; + +export const MockStatusResponse = { + getPending: ({ + srcTxHash = '0xsrcTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'PENDING' as StatusTypes, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2518.47', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + token: {}, + }, + }), + getComplete: ({ + srcTxHash = '0xsrcTxHash1', + destTxHash = '0xdestTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'COMPLETE' as StatusTypes, + isExpectedToken: true, + bridge: 'across' as BridgeId, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + txHash: destTxHash, + amount: '990654755978611', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }), +}; + +let mockFetchBridgeTxStatusCount = 0; +export const mockFetchBridgeTxStatus: () => Promise = () => { + return new Promise((resolve) => { + console.log('HELLO mockFetchBridgeTxStatus', mockFetchBridgeTxStatusCount); + setTimeout(() => { + if (mockFetchBridgeTxStatusCount === 0) { + resolve( + MockStatusResponse.getPending({ + srcTxHash: + '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', + srcChainId: 1, + destChainId: 42161, + }), + ); + } else { + resolve( + MockStatusResponse.getComplete({ + srcTxHash: + '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', + destTxHash: + '0x010e1bffe8288956012e6b6132d7eb3eaf9d0bbf066bd13aae13b973c678508f', + srcChainId: 1, + destChainId: 42161, + }), + ); + } + mockFetchBridgeTxStatusCount += 1; + }, 2000); + }); +}; + +export const getMockQuote = ({ + srcChainId = 42161, + destChainId = 10, +} = {}) => ({ + requestId: '197c402f-cb96-4096-9f8c-54aed84ca776', + srcChainId, + srcTokenAmount: '991250000000000', + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destChainId, + destTokenAmount: '990654755978612', + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + feeData: { + metabridge: { + amount: '8750000000000', + asset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + bridgeId: 'lifi', + bridges: ['across'], + steps: [ + { + action: 'bridge' as ActionTypes, + srcChainId, + destChainId, + protocol: { + name: 'across', + displayName: 'Across', + icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png', + }, + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + srcAmount: '991250000000000', + destAmount: '990654755978612', + }, + ], +}); + +export const getMockStartPollingForBridgeTxStatusArgs = ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, +} = {}): StartPollingForBridgeTxStatusArgsSerialized => ({ + bridgeTxMeta: { + id: txMetaId, + } as TransactionMeta, + statusRequest: { + bridgeId: 'lifi', + srcTxHash, + bridge: 'across', + srcChainId, + destChainId, + quote: getMockQuote({ srcChainId, destChainId }), + refuel: false, + }, + quoteResponse: { + quote: getMockQuote({ srcChainId, destChainId }), + trade: { + chainId: srcChainId, + to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + from: account, + value: '0x038d7ea4c68000', + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', + gasLimit: 282915, + }, + approval: null, + estimatedProcessingTimeInSeconds: 15, + sentAmount: { amount: '1.234', valueInCurrency: null }, + }, + startTime: 1729964825189, + slippagePercentage: 0, + initialDestAssetBalance: undefined, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', +}); + +export const MockTxHistory = { + getInitNoSrcTxHash: ({ + txMetaId = 'bridgeTxMetaId1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + }, + }), + getInit: ({ + txMetaId = 'bridgeTxMetaId1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + }, + }), + getPending: ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getPending({ + srcTxHash, + srcChainId, + }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + }, + }), + getComplete: ({ + txMetaId = 'bridgeTxMetaId1', + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [txMetaId]: { + txMetaId, + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getComplete({ srcTxHash }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + initialDestAssetBalance: undefined, + pricingData: { amountSent: '1.234' }, + }, + }), +}; diff --git a/app/scripts/controllers/bridge-status/types.ts b/app/scripts/controllers/bridge-status/types.ts index 040cd1e0c9bd..d06c3c40b3d8 100644 --- a/app/scripts/controllers/bridge-status/types.ts +++ b/app/scripts/controllers/bridge-status/types.ts @@ -9,6 +9,7 @@ import { NetworkControllerGetStateAction, } from '@metamask/network-controller'; import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; +import { TransactionControllerGetStateAction } from '@metamask/transaction-controller'; import { BridgeStatusAction, BridgeStatusControllerState, @@ -37,11 +38,19 @@ type BridgeStatusControllerEvents = ControllerStateChangeEvent< BridgeStatusControllerState >; +/** + * The external actions available to the BridgeStatusController. + */ type AllowedActions = | NetworkControllerFindNetworkClientIdByChainIdAction | NetworkControllerGetStateAction | NetworkControllerGetNetworkClientByIdAction - | AccountsControllerGetSelectedAccountAction; + | AccountsControllerGetSelectedAccountAction + | TransactionControllerGetStateAction; + +/** + * The external events available to the BridgeStatusController. + */ type AllowedEvents = never; /** diff --git a/app/scripts/controllers/bridge-status/utils.ts b/app/scripts/controllers/bridge-status/utils.ts index 323e7e2faeab..5447b483e34f 100644 --- a/app/scripts/controllers/bridge-status/utils.ts +++ b/app/scripts/controllers/bridge-status/utils.ts @@ -5,24 +5,42 @@ import { import fetchWithCache from '../../../../shared/lib/fetch-with-cache'; import { StatusResponse, - StatusRequest, + StatusRequestWithSrcTxHash, + StatusRequestDto, } from '../../../../shared/types/bridge-status'; +import type { Quote } from '../../../../shared/types/bridge'; import { validateResponse, validators } from './validators'; const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID }; export const BRIDGE_STATUS_BASE_URL = `${BRIDGE_API_BASE_URL}/getTxStatus`; -export const fetchBridgeTxStatus = async (statusRequest: StatusRequest) => { - // Assemble params +export const getStatusRequestDto = ( + statusRequest: StatusRequestWithSrcTxHash, +): StatusRequestDto => { const { quote, ...statusRequestNoQuote } = statusRequest; + const statusRequestNoQuoteFormatted = Object.fromEntries( Object.entries(statusRequestNoQuote).map(([key, value]) => [ key, value.toString(), ]), - ); - const params = new URLSearchParams(statusRequestNoQuoteFormatted); + ) as unknown as Omit; + + const requestId: { requestId: string } | Record = + quote?.requestId ? { requestId: quote.requestId } : {}; + + return { + ...statusRequestNoQuoteFormatted, + ...requestId, + }; +}; + +export const fetchBridgeTxStatus = async ( + statusRequest: StatusRequestWithSrcTxHash, +) => { + const statusRequestDto = getStatusRequestDto(statusRequest); + const params = new URLSearchParams(statusRequestDto); // Fetch const url = `${BRIDGE_STATUS_BASE_URL}?${params.toString()}`; @@ -47,3 +65,18 @@ export const fetchBridgeTxStatus = async (statusRequest: StatusRequest) => { // Return return rawTxStatus; }; + +export const getStatusRequestWithSrcTxHash = ( + quote: Quote, + srcTxHash: string, +): StatusRequestWithSrcTxHash => { + return { + bridgeId: quote.bridgeId, + srcTxHash, + bridge: quote.bridges[0], + srcChainId: quote.srcChainId, + destChainId: quote.destChainId, + quote, + refuel: Boolean(quote.refuel), + }; +}; diff --git a/app/scripts/controllers/bridge-status/validators.test.ts b/app/scripts/controllers/bridge-status/validators.test.ts index 18ca81d7a5b2..83877b80bdb5 100644 --- a/app/scripts/controllers/bridge-status/validators.test.ts +++ b/app/scripts/controllers/bridge-status/validators.test.ts @@ -162,6 +162,43 @@ const BridgeTxStatusResponses = { }, }, }, + STATUS_COMPLETE_VALID_MISSING_FIELDS_2: { + status: 'COMPLETE', + isExpectedToken: false, + bridge: 'across', + srcChain: { + chainId: 10, + txHash: + '0x4c57876fad21fb5149af5a58a4aba2ca9d6b212014505dd733b75667ca4f0f2b', + amount: '991250000000000', + token: { + chainId: 10, + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + name: 'Wrapped Ether', + decimals: 18, + icon: 'https://media.socket.tech/tokens/all/WETH', + // logoURI: 'https://media.socket.tech/tokens/all/WETH', + // chainAgnosticId: 'ETH', + }, + }, + destChain: { + chainId: 8453, + txHash: + '0x60c4cad7c3eb14c7b3ace40cd4015b90927dadacbdc8673f404bea6a5603844b', + amount: '988339336750062', + token: { + chainId: 8453, + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + name: 'Wrapped Ether', + decimals: 18, + icon: null, + // logoURI: null, + // chainAgnosticId: null, + }, + }, + }, STATUS_COMPLETE_INVALID_MISSING_FIELDS: { status: 'COMPLETE', isExpectedToken: true, @@ -203,6 +240,11 @@ describe('validators', () => { expected: false, description: 'complete bridge status with missing fields', }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID_MISSING_FIELDS_2, + expected: true, + description: 'complete bridge status with missing fields 2', + }, { input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID_MISSING_FIELDS, expected: true, diff --git a/app/scripts/controllers/bridge-status/validators.ts b/app/scripts/controllers/bridge-status/validators.ts index 69e788025b01..b942317a3119 100644 --- a/app/scripts/controllers/bridge-status/validators.ts +++ b/app/scripts/controllers/bridge-status/validators.ts @@ -54,9 +54,10 @@ const assetValidators = [ }, { property: 'icon', - type: 'string|undefined', - validator: (v: unknown): v is string | undefined => - v === undefined || typeof v === 'string', + // typeof null === 'object' + type: 'string|undefined|object', + validator: (v: unknown): v is string | undefined | object => + v === undefined || v === null || typeof v === 'string', }, ]; diff --git a/app/scripts/controllers/bridge/bridge-controller.test.ts b/app/scripts/controllers/bridge/bridge-controller.test.ts index 5cadcb1bd375..1c4864370b6b 100644 --- a/app/scripts/controllers/bridge/bridge-controller.test.ts +++ b/app/scripts/controllers/bridge/bridge-controller.test.ts @@ -5,16 +5,15 @@ import { BRIDGE_API_BASE_URL } from '../../../../shared/constants/bridge'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { SWAPS_API_V2_BASE_URL } from '../../../../shared/constants/swaps'; import { flushPromises } from '../../../../test/lib/timer-helpers'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import * as bridgeUtil from '../../../../ui/pages/bridge/bridge.util'; +import * as bridgeUtil from '../../../../shared/modules/bridge-utils/bridge.util'; import * as balanceUtils from '../../../../shared/modules/bridge-utils/balance'; import mockBridgeQuotesErc20Native from '../../../../test/data/bridge/mock-quotes-erc20-native.json'; import mockBridgeQuotesNativeErc20 from '../../../../test/data/bridge/mock-quotes-native-erc20.json'; import mockBridgeQuotesNativeErc20Eth from '../../../../test/data/bridge/mock-quotes-native-erc20-eth.json'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { QuoteResponse } from '../../../../ui/pages/bridge/types'; +import { + RequestStatus, + type QuoteResponse, +} from '../../../../shared/types/bridge'; import { decimalToHex } from '../../../../shared/modules/conversion.utils'; import BridgeController from './bridge-controller'; import { BridgeControllerMessenger } from './types'; @@ -66,10 +65,26 @@ describe('BridgeController', function () { 'extension-config': { refreshRate: 3, maxRefreshCount: 3, + support: true, + chains: { + '10': { + isActiveSrc: true, + isActiveDest: false, + }, + '534352': { + isActiveSrc: true, + isActiveDest: false, + }, + '137': { + isActiveSrc: false, + isActiveDest: true, + }, + '42161': { + isActiveSrc: false, + isActiveDest: true, + }, + }, }, - 'extension-support': true, - 'src-network-allowlist': [10, 534352], - 'dest-network-allowlist': [137, 42161], 'approval-gas-multiplier': { '137': 1.1, '42161': 1.2, @@ -90,6 +105,7 @@ describe('BridgeController', function () { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', symbol: 'ABC', decimals: 16, + aggregators: ['lifl', 'socket'], }, { address: '0x1291478912', @@ -114,12 +130,16 @@ describe('BridgeController', function () { it('setBridgeFeatureFlags should fetch and set the bridge feature flags', async function () { const expectedFeatureFlagsResponse = { - extensionSupport: true, - destNetworkAllowlist: [CHAIN_IDS.POLYGON, CHAIN_IDS.ARBITRUM], - srcNetworkAllowlist: [CHAIN_IDS.OPTIMISM, CHAIN_IDS.SCROLL], extensionConfig: { maxRefreshCount: 3, refreshRate: 3, + support: true, + chains: { + [CHAIN_IDS.OPTIMISM]: { isActiveSrc: true, isActiveDest: false }, + [CHAIN_IDS.SCROLL]: { isActiveSrc: true, isActiveDest: false }, + [CHAIN_IDS.POLYGON]: { isActiveSrc: false, isActiveDest: true }, + [CHAIN_IDS.ARBITRUM]: { isActiveSrc: false, isActiveDest: true }, + }, }, }; expect(bridgeController.state).toStrictEqual(EMPTY_INIT_STATE); @@ -151,32 +171,12 @@ describe('BridgeController', function () { it('selectDestNetwork should set the bridge dest tokens and top assets', async function () { await bridgeController.selectDestNetwork('0xa'); expect(bridgeController.state.bridgeState.destTokens).toStrictEqual({ - '0x0000000000000000000000000000000000000000': { - address: '0x0000000000000000000000000000000000000000', - decimals: 18, - iconUrl: './images/eth_logo.svg', - name: 'Ether', - symbol: 'ETH', - }, '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', symbol: 'ABC', decimals: 16, + aggregators: ['lifl', 'socket'], }, - }); - expect(bridgeController.state.bridgeState.destTopAssets).toStrictEqual([ - { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', symbol: 'ABC' }, - ]); - expect(bridgeController.state.bridgeState.quoteRequest).toStrictEqual({ - slippage: 0.5, - srcTokenAddress: '0x0000000000000000000000000000000000000000', - walletAddress: undefined, - }); - }); - - it('selectSrcNetwork should set the bridge src tokens and top assets', async function () { - await bridgeController.selectSrcNetwork('0xa'); - expect(bridgeController.state.bridgeState.srcTokens).toStrictEqual({ '0x0000000000000000000000000000000000000000': { address: '0x0000000000000000000000000000000000000000', decimals: 18, @@ -184,17 +184,12 @@ describe('BridgeController', function () { name: 'Ether', symbol: 'ETH', }, - '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': { - address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', - symbol: 'ABC', - decimals: 16, - }, }); - expect(bridgeController.state.bridgeState.srcTopAssets).toStrictEqual([ - { - address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', - symbol: 'ABC', - }, + expect( + bridgeController.state.bridgeState.destTokensLoadingStatus, + ).toStrictEqual(RequestStatus.FETCHED); + expect(bridgeController.state.bridgeState.destTopAssets).toStrictEqual([ + { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', symbol: 'ABC' }, ]); expect(bridgeController.state.bridgeState.quoteRequest).toStrictEqual({ slippage: 0.5, @@ -272,10 +267,7 @@ describe('BridgeController', function () { it('updateBridgeQuoteRequestParams should trigger quote polling if request is valid', async function () { jest.useFakeTimers(); const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); - const startPollingByNetworkClientIdSpy = jest.spyOn( - bridgeController, - 'startPollingByNetworkClientId', - ); + const startPollingSpy = jest.spyOn(bridgeController, 'startPolling'); const hasSufficientBalanceSpy = jest .spyOn(balanceUtils, 'hasSufficientBalance') .mockResolvedValue(true); @@ -328,15 +320,15 @@ describe('BridgeController', function () { await bridgeController.updateBridgeQuoteRequestParams(quoteParams); expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(startPollingSpy).toHaveBeenCalledTimes(1); expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledWith( - expect.anything(), - { + expect(startPollingSpy).toHaveBeenCalledWith({ + networkClientId: expect.anything(), + updatedQuoteRequest: { ...quoteRequest, insufficientBal: false, }, - ); + }); expect(bridgeController.state.bridgeState).toStrictEqual( expect.objectContaining({ @@ -396,6 +388,7 @@ describe('BridgeController', function () { ...mockBridgeQuotesNativeErc20Eth, ], quotesLoadingStatus: 1, + quoteFetchError: undefined, quotesRefreshCount: 2, }), ); @@ -416,12 +409,14 @@ describe('BridgeController', function () { ...mockBridgeQuotesNativeErc20Eth, ], quotesLoadingStatus: 2, + quoteFetchError: 'Network error', quotesRefreshCount: 3, }), ); - expect(bridgeController.state.bridgeState.quotesLastFetched).toStrictEqual( - secondFetchTime, - ); + secondFetchTime && + expect( + bridgeController.state.bridgeState.quotesLastFetched, + ).toBeGreaterThan(secondFetchTime); expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1); expect(getLayer1GasFeeMock).not.toHaveBeenCalled(); @@ -430,10 +425,7 @@ describe('BridgeController', function () { it('updateBridgeQuoteRequestParams should only poll once if insufficientBal=true', async function () { jest.useFakeTimers(); const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); - const startPollingByNetworkClientIdSpy = jest.spyOn( - bridgeController, - 'startPollingByNetworkClientId', - ); + const startPollingSpy = jest.spyOn(bridgeController, 'startPolling'); const hasSufficientBalanceSpy = jest .spyOn(balanceUtils, 'hasSufficientBalance') .mockResolvedValue(false); @@ -478,21 +470,22 @@ describe('BridgeController', function () { await bridgeController.updateBridgeQuoteRequestParams(quoteParams); expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(startPollingSpy).toHaveBeenCalledTimes(1); expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledWith( - expect.anything(), - { + expect(startPollingSpy).toHaveBeenCalledWith({ + networkClientId: expect.anything(), + updatedQuoteRequest: { ...quoteRequest, insufficientBal: true, }, - ); + }); expect(bridgeController.state.bridgeState).toStrictEqual( expect.objectContaining({ quoteRequest: { ...quoteRequest, walletAddress: undefined }, quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, + quotesInitialLoadTime: undefined, quotesLoadingStatus: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, }), @@ -530,6 +523,7 @@ describe('BridgeController', function () { quotes: mockBridgeQuotesNativeErc20Eth, quotesLoadingStatus: 1, quotesRefreshCount: 1, + quotesInitialLoadTime: 11000, }), ); const firstFetchTime = @@ -546,6 +540,7 @@ describe('BridgeController', function () { quotes: mockBridgeQuotesNativeErc20Eth, quotesLoadingStatus: 1, quotesRefreshCount: 1, + quotesInitialLoadTime: 11000, }), ); const secondFetchTime = @@ -556,10 +551,7 @@ describe('BridgeController', function () { it('updateBridgeQuoteRequestParams should not trigger quote polling if request is invalid', function () { const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); - const startPollingByNetworkClientIdSpy = jest.spyOn( - bridgeController, - 'startPollingByNetworkClientId', - ); + const startPollingSpy = jest.spyOn(bridgeController, 'startPolling'); messengerMock.call.mockReturnValue({ address: '0x123', provider: jest.fn(), @@ -573,7 +565,7 @@ describe('BridgeController', function () { }); expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).not.toHaveBeenCalled(); + expect(startPollingSpy).not.toHaveBeenCalled(); expect(bridgeController.state.bridgeState).toStrictEqual( expect.objectContaining({ @@ -638,10 +630,7 @@ describe('BridgeController', function () { ) => { jest.useFakeTimers(); const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); - const startPollingByNetworkClientIdSpy = jest.spyOn( - bridgeController, - 'startPollingByNetworkClientId', - ); + const startPollingSpy = jest.spyOn(bridgeController, 'startPolling'); const hasSufficientBalanceSpy = jest .spyOn(balanceUtils, 'hasSufficientBalance') .mockResolvedValue(false); @@ -676,15 +665,15 @@ describe('BridgeController', function () { await bridgeController.updateBridgeQuoteRequestParams(quoteParams); expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(startPollingSpy).toHaveBeenCalledTimes(1); expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1); - expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledWith( - expect.anything(), - { + expect(startPollingSpy).toHaveBeenCalledWith({ + networkClientId: expect.anything(), + updatedQuoteRequest: { ...quoteRequest, insufficientBal: true, }, - ); + }); expect(bridgeController.state.bridgeState).toStrictEqual( expect.objectContaining({ diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts index bbe016ac7aea..2c1c43fa6ed1 100644 --- a/app/scripts/controllers/bridge/bridge-controller.ts +++ b/app/scripts/controllers/bridge/bridge-controller.ts @@ -12,9 +12,7 @@ import { fetchBridgeFeatureFlags, fetchBridgeQuotes, fetchBridgeTokens, - // TODO: Remove restricted import - // eslint-disable-next-line import/no-restricted-paths -} from '../../../../ui/pages/bridge/bridge.util'; +} from '../../../../shared/modules/bridge-utils/bridge.util'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { fetchTopAssetsList } from '../../../../ui/pages/swaps/swaps.util'; @@ -23,30 +21,24 @@ import { sumHexes, } from '../../../../shared/modules/conversion.utils'; import { - L1GasFees, - QuoteRequest, - QuoteResponse, - TxData, - // TODO: Remove restricted import - // eslint-disable-next-line import/no-restricted-paths -} from '../../../../ui/pages/bridge/types'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { isValidQuoteRequest } from '../../../../ui/pages/bridge/utils/quote'; + type L1GasFees, + type QuoteRequest, + type QuoteResponse, + type TxData, + type BridgeControllerState, + BridgeFeatureFlagsKey, + RequestStatus, +} from '../../../../shared/types/bridge'; +import { isValidQuoteRequest } from '../../../../shared/modules/bridge-utils/quote'; import { hasSufficientBalance } from '../../../../shared/modules/bridge-utils/balance'; import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { REFRESH_INTERVAL_MS } from '../../../../shared/constants/bridge'; import { BRIDGE_CONTROLLER_NAME, DEFAULT_BRIDGE_CONTROLLER_STATE, - REFRESH_INTERVAL_MS, - RequestStatus, METABRIDGE_CHAIN_TO_ADDRESS_MAP, } from './constants'; -import { - BridgeControllerState, - BridgeControllerMessenger, - BridgeFeatureFlagsKey, -} from './types'; +import type { BridgeControllerMessenger } from './types'; const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = { bridgeState: { @@ -57,13 +49,21 @@ const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = { const RESET_STATE_ABORT_MESSAGE = 'Reset controller state'; -export default class BridgeController extends StaticIntervalPollingController< +/** The input to start polling for the {@link BridgeController} */ +type BridgePollingInput = { + networkClientId: NetworkClientId; + updatedQuoteRequest: QuoteRequest; +}; + +export default class BridgeController extends StaticIntervalPollingController()< typeof BRIDGE_CONTROLLER_NAME, { bridgeState: BridgeControllerState }, BridgeControllerMessenger > { #abortController: AbortController | undefined; + #quotesFirstFetched: number | undefined; + #getLayer1GasFee: (params: { transactionParams: TransactionParams; chainId: ChainId; @@ -96,10 +96,6 @@ export default class BridgeController extends StaticIntervalPollingController< `${BRIDGE_CONTROLLER_NAME}:setBridgeFeatureFlags`, this.setBridgeFeatureFlags.bind(this), ); - this.messagingSystem.registerActionHandler( - `${BRIDGE_CONTROLLER_NAME}:selectSrcNetwork`, - this.selectSrcNetwork.bind(this), - ); this.messagingSystem.registerActionHandler( `${BRIDGE_CONTROLLER_NAME}:selectDestNetwork`, this.selectDestNetwork.bind(this), @@ -120,11 +116,8 @@ export default class BridgeController extends StaticIntervalPollingController< this.#getLayer1GasFee = getLayer1GasFee; } - _executePoll = async ( - _: NetworkClientId, - updatedQuoteRequest: QuoteRequest, - ) => { - await this.#fetchBridgeQuotes(updatedQuoteRequest); + _executePoll = async (pollingInput: BridgePollingInput) => { + await this.#fetchBridgeQuotes(pollingInput); }; updateBridgeQuoteRequestParams = async ( @@ -147,11 +140,15 @@ export default class BridgeController extends StaticIntervalPollingController< quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, quotesLoadingStatus: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, + quoteFetchError: DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError, quotesRefreshCount: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount, + quotesInitialLoadTime: + DEFAULT_BRIDGE_CONTROLLER_STATE.quotesInitialLoadTime, }; }); if (isValidQuoteRequest(updatedQuoteRequest)) { + this.#quotesFirstFetched = Date.now(); const walletAddress = this.#getSelectedAccount().address; const srcChainIdInHex = add0x( decimalToHex(updatedQuoteRequest.srcChainId), @@ -162,10 +159,13 @@ export default class BridgeController extends StaticIntervalPollingController< !(await this.#hasSufficientBalance(updatedQuoteRequest)); const networkClientId = this.#getSelectedNetworkClientId(srcChainIdInHex); - this.startPollingByNetworkClientId(networkClientId, { - ...updatedQuoteRequest, - walletAddress, - insufficientBal, + this.startPolling({ + networkClientId, + updatedQuoteRequest: { + ...updatedQuoteRequest, + walletAddress, + insufficientBal, + }, }); } }; @@ -211,20 +211,29 @@ export default class BridgeController extends StaticIntervalPollingController< ); }; - selectSrcNetwork = async (chainId: Hex) => { - await this.#setTopAssets(chainId, 'srcTopAssets'); - await this.#setTokens(chainId, 'srcTokens'); - }; - selectDestNetwork = async (chainId: Hex) => { - await this.#setTopAssets(chainId, 'destTopAssets'); - await this.#setTokens(chainId, 'destTokens'); + this.update((state) => { + state.bridgeState.destTokensLoadingStatus = RequestStatus.LOADING; + return state; + }); + try { + await this.#setTopAssets(chainId, 'destTopAssets'); + await this.#setTokens(chainId, 'destTokens'); + } finally { + this.update((state) => { + state.bridgeState.destTokensLoadingStatus = RequestStatus.FETCHED; + return state; + }); + } }; - #fetchBridgeQuotes = async (request: QuoteRequest) => { + #fetchBridgeQuotes = async ({ + networkClientId: _networkClientId, + updatedQuoteRequest, + }: BridgePollingInput) => { this.#abortController?.abort('New quote request'); this.#abortController = new AbortController(); - if (request.srcChainId === request.destChainId) { + if (updatedQuoteRequest.srcChainId === updatedQuoteRequest.destChainId) { return; } const { bridgeState } = this.state; @@ -232,36 +241,24 @@ export default class BridgeController extends StaticIntervalPollingController< _state.bridgeState = { ...bridgeState, quotesLoadingStatus: RequestStatus.LOADING, - quoteRequest: request, + quoteRequest: updatedQuoteRequest, + quoteFetchError: DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError, }; }); - const { maxRefreshCount } = - bridgeState.bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG]; - const newQuotesRefreshCount = bridgeState.quotesRefreshCount + 1; try { const quotes = await fetchBridgeQuotes( - request, + updatedQuoteRequest, this.#abortController.signal, ); - // Stop polling if the maximum number of refreshes has been reached - if ( - (request.insufficientBal && newQuotesRefreshCount >= 1) || - (!request.insufficientBal && newQuotesRefreshCount >= maxRefreshCount) - ) { - this.stopAllPolling(); - } - const quotesWithL1GasFees = await this.#appendL1GasFees(quotes); this.update((_state) => { _state.bridgeState = { ..._state.bridgeState, quotes: quotesWithL1GasFees, - quotesLastFetched: Date.now(), quotesLoadingStatus: RequestStatus.FETCHED, - quotesRefreshCount: newQuotesRefreshCount, }; }); } catch (error) { @@ -274,11 +271,39 @@ export default class BridgeController extends StaticIntervalPollingController< this.update((_state) => { _state.bridgeState = { ...bridgeState, + quoteFetchError: + error instanceof Error ? error.message : 'Unknown error', quotesLoadingStatus: RequestStatus.ERROR, - quotesRefreshCount: newQuotesRefreshCount, }; }); console.log('Failed to fetch bridge quotes', error); + } finally { + const { maxRefreshCount } = + bridgeState.bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG]; + + const updatedQuotesRefreshCount = bridgeState.quotesRefreshCount + 1; + // Stop polling if the maximum number of refreshes has been reached + if ( + updatedQuoteRequest.insufficientBal || + (!updatedQuoteRequest.insufficientBal && + updatedQuotesRefreshCount >= maxRefreshCount) + ) { + this.stopAllPolling(); + } + + // Update quote fetching stats + const quotesLastFetched = Date.now(); + this.update((_state) => { + _state.bridgeState = { + ..._state.bridgeState, + quotesInitialLoadTime: + updatedQuotesRefreshCount === 1 && this.#quotesFirstFetched + ? quotesLastFetched - this.#quotesFirstFetched + : bridgeState.quotesInitialLoadTime, + quotesLastFetched, + quotesRefreshCount: updatedQuotesRefreshCount, + }; + }); } }; diff --git a/app/scripts/controllers/bridge/constants.ts b/app/scripts/controllers/bridge/constants.ts index ec60f8e6a0a4..b6c07044adc5 100644 --- a/app/scripts/controllers/bridge/constants.ts +++ b/app/scripts/controllers/bridge/constants.ts @@ -1,42 +1,38 @@ import { zeroAddress } from 'ethereumjs-util'; import { Hex } from '@metamask/utils'; -import { METABRIDGE_ETHEREUM_ADDRESS } from '../../../../shared/constants/bridge'; +import { + BRIDGE_DEFAULT_SLIPPAGE, + DEFAULT_MAX_REFRESH_COUNT, + METABRIDGE_ETHEREUM_ADDRESS, + REFRESH_INTERVAL_MS, +} from '../../../../shared/constants/bridge'; import { CHAIN_IDS } from '../../../../shared/constants/network'; -import { BridgeControllerState, BridgeFeatureFlagsKey } from './types'; +import { BridgeFeatureFlagsKey } from '../../../../shared/types/bridge'; +import type { BridgeControllerState } from '../../../../shared/types/bridge'; export const BRIDGE_CONTROLLER_NAME = 'BridgeController'; -export const REFRESH_INTERVAL_MS = 30 * 1000; -const DEFAULT_MAX_REFRESH_COUNT = 5; -const DEFAULT_SLIPPAGE = 0.5; - -export enum RequestStatus { - LOADING, - FETCHED, - ERROR, -} - export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = { bridgeFeatureFlags: { [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: { refreshRate: REFRESH_INTERVAL_MS, maxRefreshCount: DEFAULT_MAX_REFRESH_COUNT, + support: false, + chains: {}, }, - [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false, - [BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST]: [], - [BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: [], }, - srcTokens: {}, - srcTopAssets: [], + destTokensLoadingStatus: undefined, destTokens: {}, destTopAssets: [], quoteRequest: { walletAddress: undefined, srcTokenAddress: zeroAddress(), - slippage: DEFAULT_SLIPPAGE, + slippage: BRIDGE_DEFAULT_SLIPPAGE, }, + quotesInitialLoadTime: undefined, quotes: [], quotesLastFetched: undefined, quotesLoadingStatus: undefined, + quoteFetchError: undefined, quotesRefreshCount: 0, }; diff --git a/app/scripts/controllers/bridge/types.ts b/app/scripts/controllers/bridge/types.ts index c9e221b82418..a417ddc4d074 100644 --- a/app/scripts/controllers/bridge/types.ts +++ b/app/scripts/controllers/bridge/types.ts @@ -2,63 +2,18 @@ import { ControllerStateChangeEvent, RestrictedControllerMessenger, } from '@metamask/base-controller'; -import { Hex } from '@metamask/utils'; import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; import { NetworkControllerFindNetworkClientIdByChainIdAction, NetworkControllerGetSelectedNetworkClientAction, } from '@metamask/network-controller'; -import { SwapsTokenObject } from '../../../../shared/constants/swaps'; -import { - L1GasFees, - QuoteRequest, - QuoteResponse, - // TODO: Remove restricted import - // eslint-disable-next-line import/no-restricted-paths -} from '../../../../ui/pages/bridge/types'; +import type { + BridgeBackgroundAction, + BridgeControllerState, + BridgeUserAction, +} from '../../../../shared/types/bridge'; import BridgeController from './bridge-controller'; -import { BRIDGE_CONTROLLER_NAME, RequestStatus } from './constants'; - -export enum BridgeFeatureFlagsKey { - EXTENSION_CONFIG = 'extensionConfig', - EXTENSION_SUPPORT = 'extensionSupport', - NETWORK_SRC_ALLOWLIST = 'srcNetworkAllowlist', - NETWORK_DEST_ALLOWLIST = 'destNetworkAllowlist', -} - -export type BridgeFeatureFlags = { - [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: { - refreshRate: number; - maxRefreshCount: number; - }; - [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: boolean; - [BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST]: Hex[]; - [BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: Hex[]; -}; - -export type BridgeControllerState = { - bridgeFeatureFlags: BridgeFeatureFlags; - srcTokens: Record; - srcTopAssets: { address: string }[]; - destTokens: Record; - destTopAssets: { address: string }[]; - quoteRequest: Partial; - quotes: (QuoteResponse & L1GasFees)[]; - quotesLastFetched?: number; - quotesLoadingStatus?: RequestStatus; - quotesRefreshCount: number; -}; - -export enum BridgeUserAction { - SELECT_SRC_NETWORK = 'selectSrcNetwork', - SELECT_DEST_NETWORK = 'selectDestNetwork', - UPDATE_QUOTE_PARAMS = 'updateBridgeQuoteRequestParams', -} -export enum BridgeBackgroundAction { - SET_FEATURE_FLAGS = 'setBridgeFeatureFlags', - RESET_STATE = 'resetState', - GET_BRIDGE_ERC20_ALLOWANCE = 'getBridgeERC20Allowance', -} +import { BRIDGE_CONTROLLER_NAME } from './constants'; type BridgeControllerAction = { type: `${typeof BRIDGE_CONTROLLER_NAME}:${FunctionName}`; @@ -70,7 +25,6 @@ type BridgeControllerActions = | BridgeControllerAction | BridgeControllerAction | BridgeControllerAction - | BridgeControllerAction | BridgeControllerAction | BridgeControllerAction; diff --git a/app/scripts/controllers/metametrics-controller.test.ts b/app/scripts/controllers/metametrics-controller.test.ts index 58fad403fdab..939d8e506070 100644 --- a/app/scripts/controllers/metametrics-controller.test.ts +++ b/app/scripts/controllers/metametrics-controller.test.ts @@ -10,7 +10,7 @@ import { Token, TokensControllerState, } from '@metamask/assets-controllers'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { Browser } from 'webextension-polyfill'; import { ControllerMessenger } from '@metamask/base-controller'; import { merge } from 'lodash'; @@ -309,6 +309,7 @@ describe('MetaMetricsController', function () { }); const expectedFragment = merge( + {}, SAMPLE_TX_SUBMITTED_PARTIAL_FRAGMENT, SAMPLE_PERSISTED_EVENT_NO_ID, { @@ -1453,7 +1454,7 @@ describe('MetaMetricsController', function () { participateInMetaMetrics: true, currentCurrency: 'usd', dataCollectionForMarketing: false, - preferences: { privacyMode: true }, + preferences: { privacyMode: true, tokenNetworkFilter: [] }, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) custodyAccountDetails: {}, ///: END:ONLY_INCLUDE_IF @@ -1494,6 +1495,7 @@ describe('MetaMetricsController', function () { ///: END:ONLY_INCLUDE_IF [MetaMetricsUserTrait.TokenSortPreference]: 'token-sort-key', [MetaMetricsUserTrait.PrivacyModeEnabled]: true, + [MetaMetricsUserTrait.NetworkFilterPreference]: [], }); }); }); @@ -1543,7 +1545,7 @@ describe('MetaMetricsController', function () { allNfts: {}, participateInMetaMetrics: true, dataCollectionForMarketing: false, - preferences: { privacyMode: true }, + preferences: { privacyMode: true, tokenNetworkFilter: [] }, securityAlertsEnabled: true, names: { ethereumAddress: {}, @@ -1607,7 +1609,7 @@ describe('MetaMetricsController', function () { allNfts: {}, participateInMetaMetrics: true, dataCollectionForMarketing: false, - preferences: { privacyMode: true }, + preferences: { privacyMode: true, tokenNetworkFilter: [] }, securityAlertsEnabled: true, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) custodyAccountDetails: {}, @@ -1667,12 +1669,12 @@ describe('MetaMetricsController', function () { }, ShowNativeTokenAsMainBalance: true, allNfts: {}, + participateInMetaMetrics: true, + dataCollectionForMarketing: false, + preferences: { privacyMode: true, tokenNetworkFilter: [] }, names: { ethereumAddress: {}, }, - participateInMetaMetrics: true, - dataCollectionForMarketing: false, - preferences: { privacyMode: true }, securityAlertsEnabled: true, security_providers: ['blockaid'], currentCurrency: 'usd', @@ -1718,7 +1720,7 @@ describe('MetaMetricsController', function () { allNfts: {}, participateInMetaMetrics: true, dataCollectionForMarketing: false, - preferences: { privacyMode: true }, + preferences: { privacyMode: true, tokenNetworkFilter: [] }, names: { ethereumAddress: {}, }, @@ -1840,6 +1842,40 @@ describe('MetaMetricsController', function () { ); }); }); + describe('updateExtensionUninstallUrl', function () { + it('should include extension version in uninstall URL regardless of MetaMetrics participation', async function () { + await withController(({ controller }) => { + const setUninstallURLSpy = jest.spyOn( + MOCK_EXTENSION.runtime, + 'setUninstallURL', + ); + + // Test with MetaMetrics disabled + controller.updateExtensionUninstallUrl(false, 'test-id'); + expect(setUninstallURLSpy).toHaveBeenCalledWith( + expect.stringContaining(`av=${VERSION}`), + ); + expect(setUninstallURLSpy).toHaveBeenCalledWith( + expect.not.stringContaining('mmi='), + ); + expect(setUninstallURLSpy).toHaveBeenCalledWith( + expect.not.stringContaining('env='), + ); + + // Test with MetaMetrics enabled + controller.updateExtensionUninstallUrl(true, 'test-id'); + expect(setUninstallURLSpy).toHaveBeenCalledWith( + expect.stringContaining(`av=${VERSION}`), + ); + expect(setUninstallURLSpy).toHaveBeenCalledWith( + expect.stringContaining('mmi='), + ); + expect(setUninstallURLSpy).toHaveBeenCalledWith( + expect.stringContaining('env='), + ); + }); + }); + }); }); type WithControllerOptions = { diff --git a/app/scripts/controllers/metametrics-controller.ts b/app/scripts/controllers/metametrics-controller.ts index d29a2840eb27..9668a9f25b09 100644 --- a/app/scripts/controllers/metametrics-controller.ts +++ b/app/scripts/controllers/metametrics-controller.ts @@ -166,6 +166,7 @@ export type MetaMaskState = { currentCurrency: string; preferences: { privacyMode: PreferencesControllerState['preferences']['privacyMode']; + tokenNetworkFilter: string[]; }; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) custodyAccountDetails: { @@ -583,8 +584,7 @@ export default class MetaMetricsController extends BaseController< : {}; this.update((state) => { - // @ts-expect-error this is caused by a bug in Immer, not being able to handle recursive types like Json - state.fragments[id] = merge(additionalFragmentProps, fragment); + state.fragments[id] = merge({}, additionalFragmentProps, fragment); }); if (fragment.initialEvent) { @@ -778,13 +778,14 @@ export default class MetaMetricsController extends BaseController< const query: { mmi?: string; env?: string; - av?: string; - } = {}; + av: string; + } = { + av: this.version, + }; if (participateInMetaMetrics) { // We only want to track these things if a user opted into metrics. query.mmi = Buffer.from(metaMetricsId).toString('base64'); query.env = this.#environment; - query.av = this.version; } const queryString = new URLSearchParams(query); @@ -1233,6 +1234,9 @@ export default class MetaMetricsController extends BaseController< metamaskState.tokenSortConfig?.key || '', [MetaMetricsUserTrait.PrivacyModeEnabled]: metamaskState.preferences.privacyMode, + [MetaMetricsUserTrait.NetworkFilterPreference]: Object.keys( + metamaskState.preferences.tokenNetworkFilter || {}, + ), }; if (!previousUserTraits) { diff --git a/app/scripts/controllers/mmi-controller.test.ts b/app/scripts/controllers/mmi-controller.test.ts index 71c8329b3dcd..8e52655ee11e 100644 --- a/app/scripts/controllers/mmi-controller.test.ts +++ b/app/scripts/controllers/mmi-controller.test.ts @@ -273,7 +273,7 @@ describe('MMIController', function () { appStateController: new AppStateController({ addUnlockListener: jest.fn(), isUnlocked: jest.fn(() => true), - initState: {}, + state: {}, onInactiveTimeout: jest.fn(), showUnlockRequest: jest.fn(), messenger: { diff --git a/app/scripts/controllers/mmi-controller.ts b/app/scripts/controllers/mmi-controller.ts index 9f2701b2c0b9..7378585a7e8f 100644 --- a/app/scripts/controllers/mmi-controller.ts +++ b/app/scripts/controllers/mmi-controller.ts @@ -27,8 +27,8 @@ import { OriginalRequest, SignatureController, } from '@metamask/signature-controller'; -import { InternalAccount } from '@metamask/keyring-api'; import { toHex } from '@metamask/controller-utils'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths @@ -311,7 +311,7 @@ export class MMIController { } } - const txList = this.txStateManager.getTransactions({}, [], false); // Includes all transactions, but we are looping through keyrings. Currently filtering is done in updateCustodianTransactions :-/ + const txList = this.txStateManager.getTransactions(); // Includes all transactions, but we are looping through keyrings. Currently filtering is done in updateCustodianTransactions :-/ try { updateCustodianTransactions({ diff --git a/app/scripts/controllers/permissions/background-api.js b/app/scripts/controllers/permissions/background-api.js index b778ff42385d..62b32a072e68 100644 --- a/app/scripts/controllers/permissions/background-api.js +++ b/app/scripts/controllers/permissions/background-api.js @@ -1,29 +1,157 @@ -import nanoid from 'nanoid'; +import { nanoid } from 'nanoid'; import { - CaveatTypes, - RestrictedMethods, -} from '../../../../shared/constants/permissions'; -import { CaveatFactories, PermissionNames } from './specifications'; + MethodNames, + PermissionDoesNotExistError, +} from '@metamask/permission-controller'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, + getEthAccounts, + setEthAccounts, + getPermittedEthChainIds, + setPermittedEthChainIds, +} from '@metamask/multichain'; +import { isSnapId } from '@metamask/snaps-utils'; +import { RestrictedMethods } from '../../../../shared/constants/permissions'; +import { PermissionNames } from './specifications'; + +export function getPermissionBackgroundApiMethods({ + permissionController, + approvalController, +}) { + // Returns the CAIP-25 caveat or undefined if it does not exist + const getCaip25Caveat = (origin) => { + let caip25Caveat; + try { + caip25Caveat = permissionController.getCaveat( + origin, + Caip25EndowmentPermissionName, + Caip25CaveatType, + ); + } catch (err) { + if (err instanceof PermissionDoesNotExistError) { + // suppress expected error in case that the origin + // does not have the target permission yet + } else { + throw err; + } + } + return caip25Caveat; + }; -export function getPermissionBackgroundApiMethods(permissionController) { + // To add more than one account when already connected to the dapp const addMoreAccounts = (origin, accounts) => { - const caveat = CaveatFactories.restrictReturnedAccounts(accounts); + const caip25Caveat = getCaip25Caveat(origin); + if (!caip25Caveat) { + throw new Error( + `Cannot add account permissions for origin "${origin}": no permission currently exists for this origin.`, + ); + } - permissionController.grantPermissionsIncremental({ - subject: { origin }, - approvedPermissions: { - [RestrictedMethods.eth_accounts]: { caveats: [caveat] }, - }, - }); + const ethAccounts = getEthAccounts(caip25Caveat.value); + + const updatedEthAccounts = Array.from( + new Set([...ethAccounts, ...accounts]), + ); + + const updatedCaveatValue = setEthAccounts( + caip25Caveat.value, + updatedEthAccounts, + ); + + permissionController.updateCaveat( + origin, + Caip25EndowmentPermissionName, + Caip25CaveatType, + updatedCaveatValue, + ); }; const addMoreChains = (origin, chainIds) => { - const caveat = CaveatFactories.restrictNetworkSwitching(chainIds); + const caip25Caveat = getCaip25Caveat(origin); + if (!caip25Caveat) { + throw new Error( + `Cannot add chain permissions for origin "${origin}": no permission currently exists for this origin.`, + ); + } + + const ethChainIds = getPermittedEthChainIds(caip25Caveat.value); + + const updatedEthChainIds = Array.from( + new Set([...ethChainIds, ...chainIds]), + ); + + const caveatValueWithChains = setPermittedEthChainIds( + caip25Caveat.value, + updatedEthChainIds, + ); + + // ensure that the list of permitted eth accounts is set for the newly added eth scopes + const ethAccounts = getEthAccounts(caveatValueWithChains); + const caveatValueWithAccountsSynced = setEthAccounts( + caveatValueWithChains, + ethAccounts, + ); + + permissionController.updateCaveat( + origin, + Caip25EndowmentPermissionName, + Caip25CaveatType, + caveatValueWithAccountsSynced, + ); + }; + + const requestAccountsAndChainPermissions = async (origin, id) => { + // Note that we are purposely requesting an approval from the ApprovalController + // and then manually forming the permission that is then granted via the + // PermissionController rather than calling the PermissionController.requestPermissions() + // directly because the Approval UI is still dependent on the notion of there + // being separate "eth_accounts" and "endowment:permitted-chains" permissions. + // After that depedency is refactored, we can move to requesting "endowment:caip25" + // directly from the PermissionController instead. + const legacyApproval = await approvalController.addAndShowApprovalRequest({ + id, + origin, + requestData: { + metadata: { + id, + origin, + }, + permissions: { + [RestrictedMethods.eth_accounts]: {}, + [PermissionNames.permittedChains]: {}, + }, + }, + type: MethodNames.RequestPermissions, + }); + + const newCaveatValue = { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }; + + const caveatValueWithChains = setPermittedEthChainIds( + newCaveatValue, + legacyApproval.approvedChainIds, + ); + + const caveatValueWithAccounts = setEthAccounts( + caveatValueWithChains, + legacyApproval.approvedAccounts, + ); - permissionController.grantPermissionsIncremental({ + permissionController.grantPermissions({ subject: { origin }, approvedPermissions: { - [PermissionNames.permittedChains]: { caveats: [caveat] }, + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: caveatValueWithAccounts, + }, + ], + }, }, }); }; @@ -31,15 +159,19 @@ export function getPermissionBackgroundApiMethods(permissionController) { return { addPermittedAccount: (origin, account) => addMoreAccounts(origin, [account]), + addPermittedAccounts: (origin, accounts) => addMoreAccounts(origin, accounts), removePermittedAccount: (origin, account) => { - const { value: existingAccounts } = permissionController.getCaveat( - origin, - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, - ); + const caip25Caveat = getCaip25Caveat(origin); + if (!caip25Caveat) { + throw new Error( + `Cannot remove account "${account}": No permissions exist for origin "${origin}".`, + ); + } + + const existingAccounts = getEthAccounts(caip25Caveat.value); const remainingAccounts = existingAccounts.filter( (existingAccount) => existingAccount !== account, @@ -52,73 +184,66 @@ export function getPermissionBackgroundApiMethods(permissionController) { if (remainingAccounts.length === 0) { permissionController.revokePermission( origin, - RestrictedMethods.eth_accounts, + Caip25EndowmentPermissionName, ); } else { + const updatedCaveatValue = setEthAccounts( + caip25Caveat.value, + remainingAccounts, + ); permissionController.updateCaveat( origin, - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, - remainingAccounts, + Caip25EndowmentPermissionName, + Caip25CaveatType, + updatedCaveatValue, ); } }, addPermittedChain: (origin, chainId) => addMoreChains(origin, [chainId]), + addPermittedChains: (origin, chainIds) => addMoreChains(origin, chainIds), removePermittedChain: (origin, chainId) => { - const { value: existingChains } = permissionController.getCaveat( - origin, - PermissionNames.permittedChains, - CaveatTypes.restrictNetworkSwitching, - ); + const caip25Caveat = getCaip25Caveat(origin); + if (!caip25Caveat) { + throw new Error( + `Cannot remove permission for chainId "${chainId}": No permissions exist for origin "${origin}".`, + ); + } - const remainingChains = existingChains.filter( - (existingChain) => existingChain !== chainId, + const existingEthChainIds = getPermittedEthChainIds(caip25Caveat.value); + + const remainingChainIds = existingEthChainIds.filter( + (existingChainId) => existingChainId !== chainId, ); - if (remainingChains.length === existingChains.length) { + if (remainingChainIds.length === existingEthChainIds.length) { return; } - if (remainingChains.length === 0) { + if (remainingChainIds.length === 0 && !isSnapId(origin)) { permissionController.revokePermission( origin, - PermissionNames.permittedChains, + Caip25EndowmentPermissionName, ); } else { + const updatedCaveatValue = setPermittedEthChainIds( + caip25Caveat.value, + remainingChainIds, + ); permissionController.updateCaveat( origin, - PermissionNames.permittedChains, - CaveatTypes.restrictNetworkSwitching, - remainingChains, + Caip25EndowmentPermissionName, + Caip25CaveatType, + updatedCaveatValue, ); } }, - requestAccountsAndChainPermissionsWithId: async (origin) => { + requestAccountsAndChainPermissionsWithId: (origin) => { const id = nanoid(); - permissionController.requestPermissions( - { origin }, - { - [PermissionNames.eth_accounts]: {}, - [PermissionNames.permittedChains]: {}, - }, - { id }, - ); - return id; - }, - - requestAccountsPermissionWithId: async (origin) => { - const id = nanoid(); - permissionController.requestPermissions( - { origin }, - { - eth_accounts: {}, - }, - { id }, - ); + requestAccountsAndChainPermissions(origin, id); return id; }, }; diff --git a/app/scripts/controllers/permissions/background-api.test.js b/app/scripts/controllers/permissions/background-api.test.js index 2a050b29a00e..74a357f35f52 100644 --- a/app/scripts/controllers/permissions/background-api.test.js +++ b/app/scripts/controllers/permissions/background-api.test.js @@ -1,390 +1,962 @@ import { - CaveatTypes, - RestrictedMethods, -} from '../../../../shared/constants/permissions'; + MethodNames, + PermissionDoesNotExistError, +} from '@metamask/permission-controller'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; +import { RestrictedMethods } from '../../../../shared/constants/permissions'; +import { flushPromises } from '../../../../test/lib/timer-helpers'; import { getPermissionBackgroundApiMethods } from './background-api'; -import { CaveatFactories, PermissionNames } from './specifications'; +import { PermissionNames } from './specifications'; describe('permission background API methods', () => { - const getEthAccountsPermissions = (accounts) => ({ - [RestrictedMethods.eth_accounts]: { - caveats: [CaveatFactories.restrictReturnedAccounts(accounts)], - }, - }); - - const getPermittedChainsPermissions = (chainIds) => ({ - [PermissionNames.permittedChains]: { - caveats: [CaveatFactories.restrictNetworkSwitching(chainIds)], - }, + afterEach(() => { + jest.resetAllMocks(); }); describe('addPermittedAccount', () => { - it('calls grantPermissionsIncremental with expected parameters', () => { + it('gets the CAIP-25 caveat', () => { const permissionController = { - grantPermissionsIncremental: jest.fn(), + getCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods( - permissionController, - ).addPermittedAccount('foo.com', '0x1'); - - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledTimes(1); - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledWith({ - subject: { origin: 'foo.com' }, - approvedPermissions: getEthAccountsPermissions(['0x1']), - }); + try { + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedAccount('foo.com', '0x1'); + } catch (err) { + // noop + } + + expect(permissionController.getCaveat).toHaveBeenCalledWith( + 'foo.com', + Caip25EndowmentPermissionName, + Caip25CaveatType, + ); }); - }); - describe('addPermittedAccounts', () => { - it('calls grantPermissionsIncremental with expected parameters for single account', () => { + it('throws an error if there is no existing CAIP-25 caveat', () => { const permissionController = { - grantPermissionsIncremental: jest.fn(), + getCaveat: jest.fn().mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }), }; - getPermissionBackgroundApiMethods( - permissionController, - ).addPermittedAccounts('foo.com', ['0x1']); - - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledTimes(1); - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledWith({ - subject: { origin: 'foo.com' }, - approvedPermissions: getEthAccountsPermissions(['0x1']), - }); + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedAccount('foo.com', '0x1'), + ).toThrow( + new Error( + `Cannot add account permissions for origin "foo.com": no permission currently exists for this origin.`, + ), + ); }); - it('calls grantPermissionsIncremental with expected parameters with multiple accounts', () => { + it('throws an error if getCaveat fails unexpectedly', () => { const permissionController = { - grantPermissionsIncremental: jest.fn(), + getCaveat: jest.fn().mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }), }; - getPermissionBackgroundApiMethods( - permissionController, - ).addPermittedAccounts('foo.com', ['0x1', '0x2']); - - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledTimes(1); - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledWith({ - subject: { origin: 'foo.com' }, - approvedPermissions: getEthAccountsPermissions(['0x1', '0x2']), - }); + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedAccount('foo.com', '0x1'), + ).toThrow(new Error(`unexpected getCaveat error`)); }); - }); - describe('removePermittedAccount', () => { - it('removes a permitted account', () => { + it('calls updateCaveat with the account added', () => { const permissionController = { - getCaveat: jest.fn().mockImplementationOnce(() => { - return { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x2'], - }; + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x2', 'eip155:1:0x3'], + }, + }, + isMultichainOrigin: true, + }, }), - revokePermission: jest.fn(), updateCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).removePermittedAccount('foo.com', '0x2'); + }).addPermittedAccount('foo.com', '0x4'); - expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.getCaveat).toHaveBeenCalledWith( + expect(permissionController.updateCaveat).toHaveBeenCalledTimes(1); + expect(permissionController.updateCaveat).toHaveBeenCalledWith( 'foo.com', - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: { + 'eip155:1': { + accounts: [ + 'eip155:1:0x1', + 'eip155:1:0x2', + 'eip155:1:0x3', + 'eip155:1:0x4', + ], + }, + 'eip155:10': { + accounts: [ + 'eip155:10:0x1', + 'eip155:10:0x2', + 'eip155:10:0x3', + 'eip155:10:0x4', + ], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: [ + 'eip155:1:0x1', + 'eip155:1:0x2', + 'eip155:1:0x3', + 'eip155:1:0x4', + ], + }, + }, + isMultichainOrigin: true, + }, ); + }); + }); - expect(permissionController.revokePermission).not.toHaveBeenCalled(); + describe('addPermittedAccounts', () => { + it('gets the CAIP-25 caveat', () => { + const permissionController = { + getCaveat: jest.fn(), + }; - expect(permissionController.updateCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.updateCaveat).toHaveBeenCalledWith( + try { + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedAccounts('foo.com', ['0x1']); + } catch (err) { + // noop + } + + expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, - ['0x1'], + Caip25EndowmentPermissionName, + Caip25CaveatType, ); }); - it('revokes the accounts permission if the removed account is the only permitted account', () => { + it('throws an error if there is no existing CAIP-25 caveat', () => { const permissionController = { - getCaveat: jest.fn().mockImplementationOnce(() => { - return { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1'], - }; + getCaveat: jest.fn().mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedAccounts('foo.com', ['0x1']), + ).toThrow( + new Error( + `Cannot add account permissions for origin "foo.com": no permission currently exists for this origin.`, + ), + ); + }); + + it('throws an error if getCaveat fails unexpectedly', () => { + const permissionController = { + getCaveat: jest.fn().mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedAccounts('foo.com', ['0x1']), + ).toThrow(new Error(`unexpected getCaveat error`)); + }); + + it('calls updateCaveat with the accounts added to only eip155 scopes and all accounts for eip155 scopes synced', () => { + const permissionController = { + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x2', 'eip155:1:0x3'], + }, + }, + isMultichainOrigin: true, + }, }), - revokePermission: jest.fn(), updateCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).removePermittedAccount('foo.com', '0x1'); + }).addPermittedAccounts('foo.com', ['0x4', '0x5']); - expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.getCaveat).toHaveBeenCalledWith( + expect(permissionController.updateCaveat).toHaveBeenCalledTimes(1); + expect(permissionController.updateCaveat).toHaveBeenCalledWith( 'foo.com', - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: { + 'eip155:1': { + accounts: [ + 'eip155:1:0x1', + 'eip155:1:0x2', + 'eip155:1:0x3', + 'eip155:1:0x4', + 'eip155:1:0x5', + ], + }, + 'eip155:10': { + accounts: [ + 'eip155:10:0x1', + 'eip155:10:0x2', + 'eip155:10:0x3', + 'eip155:10:0x4', + 'eip155:10:0x5', + ], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: [ + 'eip155:1:0x1', + 'eip155:1:0x2', + 'eip155:1:0x3', + 'eip155:1:0x4', + 'eip155:1:0x5', + ], + }, + }, + isMultichainOrigin: true, + }, ); + }); + }); - expect(permissionController.revokePermission).toHaveBeenCalledTimes(1); - expect(permissionController.revokePermission).toHaveBeenCalledWith( + describe('removePermittedAccount', () => { + it('gets the CAIP-25 caveat', () => { + const permissionController = { + getCaveat: jest.fn(), + }; + + try { + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedAccount('foo.com', '0x1'); + } catch (err) { + // noop + } + + expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', - RestrictedMethods.eth_accounts, + Caip25EndowmentPermissionName, + Caip25CaveatType, ); + }); - expect(permissionController.updateCaveat).not.toHaveBeenCalled(); + it('throws an error if there is no existing CAIP-25 caveat', () => { + const permissionController = { + getCaveat: jest.fn().mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedAccount('foo.com', '0x1'), + ).toThrow( + new Error( + `Cannot remove account "0x1": No permissions exist for origin "foo.com".`, + ), + ); }); - it('does not call permissionController.updateCaveat if the specified account is not permitted', () => { + it('throws an error if getCaveat fails unexpectedly', () => { const permissionController = { - getCaveat: jest.fn().mockImplementationOnce(() => { - return { type: CaveatTypes.restrictReturnedAccounts, value: ['0x1'] }; + getCaveat: jest.fn().mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedAccount('foo.com', '0x1'), + ).toThrow(new Error(`unexpected getCaveat error`)); + }); + + it('does nothing if the account being removed does not exist', () => { + const permissionController = { + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x2', 'eip155:1:0x3'], + }, + }, + isMultichainOrigin: true, + }, }), - revokePermission: jest.fn(), updateCaveat: jest.fn(), + revokePermission: jest.fn(), }; - getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).removePermittedAccount('foo.com', '0x2'); - expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.getCaveat).toHaveBeenCalledWith( - 'foo.com', - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, - ); + }).removePermittedAccount('foo.com', '0xdeadbeef'); - expect(permissionController.revokePermission).not.toHaveBeenCalled(); expect(permissionController.updateCaveat).not.toHaveBeenCalled(); + expect(permissionController.revokePermission).not.toHaveBeenCalled(); }); - }); - describe('requestAccountsPermissionWithId', () => { - it('request an accounts permission and returns the request id', async () => { + it('revokes the entire permission if the removed account is the only eip:155 scoped account', () => { const permissionController = { - requestPermissions: jest - .fn() - .mockImplementationOnce(async (_, __, { id }) => { - return [null, { id }]; - }), + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + }, + isMultichainOrigin: true, + }, + }), + revokePermission: jest.fn(), }; - const id = await getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).requestAccountsPermissionWithId('foo.com'); + }).removePermittedAccount('foo.com', '0x1'); - expect(permissionController.requestPermissions).toHaveBeenCalledTimes(1); - expect(permissionController.requestPermissions).toHaveBeenCalledWith( - { origin: 'foo.com' }, - { eth_accounts: {} }, - { id: expect.any(String) }, + expect(permissionController.revokePermission).toHaveBeenCalledWith( + 'foo.com', + Caip25EndowmentPermissionName, ); + }); - expect(id.length > 0).toBe(true); - expect(id).toStrictEqual( - permissionController.requestPermissions.mock.calls[0][2].id, + it('updates the caveat with the account removed and all eip155 accounts synced', () => { + const permissionController = { + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x2', 'eip155:1:0x3'], + }, + }, + isMultichainOrigin: true, + }, + }), + updateCaveat: jest.fn(), + }; + + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedAccount('foo.com', '0x2'); + + expect(permissionController.updateCaveat).toHaveBeenCalledWith( + 'foo.com', + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x3'], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x3'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x3'], + }, + }, + isMultichainOrigin: true, + }, ); }); }); describe('requestAccountsAndChainPermissionsWithId', () => { - it('request eth_accounts and permittedChains permissions and returns the request id', async () => { + it('requests eth_accounts and permittedChains approval and returns the request id', async () => { + const approvalController = { + addAndShowApprovalRequest: jest.fn().mockResolvedValue({ + approvedChainIds: ['0x1', '0x5'], + approvedAccounts: ['0xdeadbeef'], + }), + }; const permissionController = { - requestPermissions: jest - .fn() - .mockImplementationOnce(async (_, __, { id }) => { - return [null, { id }]; - }), + grantPermissions: jest.fn(), }; - const id = await getPermissionBackgroundApiMethods( + const result = getPermissionBackgroundApiMethods({ + approvalController, permissionController, - ).requestAccountsAndChainPermissionsWithId('foo.com'); + }).requestAccountsAndChainPermissionsWithId('foo.com'); - expect(permissionController.requestPermissions).toHaveBeenCalledTimes(1); - expect(permissionController.requestPermissions).toHaveBeenCalledWith( - { origin: 'foo.com' }, + const { id } = + approvalController.addAndShowApprovalRequest.mock.calls[0][0]; + + expect(result).toStrictEqual(id); + expect(approvalController.addAndShowApprovalRequest).toHaveBeenCalledWith( { - [PermissionNames.eth_accounts]: {}, - [PermissionNames.permittedChains]: {}, + id, + origin: 'foo.com', + requestData: { + metadata: { + id, + origin: 'foo.com', + }, + permissions: { + [RestrictedMethods.eth_accounts]: {}, + [PermissionNames.permittedChains]: {}, + }, + }, + type: MethodNames.RequestPermissions, }, - { id: expect.any(String) }, ); + }); - expect(id.length > 0).toBe(true); - expect(id).toStrictEqual( - permissionController.requestPermissions.mock.calls[0][2].id, - ); + it('grants a legacy CAIP-25 permission (isMultichainOrigin: false) with the approved eip155 chainIds and accounts', async () => { + const approvalController = { + addAndShowApprovalRequest: jest.fn().mockResolvedValue({ + approvedChainIds: ['0x1', '0x5'], + approvedAccounts: ['0xdeadbeef'], + }), + }; + const permissionController = { + grantPermissions: jest.fn(), + }; + + getPermissionBackgroundApiMethods({ + approvalController, + permissionController, + }).requestAccountsAndChainPermissionsWithId('foo.com'); + + await flushPromises(); + + expect(permissionController.grantPermissions).toHaveBeenCalledWith({ + subject: { + origin: 'foo.com', + }, + approvedPermissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdeadbeef'], + }, + 'eip155:5': { + accounts: ['eip155:5:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }); }); }); describe('addPermittedChain', () => { - it('calls grantPermissionsIncremental with expected parameters', () => { + it('gets the CAIP-25 caveat', () => { const permissionController = { - grantPermissionsIncremental: jest.fn(), + getCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods(permissionController).addPermittedChain( + try { + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedChain('foo.com', '0x1'); + } catch (err) { + // noop + } + + expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', - '0x1', + Caip25EndowmentPermissionName, + Caip25CaveatType, ); - - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledTimes(1); - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledWith({ - subject: { origin: 'foo.com' }, - approvedPermissions: getPermittedChainsPermissions(['0x1']), - }); }); - }); - describe('addPermittedChains', () => { - it('calls grantPermissionsIncremental with expected parameters for single chain', () => { + it('throws an error if there is no existing CAIP-25 caveat', () => { const permissionController = { - grantPermissionsIncremental: jest.fn(), + getCaveat: jest.fn().mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }), }; - getPermissionBackgroundApiMethods( - permissionController, - ).addPermittedChains('foo.com', ['0x1']); - - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledTimes(1); - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledWith({ - subject: { origin: 'foo.com' }, - approvedPermissions: getPermittedChainsPermissions(['0x1']), - }); + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedChain('foo.com', '0x1'), + ).toThrow( + new Error( + `Cannot add chain permissions for origin "foo.com": no permission currently exists for this origin.`, + ), + ); }); - it('calls grantPermissionsIncremental with expected parameters with multiple chains', () => { + it('throws an error if getCaveat fails unexpectedly', () => { const permissionController = { - grantPermissionsIncremental: jest.fn(), + getCaveat: jest.fn().mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }), }; - getPermissionBackgroundApiMethods( - permissionController, - ).addPermittedChains('foo.com', ['0x1', '0x2']); - - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledTimes(1); - expect( - permissionController.grantPermissionsIncremental, - ).toHaveBeenCalledWith({ - subject: { origin: 'foo.com' }, - approvedPermissions: getPermittedChainsPermissions(['0x1', '0x2']), - }); + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedChain('foo.com', '0x1'), + ).toThrow(new Error(`unexpected getCaveat error`)); }); - }); - describe('removePermittedChain', () => { - it('removes a permitted chain', () => { + it('calls updateCaveat with the chain added and all eip155 accounts synced', () => { const permissionController = { - getCaveat: jest.fn().mockImplementationOnce(() => { - return { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1', '0x2'], - }; + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x2'], + }, + }, + isMultichainOrigin: true, + }, }), - revokePermission: jest.fn(), updateCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).removePermittedChain('foo.com', '0x2'); + }).addPermittedChain('foo.com', '0x539'); // 1337 - expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.getCaveat).toHaveBeenCalledWith( + expect(permissionController.updateCaveat).toHaveBeenCalledTimes(1); + expect(permissionController.updateCaveat).toHaveBeenCalledWith( 'foo.com', - PermissionNames.permittedChains, - CaveatTypes.restrictNetworkSwitching, + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + 'eip155:1337': { + accounts: ['eip155:1337:0x1', 'eip155:1337:0x2'], + }, + }, + isMultichainOrigin: true, + }, ); + }); + }); - expect(permissionController.revokePermission).not.toHaveBeenCalled(); + describe('addPermittedChains', () => { + it('gets the CAIP-25 caveat', () => { + const permissionController = { + getCaveat: jest.fn(), + }; - expect(permissionController.updateCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.updateCaveat).toHaveBeenCalledWith( + try { + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedChains('foo.com', ['0x1']); + } catch (err) { + // noop + } + + expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', - PermissionNames.permittedChains, - CaveatTypes.restrictNetworkSwitching, - ['0x1'], + Caip25EndowmentPermissionName, + Caip25CaveatType, ); }); - it('revokes the permittedChains permission if the removed chain is the only permitted chain', () => { + it('throws an error if there is no existing CAIP-25 caveat', () => { const permissionController = { - getCaveat: jest.fn().mockImplementationOnce(() => { - return { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1'], - }; + getCaveat: jest.fn().mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedChains('foo.com', ['0x1']), + ).toThrow( + new Error( + `Cannot add chain permissions for origin "foo.com": no permission currently exists for this origin.`, + ), + ); + }); + + it('throws an error if getCaveat fails unexpectedly', () => { + const permissionController = { + getCaveat: jest.fn().mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).addPermittedChains('foo.com', ['0x1']), + ).toThrow(new Error(`unexpected getCaveat error`)); + }); + + it('calls updateCaveat with the chains added and all eip155 accounts synced', () => { + const permissionController = { + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x2'], + }, + }, + isMultichainOrigin: true, + }, }), - revokePermission: jest.fn(), updateCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).removePermittedChain('foo.com', '0x1'); + }).addPermittedChains('foo.com', ['0x4', '0x5']); - expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.getCaveat).toHaveBeenCalledWith( + expect(permissionController.updateCaveat).toHaveBeenCalledTimes(1); + expect(permissionController.updateCaveat).toHaveBeenCalledWith( 'foo.com', - PermissionNames.permittedChains, - CaveatTypes.restrictNetworkSwitching, + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + 'eip155:4': { + accounts: ['eip155:4:0x1', 'eip155:4:0x2'], + }, + 'eip155:5': { + accounts: ['eip155:5:0x1', 'eip155:5:0x2'], + }, + }, + isMultichainOrigin: true, + }, ); + }); + }); - expect(permissionController.revokePermission).toHaveBeenCalledTimes(1); - expect(permissionController.revokePermission).toHaveBeenCalledWith( + describe('removePermittedChain', () => { + it('gets the CAIP-25 caveat', () => { + const permissionController = { + getCaveat: jest.fn(), + }; + + try { + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedChain('foo.com', '0x1'); + } catch (err) { + // noop + } + + expect(permissionController.getCaveat).toHaveBeenCalledWith( 'foo.com', - PermissionNames.permittedChains, + Caip25EndowmentPermissionName, + Caip25CaveatType, ); + }); + + it('throws an error if there is no existing CAIP-25 caveat', () => { + const permissionController = { + getCaveat: jest.fn().mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedChain('foo.com', '0x1'), + ).toThrow( + new Error( + `Cannot remove permission for chainId "0x1": No permissions exist for origin "foo.com".`, + ), + ); + }); + + it('throws an error if getCaveat fails unexpectedly', () => { + const permissionController = { + getCaveat: jest.fn().mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }), + }; + + expect(() => + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedChain('foo.com', '0x1'), + ).toThrow(new Error(`unexpected getCaveat error`)); + }); + + it('does nothing if the chain being removed does not exist', () => { + const permissionController = { + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + }, + isMultichainOrigin: true, + }, + }), + updateCaveat: jest.fn(), + revokePermission: jest.fn(), + }; + + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedChain('foo.com', '0xdeadbeef'); expect(permissionController.updateCaveat).not.toHaveBeenCalled(); + expect(permissionController.revokePermission).not.toHaveBeenCalled(); }); - it('does not call permissionController.updateCaveat if the specified chain is not permitted', () => { + it('revokes the entire permission if the removed chain is the only eip:155 scope', () => { const permissionController = { - getCaveat: jest.fn().mockImplementationOnce(() => { - return { type: CaveatTypes.restrictNetworkSwitching, value: ['0x1'] }; + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': {}, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + }, + isMultichainOrigin: true, + }, }), revokePermission: jest.fn(), - updateCaveat: jest.fn(), }; - getPermissionBackgroundApiMethods( + getPermissionBackgroundApiMethods({ permissionController, - ).removePermittedChain('foo.com', '0x2'); - expect(permissionController.getCaveat).toHaveBeenCalledTimes(1); - expect(permissionController.getCaveat).toHaveBeenCalledWith( + }).removePermittedChain('foo.com', '0x1'); + + expect(permissionController.revokePermission).toHaveBeenCalledWith( 'foo.com', - PermissionNames.permittedChains, - CaveatTypes.restrictNetworkSwitching, + Caip25EndowmentPermissionName, ); + }); - expect(permissionController.revokePermission).not.toHaveBeenCalled(); - expect(permissionController.updateCaveat).not.toHaveBeenCalled(); + it('updates the caveat with the chain removed', () => { + const permissionController = { + getCaveat: jest.fn().mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:10': { + accounts: ['eip155:10:0x1', 'eip155:10:0x2'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + }, + isMultichainOrigin: true, + }, + }), + updateCaveat: jest.fn(), + }; + + getPermissionBackgroundApiMethods({ + permissionController, + }).removePermittedChain('foo.com', '0xa'); // 10 + + expect(permissionController.updateCaveat).toHaveBeenCalledWith( + 'foo.com', + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + }, + isMultichainOrigin: true, + }, + ); }); }); }); diff --git a/app/scripts/controllers/permissions/caveat-mutators.js b/app/scripts/controllers/permissions/caveat-mutators.js deleted file mode 100644 index 551d1f7b37b2..000000000000 --- a/app/scripts/controllers/permissions/caveat-mutators.js +++ /dev/null @@ -1,71 +0,0 @@ -import { CaveatMutatorOperation } from '@metamask/permission-controller'; -import { CaveatTypes } from '../../../../shared/constants/permissions'; -import { normalizeSafeAddress } from '../../lib/multichain/address'; - -/** - * Factories that construct caveat mutator functions that are passed to - * PermissionController.updatePermissionsByCaveat. - */ -export const CaveatMutatorFactories = { - [CaveatTypes.restrictReturnedAccounts]: { - removeAccount, - }, - [CaveatTypes.restrictNetworkSwitching]: { - removeChainId, - }, -}; - -/** - * Removes the target account from the value arrays of all - * `restrictReturnedAccounts` caveats. No-ops if the target account is not in - * the array, and revokes the parent permission if it's the only account in - * the array. - * - * @param {string} targetAccount - The address of the account to remove from - * all accounts permissions. - * @param {string[]} existingAccounts - The account address array from the - * account permissions. - */ -function removeAccount(targetAccount, existingAccounts) { - const checkSumTargetAccount = normalizeSafeAddress(targetAccount); - const newAccounts = existingAccounts.filter( - (address) => normalizeSafeAddress(address) !== checkSumTargetAccount, - ); - - if (newAccounts.length === existingAccounts.length) { - return { operation: CaveatMutatorOperation.noop }; - } else if (newAccounts.length > 0) { - return { - operation: CaveatMutatorOperation.updateValue, - value: newAccounts, - }; - } - return { operation: CaveatMutatorOperation.revokePermission }; -} - -/** - * Removes the target chain ID from the value arrays of all - * `restrictNetworkSwitching` caveats. No-ops if the target chain ID is not in - * the array, and revokes the parent permission if it's the only chain ID in - * the array. - * - * @param {string} targetChainId - The chain ID to remove from - * all network switching permissions. - * @param {string[]} existingChainIds - The chain ID array from the - * network switching permissions. - */ -function removeChainId(targetChainId, existingChainIds) { - const newChainIds = existingChainIds.filter( - (chainId) => chainId !== targetChainId, - ); - - if (newChainIds.length === existingChainIds.length) { - return { operation: CaveatMutatorOperation.noop }; - } else if (newChainIds.length > 0) { - return { - operation: CaveatMutatorOperation.updateValue, - value: newChainIds, - }; - } - return { operation: CaveatMutatorOperation.revokePermission }; -} diff --git a/app/scripts/controllers/permissions/caveat-mutators.test.js b/app/scripts/controllers/permissions/caveat-mutators.test.js deleted file mode 100644 index a87115dc744b..000000000000 --- a/app/scripts/controllers/permissions/caveat-mutators.test.js +++ /dev/null @@ -1,67 +0,0 @@ -import { CaveatMutatorOperation } from '@metamask/permission-controller'; -import { CaveatTypes } from '../../../../shared/constants/permissions'; -import { CaveatMutatorFactories } from './caveat-mutators'; - -const address1 = '0xbf16f7f5db8ae6af2512399bace3101debbde7fc'; -const address2 = '0xb6d5abeca51bfc3d53d00afed06b17eeea32ecdf'; -const nonEvmAddress = 'bc1qdkwac3em6mvlur4fatn2g4q050f4kkqadrsmnp'; - -describe('caveat mutators', () => { - describe('restrictReturnedAccounts', () => { - const { removeAccount } = - CaveatMutatorFactories[CaveatTypes.restrictReturnedAccounts]; - - describe('removeAccount', () => { - it('returns the no-op operation if the target account is not permitted', () => { - expect(removeAccount(address2, [address1])).toStrictEqual({ - operation: CaveatMutatorOperation.noop, - }); - }); - - it('returns the update operation and a new value if the target account is permitted', () => { - expect(removeAccount(address2, [address1, address2])).toStrictEqual({ - operation: CaveatMutatorOperation.updateValue, - value: [address1], - }); - }); - - it('returns the revoke permission operation the target account is the only permitted account', () => { - expect(removeAccount(address1, [address1])).toStrictEqual({ - operation: CaveatMutatorOperation.revokePermission, - }); - }); - - it('returns the revoke permission operation even if the target account is a checksummed address', () => { - const address3 = '0x95222290dd7278aa3ddd389cc1e1d165cc4baee5'; - const checksummedAddress3 = - '0x95222290dd7278AA3DDd389cc1E1d165Cc4BaeE5'; - expect(removeAccount(checksummedAddress3, [address3])).toStrictEqual({ - operation: CaveatMutatorOperation.revokePermission, - }); - }); - - describe('Multichain behaviour', () => { - it('returns the no-op operation if the target account is not permitted', () => { - expect(removeAccount(address2, [nonEvmAddress])).toStrictEqual({ - operation: CaveatMutatorOperation.noop, - }); - }); - - it('can revoke permission for non-EVM addresses', () => { - expect(removeAccount(nonEvmAddress, [nonEvmAddress])).toStrictEqual({ - operation: CaveatMutatorOperation.revokePermission, - }); - }); - - it('returns the update operation and a new value if the target non-EVM account is permitted', () => { - expect( - removeAccount(nonEvmAddress, [address1, nonEvmAddress]), - ).toStrictEqual({ - operation: CaveatMutatorOperation.updateValue, - value: [address1], - }); - }); - }); - }); - }); -}); diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js index b0ec94b175f1..76a460487dfe 100644 --- a/app/scripts/controllers/permissions/index.js +++ b/app/scripts/controllers/permissions/index.js @@ -1,4 +1,3 @@ -export * from './caveat-mutators'; export * from './background-api'; export * from './enums'; export * from './specifications'; diff --git a/app/scripts/controllers/permissions/selectors.js b/app/scripts/controllers/permissions/selectors.js index 76e638d25b54..97464885b7a6 100644 --- a/app/scripts/controllers/permissions/selectors.js +++ b/app/scripts/controllers/permissions/selectors.js @@ -1,6 +1,10 @@ import { createSelector } from 'reselect'; -import { CaveatTypes } from '../../../../shared/constants/permissions'; -import { PermissionNames } from './specifications'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, + getEthAccounts, + getPermittedEthChainIds, +} from '@metamask/multichain'; /** * This file contains selectors for PermissionController selector event @@ -26,14 +30,14 @@ export const getPermittedAccountsByOrigin = createSelector( getSubjects, (subjects) => { return Object.values(subjects).reduce((originToAccountsMap, subject) => { - const caveats = subject.permissions?.eth_accounts?.caveats || []; + const caveats = + subject.permissions?.[Caip25EndowmentPermissionName]?.caveats || []; - const caveat = caveats.find( - ({ type }) => type === CaveatTypes.restrictReturnedAccounts, - ); + const caveat = caveats.find(({ type }) => type === Caip25CaveatType); if (caveat) { - originToAccountsMap.set(subject.origin, caveat.value); + const ethAccounts = getEthAccounts(caveat.value); + originToAccountsMap.set(subject.origin, ethAccounts); } return originToAccountsMap; }, new Map()); @@ -52,14 +56,13 @@ export const getPermittedChainsByOrigin = createSelector( (subjects) => { return Object.values(subjects).reduce((originToChainsMap, subject) => { const caveats = - subject.permissions?.[PermissionNames.permittedChains]?.caveats || []; + subject.permissions?.[Caip25EndowmentPermissionName]?.caveats || []; - const caveat = caveats.find( - ({ type }) => type === CaveatTypes.restrictNetworkSwitching, - ); + const caveat = caveats.find(({ type }) => type === Caip25CaveatType); if (caveat) { - originToChainsMap.set(subject.origin, caveat.value); + const ethChainIds = getPermittedEthChainIds(caveat.value); + originToChainsMap.set(subject.origin, ethChainIds); } return originToChainsMap; }, new Map()); diff --git a/app/scripts/controllers/permissions/selectors.test.js b/app/scripts/controllers/permissions/selectors.test.js index 41264d405ab2..9a6cc10a9a07 100644 --- a/app/scripts/controllers/permissions/selectors.test.js +++ b/app/scripts/controllers/permissions/selectors.test.js @@ -1,11 +1,13 @@ import { cloneDeep } from 'lodash'; -import { CaveatTypes } from '../../../../shared/constants/permissions'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; import { diffMap, getPermittedAccountsByOrigin, getPermittedChainsByOrigin, } from './selectors'; -import { PermissionNames } from './specifications'; describe('PermissionController selectors', () => { describe('diffMap', () => { @@ -53,25 +55,72 @@ describe('PermissionController selectors', () => { 'foo.bar': { origin: 'foo.bar', permissions: { - eth_accounts: { - caveats: [{ type: 'restrictReturnedAccounts', value: ['0x1'] }], + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x1'], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6', + ], + }, + }, + isMultichainOrigin: true, + }, + }, + ], }, }, }, 'bar.baz': { origin: 'bar.baz', permissions: { - eth_accounts: { - caveats: [{ type: 'restrictReturnedAccounts', value: ['0x2'] }], + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x2'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], }, }, }, 'baz.bizz': { origin: 'baz.fizz', permissions: { - eth_accounts: { + [Caip25EndowmentPermissionName]: { caveats: [ - { type: 'restrictReturnedAccounts', value: ['0x1', '0x2'] }, + { + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x1'], + }, + }, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x2'], + }, + }, + isMultichainOrigin: false, + }, + }, ], }, }, @@ -125,11 +174,23 @@ describe('PermissionController selectors', () => { 'foo.bar': { origin: 'foo.bar', permissions: { - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1'], + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + }, + optionalScopes: { + 'bip122:000000000019d6689c085ae165831e93': { + accounts: [], + }, + }, + isMultichainOrigin: true, + }, }, ], }, @@ -138,11 +199,19 @@ describe('PermissionController selectors', () => { 'bar.baz': { origin: 'bar.baz', permissions: { - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x2'], + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:2': { + accounts: [], + }, + }, + optionalScopes: {}, + isMultichainOrigin: true, + }, }, ], }, @@ -151,17 +220,29 @@ describe('PermissionController selectors', () => { 'baz.bizz': { origin: 'baz.fizz', permissions: { - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1', '0x2'], + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + }, + optionalScopes: { + 'eip155:2': { + accounts: [], + }, + }, + isMultichainOrigin: true, + }, }, ], }, }, }, - 'no.accounts': { + 'no.chains': { // we shouldn't see this in the result permissions: { foobar: {}, diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index b0b2051b10f5..f4a1e3320172 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -1,14 +1,14 @@ -import { - constructPermission, - PermissionType, -} from '@metamask/permission-controller'; import { caveatSpecifications as snapsCaveatsSpecifications, endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications, } from '@metamask/snaps-rpc-methods'; -import { isValidHexAddress } from '@metamask/utils'; import { - CaveatTypes, + createCaip25Caveat, + Caip25CaveatType, + caip25EndowmentBuilder, + caip25CaveatBuilder, +} from '@metamask/multichain'; +import { EndowmentTypes, RestrictedMethods, } from '../../../../shared/constants/permissions'; @@ -33,58 +33,29 @@ export const PermissionNames = Object.freeze({ * PermissionController. */ export const CaveatFactories = Object.freeze({ - [CaveatTypes.restrictReturnedAccounts]: (accounts) => { - return { type: CaveatTypes.restrictReturnedAccounts, value: accounts }; - }, - - [CaveatTypes.restrictNetworkSwitching]: (chainIds) => { - return { type: CaveatTypes.restrictNetworkSwitching, value: chainIds }; - }, + [Caip25CaveatType]: createCaip25Caveat, }); /** * Gets the specifications for all caveats that will be recognized by the * PermissionController. * - * @param {{ - * getInternalAccounts: () => Record, - * }} options - Options bag. + * @param options - The options object. + * @param options.listAccounts - A function that returns the + * `AccountsController` internalAccount objects for all evm accounts. + * @param options.findNetworkClientIdByChainId - A function that + * returns the networkClientId given a chainId. + * @returns the caveat specifications to construct the PermissionController. */ export const getCaveatSpecifications = ({ - getInternalAccounts, + listAccounts, findNetworkClientIdByChainId, }) => { return { - [CaveatTypes.restrictReturnedAccounts]: { - type: CaveatTypes.restrictReturnedAccounts, - - decorator: (method, caveat) => { - return async (args) => { - const result = await method(args); - return result.filter((account) => caveat.value.includes(account)); - }; - }, - - validator: (caveat, _origin, _target) => - validateCaveatAccounts(caveat.value, getInternalAccounts), - - merger: (leftValue, rightValue) => { - const newValue = Array.from(new Set([...leftValue, ...rightValue])); - const diff = newValue.filter((value) => !leftValue.includes(value)); - return [newValue, diff]; - }, - }, - [CaveatTypes.restrictNetworkSwitching]: { - type: CaveatTypes.restrictNetworkSwitching, - validator: (caveat, _origin, _target) => - validateCaveatNetworks(caveat.value, findNetworkClientIdByChainId), - merger: (leftValue, rightValue) => { - const newValue = Array.from(new Set([...leftValue, ...rightValue])); - const diff = newValue.filter((value) => !leftValue.includes(value)); - return [newValue, diff]; - }, - }, - + [Caip25CaveatType]: caip25CaveatBuilder({ + listAccounts, + findNetworkClientIdByChainId, + }), ...snapsCaveatsSpecifications, ...snapsEndowmentCaveatSpecifications, }; @@ -94,227 +65,15 @@ export const getCaveatSpecifications = ({ * Gets the specifications for all permissions that will be recognized by the * PermissionController. * - * @param {{ - * getAllAccounts: () => Promise, - * getInternalAccounts: () => Record, - * }} options - Options bag. - * @param options.getAllAccounts - A function that returns all Ethereum accounts - * in the current MetaMask instance. - * @param options.getInternalAccounts - A function that returns the - * `AccountsController` internalAccount objects for all accounts in the - * @param options.captureKeyringTypesWithMissingIdentities - A function that - * captures extra error information about the "Missing identity for address" - * error. - * current MetaMask instance. + * @returns the permission specifications to construct the PermissionController. */ -export const getPermissionSpecifications = ({ - getAllAccounts, - getInternalAccounts, - captureKeyringTypesWithMissingIdentities, -}) => { +export const getPermissionSpecifications = () => { return { - [PermissionNames.eth_accounts]: { - permissionType: PermissionType.RestrictedMethod, - targetName: PermissionNames.eth_accounts, - allowedCaveats: [CaveatTypes.restrictReturnedAccounts], - - factory: (permissionOptions, requestData) => { - // This occurs when we use PermissionController.grantPermissions(). - if (requestData === undefined) { - return constructPermission({ - ...permissionOptions, - }); - } - - // The approved accounts will be further validated as part of the caveat. - if (!requestData.approvedAccounts) { - throw new Error( - `${PermissionNames.eth_accounts} error: No approved accounts specified.`, - ); - } - - return constructPermission({ - ...permissionOptions, - caveats: [ - CaveatFactories[CaveatTypes.restrictReturnedAccounts]( - requestData.approvedAccounts, - ), - ], - }); - }, - methodImplementation: async (_args) => { - // We only consider EVM addresses here, hence the filtering: - const accounts = (await getAllAccounts()).filter(isValidHexAddress); - const internalAccounts = getInternalAccounts(); - - return accounts.sort((firstAddress, secondAddress) => { - const firstAccount = internalAccounts.find( - (internalAccount) => - internalAccount.address.toLowerCase() === - firstAddress.toLowerCase(), - ); - - const secondAccount = internalAccounts.find( - (internalAccount) => - internalAccount.address.toLowerCase() === - secondAddress.toLowerCase(), - ); - - if (!firstAccount) { - captureKeyringTypesWithMissingIdentities( - internalAccounts, - accounts, - ); - throw new Error(`Missing identity for address: "${firstAddress}".`); - } else if (!secondAccount) { - captureKeyringTypesWithMissingIdentities( - internalAccounts, - accounts, - ); - throw new Error( - `Missing identity for address: "${secondAddress}".`, - ); - } else if ( - firstAccount.metadata.lastSelected === - secondAccount.metadata.lastSelected - ) { - return 0; - } else if (firstAccount.metadata.lastSelected === undefined) { - return 1; - } else if (secondAccount.metadata.lastSelected === undefined) { - return -1; - } - - return ( - secondAccount.metadata.lastSelected - - firstAccount.metadata.lastSelected - ); - }); - }, - validator: (permission, _origin, _target) => { - const { caveats } = permission; - if ( - !caveats || - caveats.length !== 1 || - caveats[0].type !== CaveatTypes.restrictReturnedAccounts - ) { - throw new Error( - `${PermissionNames.eth_accounts} error: Invalid caveats. There must be a single caveat of type "${CaveatTypes.restrictReturnedAccounts}".`, - ); - } - }, - }, - - [PermissionNames.permittedChains]: { - permissionType: PermissionType.Endowment, - targetName: PermissionNames.permittedChains, - allowedCaveats: [CaveatTypes.restrictNetworkSwitching], - - factory: (permissionOptions, requestData) => { - if (requestData === undefined) { - return constructPermission({ - ...permissionOptions, - }); - } - if (!requestData.approvedChainIds) { - throw new Error( - `${PermissionNames.permittedChains}: No approved networks specified.`, - ); - } - - return constructPermission({ - ...permissionOptions, - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]( - requestData.approvedChainIds, - ), - ], - }); - }, - endowmentGetter: async (_getterOptions) => undefined, - validator: (permission, _origin, _target) => { - const { caveats } = permission; - if ( - !caveats || - caveats.length !== 1 || - caveats[0].type !== CaveatTypes.restrictNetworkSwitching - ) { - throw new Error( - `${PermissionNames.permittedChains} error: Invalid caveats. There must be a single caveat of type "${CaveatTypes.restrictNetworkSwitching}".`, - ); - } - }, - }, + [caip25EndowmentBuilder.targetName]: + caip25EndowmentBuilder.specificationBuilder({}), }; }; -/** - * Validates the accounts associated with a caveat. In essence, ensures that - * the accounts value is an array of non-empty strings, and that each string - * corresponds to a PreferencesController identity. - * - * @param {string[]} accounts - The accounts associated with the caveat. - * @param {() => Record} getInternalAccounts - - * Gets all AccountsController InternalAccounts. - */ -function validateCaveatAccounts(accounts, getInternalAccounts) { - if (!Array.isArray(accounts) || accounts.length === 0) { - throw new Error( - `${PermissionNames.eth_accounts} error: Expected non-empty array of Ethereum addresses.`, - ); - } - - const internalAccounts = getInternalAccounts(); - accounts.forEach((address) => { - if (!address || typeof address !== 'string') { - throw new Error( - `${PermissionNames.eth_accounts} error: Expected an array of Ethereum addresses. Received: "${address}".`, - ); - } - - if ( - !internalAccounts.some( - (internalAccount) => - internalAccount.address.toLowerCase() === address.toLowerCase(), - ) - ) { - throw new Error( - `${PermissionNames.eth_accounts} error: Received unrecognized address: "${address}".`, - ); - } - }); -} - -/** - * Validates the networks associated with a caveat. Ensures that - * the networks value is an array of valid chain IDs. - * - * @param {string[]} chainIdsForCaveat - The list of chain IDs to validate. - * @param {function(string): string} findNetworkClientIdByChainId - Function to find network client ID by chain ID. - * @throws {Error} If the chainIdsForCaveat is not a non-empty array of valid chain IDs. - */ -function validateCaveatNetworks( - chainIdsForCaveat, - findNetworkClientIdByChainId, -) { - if (!Array.isArray(chainIdsForCaveat) || chainIdsForCaveat.length === 0) { - throw new Error( - `${PermissionNames.permittedChains} error: Expected non-empty array of chainIds.`, - ); - } - - chainIdsForCaveat.forEach((chainId) => { - try { - findNetworkClientIdByChainId(chainId); - } catch (e) { - console.error(e); - throw new Error( - `${PermissionNames.permittedChains} error: Received unrecognized chainId: "${chainId}". Please try adding the network first via wallet_addEthereumChain.`, - ); - } - }); -} - /** * Unrestricted methods for Ethereum, see {@link unrestrictedMethods} for more details. */ @@ -408,13 +167,19 @@ export const unrestrictedMethods = Object.freeze([ 'wallet_invokeSnap', 'wallet_invokeKeyring', 'snap_getClientStatus', + 'snap_getCurrencyRate', + 'snap_clearState', 'snap_getFile', + 'snap_getState', 'snap_createInterface', 'snap_updateInterface', 'snap_getInterfaceState', 'snap_getInterfaceContext', 'snap_resolveInterface', - 'snap_getCurrencyRate', + 'snap_setState', + 'snap_scheduleBackgroundEvent', + 'snap_cancelBackgroundEvent', + 'snap_getBackgroundEvents', ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) 'metamaskinstitutional_authenticate', 'metamaskinstitutional_reauthenticate', diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js index b27ec07a45b1..e0b3f1623ccd 100644 --- a/app/scripts/controllers/permissions/specifications.test.js +++ b/app/scripts/controllers/permissions/specifications.test.js @@ -1,15 +1,11 @@ -import { EthAccountType } from '@metamask/keyring-api'; import { SnapCaveatType } from '@metamask/snaps-rpc-methods'; import { - CaveatTypes, - RestrictedMethods, -} from '../../../../shared/constants/permissions'; -import { ETH_EOA_METHODS } from '../../../../shared/constants/eth-methods'; + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; import { - CaveatFactories, getCaveatSpecifications, getPermissionSpecifications, - PermissionNames, unrestrictedMethods, } from './specifications'; @@ -20,13 +16,10 @@ describe('PermissionController specifications', () => { describe('caveat specifications', () => { it('getCaveatSpecifications returns the expected specifications object', () => { const caveatSpecifications = getCaveatSpecifications({}); - expect(Object.keys(caveatSpecifications)).toHaveLength(13); - expect( - caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type, - ).toStrictEqual(CaveatTypes.restrictReturnedAccounts); - expect( - caveatSpecifications[CaveatTypes.restrictNetworkSwitching].type, - ).toStrictEqual(CaveatTypes.restrictNetworkSwitching); + expect(Object.keys(caveatSpecifications)).toHaveLength(12); + expect(caveatSpecifications[Caip25CaveatType].type).toStrictEqual( + Caip25CaveatType, + ); expect(caveatSpecifications.permittedDerivationPaths.type).toStrictEqual( SnapCaveatType.PermittedDerivationPaths, @@ -62,537 +55,15 @@ describe('PermissionController specifications', () => { SnapCaveatType.LookupMatchers, ); }); - - describe('restrictReturnedAccounts', () => { - describe('decorator', () => { - it('only returns array members included in the caveat value', async () => { - const getInternalAccounts = jest.fn(); - const { decorator } = getCaveatSpecifications({ - getInternalAccounts, - })[CaveatTypes.restrictReturnedAccounts]; - - const method = async () => ['0x1', '0x2', '0x3']; - const caveat = { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x3'], - }; - const decorated = decorator(method, caveat); - expect(await decorated()).toStrictEqual(['0x1', '0x3']); - }); - - it('returns an empty array if no array members are included in the caveat value', async () => { - const getInternalAccounts = jest.fn(); - const { decorator } = getCaveatSpecifications({ - getInternalAccounts, - })[CaveatTypes.restrictReturnedAccounts]; - - const method = async () => ['0x1', '0x2', '0x3']; - const caveat = { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x5'], - }; - const decorated = decorator(method, caveat); - expect(await decorated()).toStrictEqual([]); - }); - - it('returns an empty array if the method result is an empty array', async () => { - const getInternalAccounts = jest.fn(); - const { decorator } = getCaveatSpecifications({ - getInternalAccounts, - })[CaveatTypes.restrictReturnedAccounts]; - - const method = async () => []; - const caveat = { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x2'], - }; - const decorated = decorator(method, caveat); - expect(await decorated()).toStrictEqual([]); - }); - }); - - describe('validator', () => { - it('rejects invalid array values', () => { - const getInternalAccounts = jest.fn(); - const { validator } = getCaveatSpecifications({ - getInternalAccounts, - })[CaveatTypes.restrictReturnedAccounts]; - - [null, 'foo', {}, []].forEach((invalidValue) => { - expect(() => validator({ value: invalidValue })).toThrow( - /Expected non-empty array of Ethereum addresses\.$/u, - ); - }); - }); - - it('rejects falsy or non-string addresses', () => { - const getInternalAccounts = jest.fn(); - const { validator } = getCaveatSpecifications({ - getInternalAccounts, - })[CaveatTypes.restrictReturnedAccounts]; - - [[{}], [[]], [null], ['']].forEach((invalidValue) => { - expect(() => validator({ value: invalidValue })).toThrow( - /Expected an array of Ethereum addresses. Received:/u, - ); - }); - }); - - it('rejects addresses that have no corresponding identity', () => { - const getInternalAccounts = jest.fn().mockImplementationOnce(() => { - return [ - { - address: '0x1', - id: '21066553-d8c8-4cdc-af33-efc921cd3ca9', - metadata: { - name: 'Test Account 1', - lastSelected: 1, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - { - address: '0x3', - id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', - metadata: { - name: 'Test Account 3', - lastSelected: 3, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - ]; - }); - - const { validator } = getCaveatSpecifications({ - getInternalAccounts, - })[CaveatTypes.restrictReturnedAccounts]; - - expect(() => validator({ value: ['0x1', '0x2', '0x3'] })).toThrow( - /Received unrecognized address:/u, - ); - }); - }); - - describe('merger', () => { - it.each([ - { - left: [], - right: [], - expected: [[], []], - }, - { - left: ['0x1'], - right: [], - expected: [['0x1'], []], - }, - { - left: [], - right: ['0x1'], - expected: [['0x1'], ['0x1']], - }, - { - left: ['0x1', '0x2'], - right: ['0x1', '0x2'], - expected: [['0x1', '0x2'], []], - }, - { - left: ['0x1', '0x2'], - right: ['0x2', '0x3'], - expected: [['0x1', '0x2', '0x3'], ['0x3']], - }, - { - left: ['0x1', '0x2'], - right: ['0x3', '0x4'], - expected: [ - ['0x1', '0x2', '0x3', '0x4'], - ['0x3', '0x4'], - ], - }, - { - left: [{ a: 1 }, { b: 2 }], - right: [{ a: 1 }], - expected: [[{ a: 1 }, { b: 2 }, { a: 1 }], [{ a: 1 }]], - }, - ])('merges arrays as expected', ({ left, right, expected }) => { - const { merger } = getCaveatSpecifications({})[ - CaveatTypes.restrictReturnedAccounts - ]; - - expect(merger(left, right)).toStrictEqual(expected); - }); - }); - }); }); describe('permission specifications', () => { it('getPermissionSpecifications returns the expected specifications object', () => { const permissionSpecifications = getPermissionSpecifications({}); - expect(Object.keys(permissionSpecifications)).toHaveLength(2); + expect(Object.keys(permissionSpecifications)).toHaveLength(1); expect( - permissionSpecifications[RestrictedMethods.eth_accounts].targetName, - ).toStrictEqual(RestrictedMethods.eth_accounts); - expect( - permissionSpecifications[PermissionNames.permittedChains].targetName, - ).toStrictEqual('endowment:permitted-chains'); - }); - - describe('eth_accounts', () => { - describe('factory', () => { - it('constructs a valid eth_accounts permission, using permissionOptions', () => { - const getInternalAccounts = jest.fn(); - const getAllAccounts = jest.fn(); - const { factory } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - expect( - factory({ - invoker: 'foo.bar', - target: 'eth_accounts', - caveats: [ - CaveatFactories[CaveatTypes.restrictReturnedAccounts](['0x1']), - ], - }), - ).toStrictEqual({ - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1'], - }, - ], - date: 1, - id: expect.any(String), - invoker: 'foo.bar', - parentCapability: 'eth_accounts', - }); - }); - - it('constructs a valid eth_accounts permission, using requestData.approvedAccounts', () => { - const getInternalAccounts = jest.fn(); - const getAllAccounts = jest.fn(); - const { factory } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - expect( - factory( - { invoker: 'foo.bar', target: 'eth_accounts' }, - { approvedAccounts: ['0x1'] }, - ), - ).toStrictEqual({ - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1'], - }, - ], - date: 1, - id: expect.any(String), - invoker: 'foo.bar', - parentCapability: 'eth_accounts', - }); - }); - - it('throws if requestData is defined but approvedAccounts is not specified', () => { - const getInternalAccounts = jest.fn(); - const getAllAccounts = jest.fn(); - const { factory } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - expect(() => - factory( - { invoker: 'foo.bar', target: 'eth_accounts' }, - {}, // no approvedAccounts - ), - ).toThrow(/No approved accounts specified\.$/u); - }); - - it('prefers requestData.approvedAccounts over a specified caveat', () => { - const getInternalAccounts = jest.fn(); - const getAllAccounts = jest.fn(); - const { factory } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - expect( - factory( - { - caveats: [ - CaveatFactories[CaveatTypes.restrictReturnedAccounts]([ - '0x1', - '0x2', - ]), - ], - invoker: 'foo.bar', - target: 'eth_accounts', - }, - { approvedAccounts: ['0x1', '0x3'] }, - ), - ).toStrictEqual({ - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x3'], - }, - ], - date: 1, - id: expect.any(String), - invoker: 'foo.bar', - parentCapability: 'eth_accounts', - }); - }); - }); - - describe('methodImplementation', () => { - it('returns the keyring accounts in lastSelected order', async () => { - const getInternalAccounts = jest.fn().mockImplementationOnce(() => { - return [ - { - address: '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', - id: '21066553-d8c8-4cdc-af33-efc921cd3ca9', - metadata: { - name: 'Test Account', - lastSelected: 1, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - { - address: '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', - id: '0bd7348e-bdfe-4f67-875c-de831a583857', - metadata: { - name: 'Test Account', - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - { - address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', - metadata: { - name: 'Test Account', - keyring: { - type: 'HD Key Tree', - }, - lastSelected: 3, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - { - address: '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', - id: '0bd7348e-bdfe-4f67-875c-de831a583857', - metadata: { - name: 'Test Account', - lastSelected: 3, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - ]; - }); - const getAllAccounts = jest - .fn() - .mockImplementationOnce(() => [ - '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', - '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', - '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', - ]); - - const { methodImplementation } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - expect(await methodImplementation()).toStrictEqual([ - '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', - '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', - '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', - ]); - }); - - it('throws if a keyring account is missing an address (case 1)', async () => { - const getInternalAccounts = jest.fn().mockImplementationOnce(() => { - return [ - { - address: '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', - id: '0bd7348e-bdfe-4f67-875c-de831a583857', - metadata: { - name: 'Test Account', - lastSelected: 2, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - { - address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', - metadata: { - name: 'Test Account', - lastSelected: 3, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - ]; - }); - const getAllAccounts = jest - .fn() - .mockImplementationOnce(() => [ - '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', - '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', - '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - ]); - - const { methodImplementation } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - captureKeyringTypesWithMissingIdentities: jest.fn(), - })[RestrictedMethods.eth_accounts]; - - await expect(() => methodImplementation()).rejects.toThrow( - 'Missing identity for address: "0x7A2Bd22810088523516737b4Dc238A4bC37c23F2".', - ); - }); - - it('throws if a keyring account is missing an address (case 2)', async () => { - const getInternalAccounts = jest.fn().mockImplementationOnce(() => { - return [ - { - address: '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', - id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', - metadata: { - name: 'Test Account', - lastSelected: 1, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - { - address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', - metadata: { - name: 'Test Account', - lastSelected: 3, - keyring: { - type: 'HD Key Tree', - }, - }, - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - }, - ]; - }); - const getAllAccounts = jest - .fn() - .mockImplementationOnce(() => [ - '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', - '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', - '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', - ]); - - const { methodImplementation } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - captureKeyringTypesWithMissingIdentities: jest.fn(), - })[RestrictedMethods.eth_accounts]; - - await expect(() => methodImplementation()).rejects.toThrow( - 'Missing identity for address: "0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3".', - ); - }); - }); - - describe('validator', () => { - it('accepts valid permissions', () => { - const getInternalAccounts = jest.fn(); - const getAllAccounts = jest.fn(); - const { validator } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - expect(() => - validator({ - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x2'], - }, - ], - date: 1, - id: expect.any(String), - invoker: 'foo.bar', - parentCapability: 'eth_accounts', - }), - ).not.toThrow(); - }); - - it('rejects invalid caveats', () => { - const getInternalAccounts = jest.fn(); - const getAllAccounts = jest.fn(); - const { validator } = getPermissionSpecifications({ - getInternalAccounts, - getAllAccounts, - })[RestrictedMethods.eth_accounts]; - - [null, [], [1, 2], [{ type: 'foobar' }]].forEach( - (invalidCaveatsValue) => { - expect(() => - validator({ - caveats: invalidCaveatsValue, - date: 1, - id: expect.any(String), - invoker: 'foo.bar', - parentCapability: 'eth_accounts', - }), - ).toThrow(/Invalid caveats./u); - }, - ); - }); - }); + permissionSpecifications[Caip25EndowmentPermissionName].targetName, + ).toStrictEqual('endowment:caip25'); }); }); diff --git a/app/scripts/controllers/preferences-controller.test.ts b/app/scripts/controllers/preferences-controller.test.ts index 39a2d49648b2..d0f0fc22c888 100644 --- a/app/scripts/controllers/preferences-controller.test.ts +++ b/app/scripts/controllers/preferences-controller.test.ts @@ -640,19 +640,6 @@ describe('preferences controller', () => { }); }); - describe('useRequestQueue', () => { - it('defaults useRequestQueue to true', () => { - const { controller } = setupController({}); - expect(controller.state.useRequestQueue).toStrictEqual(true); - }); - - it('setUseRequestQueue to false', () => { - const { controller } = setupController({}); - controller.setUseRequestQueue(false); - expect(controller.state.useRequestQueue).toStrictEqual(false); - }); - }); - describe('addSnapAccountEnabled', () => { it('defaults addSnapAccountEnabled to false', () => { const { controller } = setupController({}); @@ -733,12 +720,11 @@ describe('preferences controller', () => { privacyMode: false, showFiatInTestnets: false, showTestNetworks: false, + smartTransactionsMigrationApplied: false, smartTransactionsOptInStatus: true, useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, petnamesEnabled: true, - redesignedConfirmationsEnabled: true, - redesignedTransactionsEnabled: true, shouldShowAggregatedBalancePopover: true, featureNotificationsEnabled: false, isRedesignedConfirmationsDeveloperEnabled: false, @@ -762,13 +748,12 @@ describe('preferences controller', () => { showExtensionInFullSizeView: false, showFiatInTestnets: false, showTestNetworks: false, + smartTransactionsMigrationApplied: false, smartTransactionsOptInStatus: true, useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, petnamesEnabled: true, privacyMode: false, - redesignedConfirmationsEnabled: true, - redesignedTransactionsEnabled: true, shouldShowAggregatedBalancePopover: true, featureNotificationsEnabled: false, isRedesignedConfirmationsDeveloperEnabled: false, diff --git a/app/scripts/controllers/preferences-controller.ts b/app/scripts/controllers/preferences-controller.ts index dce2ef3d0512..69174b029b3e 100644 --- a/app/scripts/controllers/preferences-controller.ts +++ b/app/scripts/controllers/preferences-controller.ts @@ -104,12 +104,11 @@ export type Preferences = { showFiatInTestnets: boolean; showTestNetworks: boolean; smartTransactionsOptInStatus: boolean; + smartTransactionsMigrationApplied: boolean; showNativeTokenAsMainBalance: boolean; useNativeCurrencyAsPrimaryCurrency: boolean; hideZeroBalanceTokens: boolean; petnamesEnabled: boolean; - redesignedConfirmationsEnabled: boolean; - redesignedTransactionsEnabled: boolean; featureNotificationsEnabled: boolean; showMultiRpcModal: boolean; privacyMode: boolean; @@ -124,10 +123,15 @@ export type Preferences = { shouldShowAggregatedBalancePopover: boolean; }; -// Omitting showTestNetworks and smartTransactionsOptInStatus, as they already exists here in Preferences type +// Omitting properties that already exist in the PreferencesState, as part of the preferences property. export type PreferencesControllerState = Omit< PreferencesState, - 'showTestNetworks' | 'smartTransactionsOptInStatus' + | 'showTestNetworks' + | 'smartTransactionsOptInStatus' + | 'smartTransactionsMigrationApplied' + | 'privacyMode' + | 'tokenSortConfig' + | 'useMultiRpcMigration' > & { useBlockie: boolean; useNonceField: boolean; @@ -135,10 +139,8 @@ export type PreferencesControllerState = Omit< dismissSeedBackUpReminder: boolean; overrideContentSecurityPolicyHeader: boolean; useMultiAccountBalanceChecker: boolean; - useSafeChainsListValidation: boolean; use4ByteResolution: boolean; useCurrencyRateCheck: boolean; - useRequestQueue: boolean; ///: BEGIN:ONLY_INCLUDE_IF(build-flask) watchEthereumAccountEnabled: boolean; ///: END:ONLY_INCLUDE_IF @@ -185,7 +187,6 @@ export const getDefaultPreferencesControllerState = useNftDetection: true, use4ByteResolution: true, useCurrencyRateCheck: true, - useRequestQueue: true, openSeaEnabled: true, securityAlertsEnabled: true, watchEthereumAccountEnabled: false, @@ -214,12 +215,11 @@ export const getDefaultPreferencesControllerState = showFiatInTestnets: false, showTestNetworks: false, smartTransactionsOptInStatus: true, + smartTransactionsMigrationApplied: false, showNativeTokenAsMainBalance: false, useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, petnamesEnabled: true, - redesignedConfirmationsEnabled: true, - redesignedTransactionsEnabled: true, featureNotificationsEnabled: false, isRedesignedConfirmationsDeveloperEnabled: false, showConfirmationAdvancedDetails: false, @@ -336,10 +336,6 @@ const controllerMetadata = { persist: true, anonymous: true, }, - useRequestQueue: { - persist: true, - anonymous: true, - }, openSeaEnabled: { persist: true, anonymous: true, @@ -403,6 +399,16 @@ const controllerMetadata = { preferences: { persist: true, anonymous: true, + properties: { + smartTransactionsOptInStatus: { + persist: true, + anonymous: true, + }, + smartTransactionsMigrationApplied: { + persist: true, + anonymous: true, + }, + }, }, ipfsGateway: { persist: true, @@ -626,17 +632,6 @@ export class PreferencesController extends BaseController< }); } - /** - * Setter for the `useRequestQueue` property - * - * @param val - Whether or not the user wants to have requests queued if network change is required. - */ - setUseRequestQueue(val: boolean): void { - this.update((state) => { - state.useRequestQueue = val; - }); - } - /** * Setter for the `openSeaEnabled` property * @@ -849,15 +844,6 @@ export class PreferencesController extends BaseController< return selectedAccount.address; } - /** - * Getter for the `useRequestQueue` property - * - * @returns whether this option is on or off. - */ - getUseRequestQueue(): boolean { - return this.state.useRequestQueue; - } - /** * Sets a custom label for an account * diff --git a/app/scripts/fixtures/with-preferences.js b/app/scripts/fixtures/with-preferences.js index c3a482ef8f94..81c98c705940 100644 --- a/app/scripts/fixtures/with-preferences.js +++ b/app/scripts/fixtures/with-preferences.js @@ -7,7 +7,6 @@ export const FIXTURES_PREFERENCES = { smartTransactionsOptInStatus: true, useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, - redesignedConfirmationsEnabled: true, featureNotificationsEnabled: true, showTokenAutodetectModal: false, showNftAutodetectModal: false, @@ -31,7 +30,6 @@ export const FIXTURES_PREFERENCES = { useTokenDetection: true, useCurrencyRateCheck: true, useMultiAccountBalanceChecker: true, - useRequestQueue: true, theme: 'light', useExternalNameSources: true, useTransactionSimulations: true, diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index d00a0542db03..9aca84081406 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -34,7 +34,7 @@ cleanContextForImports(); import log from 'loglevel'; import { v4 as uuid } from 'uuid'; import { WindowPostMessageStream } from '@metamask/post-message-stream'; -import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; +import { initializeProvider } from '@metamask/providers/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; // contexts diff --git a/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts b/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts index 87f4d114eabb..dac123744772 100644 --- a/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts +++ b/app/scripts/lib/AccountIdentitiesPetnamesBridge.ts @@ -4,7 +4,7 @@ import { NameType, NameOrigin, } from '@metamask/name-controller'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { AccountsControllerChangeEvent, AccountsControllerListAccountsAction, diff --git a/app/scripts/lib/accounts/BalancesController.test.ts b/app/scripts/lib/accounts/BalancesController.test.ts index 982df0289fea..3dec8c565c73 100644 --- a/app/scripts/lib/accounts/BalancesController.test.ts +++ b/app/scripts/lib/accounts/BalancesController.test.ts @@ -1,10 +1,7 @@ import { ControllerMessenger } from '@metamask/base-controller'; -import { - Balance, - BtcAccountType, - CaipAssetType, - InternalAccount, -} from '@metamask/keyring-api'; +import { Balance, BtcAccountType, CaipAssetType } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { KeyringTypes } from '@metamask/keyring-controller'; import { createMockInternalAccount } from '../../../../test/jest/mocks'; import { MultichainNetworks } from '../../../../shared/constants/multichain/networks'; import { @@ -29,6 +26,7 @@ const mockBtcAccount = createMockInternalAccount({ options: { scope: MultichainNetworks.BITCOIN_TESTNET, }, + keyringType: KeyringTypes.snap, }); const mockBalanceResult = { diff --git a/app/scripts/lib/accounts/BalancesController.ts b/app/scripts/lib/accounts/BalancesController.ts index 588053d6ea2a..4b9616fac35f 100644 --- a/app/scripts/lib/accounts/BalancesController.ts +++ b/app/scripts/lib/accounts/BalancesController.ts @@ -8,13 +8,13 @@ import { } from '@metamask/base-controller'; import { BtcAccountType, - KeyringClient, + SolAccountType, + isEvmAccountType, type Balance, type CaipAssetType, - type InternalAccount, - isEvmAccountType, - SolAccountType, } from '@metamask/keyring-api'; +import { type InternalAccount } from '@metamask/keyring-internal-api'; +import { KeyringClient } from '@metamask/keyring-snap-client'; import type { HandleSnapRequest } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { HandlerType } from '@metamask/snaps-utils'; diff --git a/app/scripts/lib/approval/utils.test.ts b/app/scripts/lib/approval/utils.test.ts new file mode 100644 index 000000000000..14c472df89ba --- /dev/null +++ b/app/scripts/lib/approval/utils.test.ts @@ -0,0 +1,103 @@ +import { + ApprovalController, + ApprovalRequest, +} from '@metamask/approval-controller'; +import { Json } from '@metamask/utils'; +import { ApprovalType } from '@metamask/controller-utils'; +import { providerErrors } from '@metamask/rpc-errors'; +import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; +import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; +import { rejectAllApprovals } from './utils'; + +const ID_MOCK = '123'; +const ID_MOCK_2 = '456'; +const INTERFACE_ID_MOCK = '789'; + +function createApprovalControllerMock( + pendingApprovals: Partial>>[], +) { + return { + state: { + pendingApprovals, + }, + accept: jest.fn(), + reject: jest.fn(), + } as unknown as jest.Mocked; +} + +describe('Approval Utils', () => { + describe('rejectAllApprovals', () => { + it('rejects approval requests with rejected error', () => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type: ApprovalType.Transaction }, + { id: ID_MOCK_2, type: ApprovalType.EthSignTypedData }, + ]); + + rejectAllApprovals({ + approvalController, + }); + + expect(approvalController.reject).toHaveBeenCalledTimes(2); + expect(approvalController.reject).toHaveBeenCalledWith( + ID_MOCK, + providerErrors.userRejectedRequest(), + ); + expect(approvalController.reject).toHaveBeenCalledWith( + ID_MOCK_2, + providerErrors.userRejectedRequest(), + ); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ApprovalType.SnapDialogAlert, + ApprovalType.SnapDialogPrompt, + DIALOG_APPROVAL_TYPES.default, + ])('accepts pending approval if type is %s', (type: string) => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type }, + ]); + + rejectAllApprovals({ approvalController }); + + expect(approvalController.accept).toHaveBeenCalledTimes(1); + expect(approvalController.accept).toHaveBeenCalledWith(ID_MOCK, null); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ApprovalType.SnapDialogConfirmation, + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval, + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect, + ])('accepts pending approval if type is %s', (type: string) => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type }, + ]); + + rejectAllApprovals({ approvalController }); + + expect(approvalController.accept).toHaveBeenCalledTimes(1); + expect(approvalController.accept).toHaveBeenCalledWith(ID_MOCK, false); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ApprovalType.SnapDialogAlert, + ApprovalType.SnapDialogPrompt, + DIALOG_APPROVAL_TYPES.default, + ApprovalType.SnapDialogConfirmation, + ])('deletes interface if type is %s', (type: string) => { + const approvalController = createApprovalControllerMock([ + { id: ID_MOCK, type, requestData: { id: INTERFACE_ID_MOCK } }, + ]); + + const deleteInterface = jest.fn(); + + rejectAllApprovals({ approvalController, deleteInterface }); + + expect(deleteInterface).toHaveBeenCalledTimes(1); + expect(deleteInterface).toHaveBeenCalledWith(INTERFACE_ID_MOCK); + }); + }); +}); diff --git a/app/scripts/lib/approval/utils.ts b/app/scripts/lib/approval/utils.ts new file mode 100644 index 000000000000..5bd44b5db079 --- /dev/null +++ b/app/scripts/lib/approval/utils.ts @@ -0,0 +1,56 @@ +import { ApprovalController } from '@metamask/approval-controller'; +import { ApprovalType } from '@metamask/controller-utils'; +import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; +import { providerErrors } from '@metamask/rpc-errors'; +import { createProjectLogger } from '@metamask/utils'; +///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) +import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; +///: END:ONLY_INCLUDE_IF + +const log = createProjectLogger('approval-utils'); + +export function rejectAllApprovals({ + approvalController, + deleteInterface, +}: { + approvalController: ApprovalController; + deleteInterface?: (id: string) => void; +}) { + const approvalRequestsById = approvalController.state.pendingApprovals; + const approvalRequests = Object.values(approvalRequestsById); + + for (const approvalRequest of approvalRequests) { + const { id, type } = approvalRequest; + const interfaceId = approvalRequest.requestData?.id as string; + + switch (type) { + case ApprovalType.SnapDialogAlert: + case ApprovalType.SnapDialogPrompt: + case DIALOG_APPROVAL_TYPES.default: + log('Rejecting snap dialog', { id, interfaceId }); + approvalController.accept(id, null); + deleteInterface?.(interfaceId); + break; + + case ApprovalType.SnapDialogConfirmation: + log('Rejecting snap confirmation', { id, interfaceId }); + approvalController.accept(id, false); + deleteInterface?.(interfaceId); + break; + + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation: + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval: + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect: + log('Rejecting snap account confirmation', { id }); + approvalController.accept(id, false); + break; + ///: END:ONLY_INCLUDE_IF + + default: + log('Rejecting pending approval', { id }); + approvalController.reject(id, providerErrors.userRejectedRequest()); + break; + } + } +} diff --git a/app/scripts/lib/backup.test.js b/app/scripts/lib/backup.test.js index 826aa04018d9..242b9174add4 100644 --- a/app/scripts/lib/backup.test.js +++ b/app/scripts/lib/backup.test.js @@ -175,7 +175,6 @@ const jsonData = JSON.stringify({ theme: 'light', customNetworkListEnabled: false, textDirection: 'auto', - useRequestQueue: true, }, internalAccounts: { accounts: { diff --git a/app/scripts/lib/createMainFrameOriginMiddleware.ts b/app/scripts/lib/createMainFrameOriginMiddleware.ts new file mode 100644 index 000000000000..bcbc2cb7d6fd --- /dev/null +++ b/app/scripts/lib/createMainFrameOriginMiddleware.ts @@ -0,0 +1,24 @@ +// Request and responses are currently untyped. +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * Returns a middleware that appends the mainFrameOrigin to request + * + * @param {{ mainFrameOrigin: string }} opts - The middleware options + * @returns {Function} + */ + +export default function createMainFrameOriginMiddleware({ + mainFrameOrigin, +}: { + mainFrameOrigin: string; +}) { + return function mainFrameOriginMiddleware( + req: any, + _res: any, + next: () => void, + ) { + req.mainFrameOrigin = mainFrameOrigin; + next(); + }; +} diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index 5ca2374db05b..a2caaffd1add 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -195,7 +195,6 @@ function finalizeSignatureFragment( * that should be tracked for methods rate limited by random sample. * @param {Function} opts.getAccountType * @param {Function} opts.getDeviceModel - * @param {Function} opts.isConfirmationRedesignEnabled * @param {Function} opts.isRedesignedConfirmationsDeveloperEnabled * @param {RestrictedControllerMessenger} opts.snapAndHardwareMessenger * @param {number} [opts.globalRateLimitTimeout] - time, in milliseconds, of the sliding @@ -214,7 +213,6 @@ export default function createRPCMethodTrackingMiddleware({ globalRateLimitMaxAmount = 10, // max of events in the globalRateLimitTimeout window. pass 0 for no global rate limit getAccountType, getDeviceModel, - isConfirmationRedesignEnabled, isRedesignedConfirmationsDeveloperEnabled, snapAndHardwareMessenger, appStateController, @@ -320,8 +318,6 @@ export default function createRPCMethodTrackingMiddleware({ if ( shouldUseRedesignForSignatures({ approvalType: MESSAGE_TYPE_TO_APPROVAL_TYPE[method], - isRedesignedSignaturesUserSettingEnabled: - isConfirmationRedesignEnabled(), isRedesignedConfirmationsDeveloperEnabled: isRedesignedConfirmationsDeveloperEnabled(), }) diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js index 1949ca7f876e..44eead3dc984 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js @@ -36,20 +36,16 @@ const expectedMetametricsEventUndefinedProps = { }; const appStateController = { - store: { - getState: () => ({ - signatureSecurityAlertResponses: { - 1: { - result_type: BlockaidResultType.Malicious, - reason: BlockaidReason.maliciousDomain, - }, + state: { + signatureSecurityAlertResponses: { + 1: { + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.maliciousDomain, }, - }), + }, }, getSignatureSecurityAlertResponse: (id) => { - return appStateController.store.getState().signatureSecurityAlertResponses[ - id - ]; + return appStateController.state.signatureSecurityAlertResponses[id]; }, }; @@ -117,7 +113,6 @@ const createHandler = (opts) => globalRateLimitMaxAmount: 0, appStateController, metaMetricsController, - isConfirmationRedesignEnabled: () => false, isRedesignedConfirmationsDeveloperEnabled: () => false, ...opts, }); @@ -339,6 +334,9 @@ describe('createRPCMethodTrackingMiddleware', () => { security_alert_reason: BlockaidReason.maliciousDomain, ppom_eth_call_count: 5, ppom_eth_getCode_count: 3, + ui_customizations: [ + MetaMetricsEventUiCustomization.RedesignedConfirmation, + ], }, referrer: { url: 'some.dapp' }, uniqueIdentifier: expectedUniqueIdentifier, @@ -604,38 +602,6 @@ describe('createRPCMethodTrackingMiddleware', () => { }); }); - it('should track Confirmation Redesign through ui_customizations prop if enabled', async () => { - const req = { - id: MOCK_ID, - method: MESSAGE_TYPE.PERSONAL_SIGN, - origin: 'some.dapp', - }; - const res = { - error: null, - }; - const { next, executeMiddlewareStack } = getNext(); - const handler = createHandler({ - isConfirmationRedesignEnabled: () => true, - }); - - await handler(req, res, next); - await executeMiddlewareStack(); - - expect(trackEventSpy).toHaveBeenCalledTimes(2); - - expect(trackEventSpy.mock.calls[1][0]).toMatchObject({ - category: MetaMetricsEventCategory.InpageProvider, - event: MetaMetricsEventName.SignatureApproved, - properties: { - signature_type: MESSAGE_TYPE.PERSONAL_SIGN, - ui_customizations: [ - MetaMetricsEventUiCustomization.RedesignedConfirmation, - ], - }, - referrer: { url: 'some.dapp' }, - }); - }); - it('should not track Confirmation Redesign through ui_customizations prop if not enabled', async () => { const req = { id: MOCK_ID, @@ -689,7 +655,10 @@ describe('createRPCMethodTrackingMiddleware', () => { event: MetaMetricsEventName.SignatureApproved, properties: { signature_type: MESSAGE_TYPE.PERSONAL_SIGN, - ui_customizations: [MetaMetricsEventUiCustomization.Siwe], + ui_customizations: [ + MetaMetricsEventUiCustomization.RedesignedConfirmation, + MetaMetricsEventUiCustomization.Siwe, + ], }, referrer: { url: 'some.dapp' }, }); @@ -749,7 +718,10 @@ describe('createRPCMethodTrackingMiddleware', () => { event: MetaMetricsEventName.SignatureApproved, properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, - ui_customizations: [MetaMetricsEventUiCustomization.Permit], + ui_customizations: [ + MetaMetricsEventUiCustomization.RedesignedConfirmation, + MetaMetricsEventUiCustomization.Permit, + ], eip712_primary_type: 'Permit', }, referrer: { url: 'some.dapp' }, @@ -804,7 +776,10 @@ describe('createRPCMethodTrackingMiddleware', () => { event: MetaMetricsEventName.SignatureApproved, properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, - ui_customizations: [MetaMetricsEventUiCustomization.Order], + ui_customizations: [ + MetaMetricsEventUiCustomization.RedesignedConfirmation, + MetaMetricsEventUiCustomization.Order, + ], }, referrer: { url: 'some.dapp' }, }); @@ -836,13 +811,12 @@ describe('createRPCMethodTrackingMiddleware', () => { properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, eip712_primary_type: 'Unknown', + ui_customizations: [ + MetaMetricsEventUiCustomization.RedesignedConfirmation, + ], }, referrer: { url: 'some.dapp' }, }); - - expect(trackEventSpy.mock.calls[1][0].properties).not.toHaveProperty( - 'ui_customizations', - ); }); describe('when request is flagged as safe by security provider', () => { diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js index ecdab8c05596..575cbd999ff6 100644 --- a/app/scripts/lib/ens-ipfs/resolver.js +++ b/app/scripts/lib/ens-ipfs/resolver.js @@ -1,35 +1,49 @@ import namehash from 'eth-ens-namehash'; -import Eth from '@metamask/ethjs-query'; -import EthContract from '@metamask/ethjs-contract'; import contentHash from '@ensdomains/content-hash'; +import { Web3Provider } from '@ethersproject/providers'; +import { Contract } from '@ethersproject/contracts'; import registryAbi from './contracts/registry'; import resolverAbi from './contracts/resolver'; export default async function resolveEnsToIpfsContentId({ provider, name }) { - const eth = new Eth(provider); const hash = namehash.hash(name); - const contract = new EthContract(eth); + // lookup registry - const chainId = Number.parseInt(await eth.net_version(), 10); + const chainId = Number.parseInt( + await provider.request({ method: 'net_version' }), + 10, + ); const registryAddress = getRegistryForChainId(chainId); if (!registryAddress) { throw new Error( `EnsIpfsResolver - no known ens-ipfs registry for chainId "${chainId}"`, ); } - const Registry = contract(registryAbi).at(registryAddress); + const web3Provider = new Web3Provider(provider); + const registryContract = new Contract( + registryAddress, + registryAbi, + web3Provider, + ); // lookup resolver - const resolverLookupResult = await Registry.resolver(hash); - const resolverAddress = resolverLookupResult[0]; + const resolverAddress = await registryContract.resolver(hash); if (hexValueIsEmpty(resolverAddress)) { throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`); } - const Resolver = contract(resolverAbi).at(resolverAddress); + const resolverContract = new Contract( + resolverAddress, + resolverAbi, + web3Provider, + ); - const isEIP1577Compliant = await Resolver.supportsInterface('0xbc1c58d1'); - const isLegacyResolver = await Resolver.supportsInterface('0xd8389dc5'); - if (isEIP1577Compliant[0]) { - const contentLookupResult = await Resolver.contenthash(hash); + const isEIP1577Compliant = await resolverContract.supportsInterface( + '0xbc1c58d1', + ); + const isLegacyResolver = await resolverContract.supportsInterface( + '0xd8389dc5', + ); + if (isEIP1577Compliant) { + const contentLookupResult = await resolverContract.contenthash(hash); const rawContentHash = contentLookupResult[0]; let decodedContentHash = contentHash.decode(rawContentHash); const type = contentHash.getCodec(rawContentHash); @@ -41,9 +55,9 @@ export default async function resolveEnsToIpfsContentId({ provider, name }) { return { type, hash: decodedContentHash }; } - if (isLegacyResolver[0]) { + if (isLegacyResolver) { // lookup content id - const contentLookupResult = await Resolver.content(hash); + const contentLookupResult = await resolverContract.content(hash); const content = contentLookupResult[0]; if (hexValueIsEmpty(content)) { throw new Error( diff --git a/app/scripts/lib/manifestFlags.ts b/app/scripts/lib/manifestFlags.ts index 5804c7391973..574099d0cb94 100644 --- a/app/scripts/lib/manifestFlags.ts +++ b/app/scripts/lib/manifestFlags.ts @@ -1,17 +1,63 @@ import browser from 'webextension-polyfill'; +/** + * Flags that we use to control runtime behavior of the extension. Typically + * used for E2E tests. + * + * These flags are added to `manifest.json` for runtime querying. + */ export type ManifestFlags = { + /** + * CircleCI metadata for the current run + */ circleci?: { + /** + * Whether CircleCI manifest flags are enabled. + */ enabled: boolean; + /** + * The name of the branch that triggered the current run on CircleCI + */ branch?: string; + /** + * The current CircleCI build number + */ buildNum?: number; + /** + * The name of the CircleCI job currently running + */ job?: string; + /** + * For jobs with CircleCI parallelism enabled, this is the index of the current machine. + */ nodeIndex?: number; + /** + * The number of the pull request that triggered the current run + */ prNumber?: number; + /** + * The number of minutes to allow the E2E tests to run before timing out + */ + timeoutMinutes?: number; }; + /** + * Sentry flags + */ sentry?: { + /** + * Override the performance trace sample rate + */ tracesSampleRate?: number; - lazyLoadSubSampleRate?: number; // multiply by tracesSampleRate to get the actual probability + /** + * Sub-sample rate for lazy-loaded components. + * + * Multiply this rate by tracesSampleRate to get the actual probability of sampling the load + * time of a lazy-loaded component. + */ + lazyLoadSubSampleRate?: number; + /** + * Force enable Sentry (this is typically set by individual E2E tests in spec files) + */ forceEnable?: boolean; }; }; diff --git a/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts b/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts index 608d1ee8156b..1ff85111f54a 100644 --- a/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts +++ b/app/scripts/lib/offscreen-bridge/ledger-offscreen-bridge.ts @@ -1,4 +1,8 @@ -import { LedgerBridge } from '@metamask/eth-ledger-bridge-keyring'; +import { + LedgerBridge, + LedgerSignTypedDataParams, + LedgerSignTypedDataResponse, +} from '@metamask/eth-ledger-bridge-keyring'; import { LedgerAction, OffscreenCommunicationEvents, @@ -161,11 +165,9 @@ export class LedgerOffscreenBridge }); } - deviceSignTypedData(params: { - hdPath: string; - domainSeparatorHex: string; - hashStructMessageHex: string; - }) { + deviceSignTypedData( + params: LedgerSignTypedDataParams, + ): Promise { return new Promise<{ v: number; s: string; diff --git a/app/scripts/lib/ppom/ppom-util.test.ts b/app/scripts/lib/ppom/ppom-util.test.ts index 8acb6dd9788c..c143f3dfe11b 100644 --- a/app/scripts/lib/ppom/ppom-util.test.ts +++ b/app/scripts/lib/ppom/ppom-util.test.ts @@ -10,7 +10,7 @@ import { SignatureController, SignatureRequest, } from '@metamask/signature-controller'; -import { Hex } from '@metamask/utils'; +import { Hex, JsonRpcRequest } from '@metamask/utils'; import { BlockaidReason, BlockaidResultType, @@ -22,6 +22,8 @@ import { AppStateController } from '../../controllers/app-state-controller'; import { generateSecurityAlertId, isChainSupported, + METHOD_SIGN_TYPED_DATA_V3, + METHOD_SIGN_TYPED_DATA_V4, updateSecurityAlertResponse, validateRequestWithPPOM, } from './ppom-util'; @@ -57,6 +59,10 @@ const TRANSACTION_PARAMS_MOCK_1: TransactionParams = { value: '0x123', }; +const SIGN_TYPED_DATA_PARAMS_MOCK_1 = '0x123'; +const SIGN_TYPED_DATA_PARAMS_MOCK_2 = + '{"primaryType":"Permit","domain":{},"types":{}}'; + const TRANSACTION_PARAMS_MOCK_2: TransactionParams = { ...TRANSACTION_PARAMS_MOCK_1, to: '0x456', @@ -198,6 +204,7 @@ describe('PPOM Utils', () => { result_type: BlockaidResultType.Errored, reason: BlockaidReason.errored, description: 'Test Error: Test error message', + source: SecurityAlertSource.Local, }, ); }); @@ -219,6 +226,7 @@ describe('PPOM Utils', () => { result_type: BlockaidResultType.Errored, reason: BlockaidReason.errored, description: 'Test Error: Test error message', + source: SecurityAlertSource.Local, }, ); }); @@ -259,6 +267,48 @@ describe('PPOM Utils', () => { ); }); + // @ts-expect-error This is missing from the Mocha type definitions + it.each([METHOD_SIGN_TYPED_DATA_V3, METHOD_SIGN_TYPED_DATA_V4])( + 'sanitizes request params if method is %s', + async (method: string) => { + const ppom = createPPOMMock(); + const ppomController = createPPOMControllerMock(); + + ppomController.usePPOM.mockImplementation( + (callback) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback(ppom as any) as any, + ); + + const firstTwoParams = [ + SIGN_TYPED_DATA_PARAMS_MOCK_1, + SIGN_TYPED_DATA_PARAMS_MOCK_2, + ]; + + const unwantedParams = [{}, undefined, 1, null]; + + const params = [...firstTwoParams, ...unwantedParams]; + + const request = { + ...REQUEST_MOCK, + method, + params, + } as unknown as JsonRpcRequest; + + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, + ppomController, + request, + }); + + expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1); + expect(ppom.validateJsonRpc).toHaveBeenCalledWith({ + ...request, + params: firstTwoParams, + }); + }, + ); + it('updates response indicating chain is not supported', async () => { const ppomController = {} as PPOMController; const CHAIN_ID_UNSUPPORTED_MOCK = '0x2'; diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index d27ec6c8e505..847773baceee 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -29,6 +29,8 @@ import { const { sentry } = global; const METHOD_SEND_TRANSACTION = 'eth_sendTransaction'; +export const METHOD_SIGN_TYPED_DATA_V3 = 'eth_signTypedData_v3'; +export const METHOD_SIGN_TYPED_DATA_V4 = 'eth_signTypedData_v4'; const SECURITY_ALERT_RESPONSE_ERROR = { result_type: BlockaidResultType.Errored, @@ -132,16 +134,20 @@ export async function updateSecurityAlertResponse({ export function handlePPOMError( error: unknown, logMessage: string, + source: SecurityAlertSource = SecurityAlertSource.API, ): SecurityAlertResponse { const errorData = getErrorData(error); const description = getErrorMessage(error); - sentry?.captureException(error); + if (source === SecurityAlertSource.Local) { + sentry?.captureException(error); + } console.error(logMessage, errorData); return { ...SECURITY_ALERT_RESPONSE_ERROR, description, + source, }; } @@ -169,7 +175,7 @@ function normalizePPOMRequest( request, ) ) { - return request; + return sanitizeRequest(request); } const transactionParams = request.params[0]; @@ -181,6 +187,22 @@ function normalizePPOMRequest( }; } +function sanitizeRequest(request: JsonRpcRequest): JsonRpcRequest { + // This is a temporary fix to prevent a PPOM bypass + if ( + request.method === METHOD_SIGN_TYPED_DATA_V4 || + request.method === METHOD_SIGN_TYPED_DATA_V3 + ) { + if (Array.isArray(request.params)) { + return { + ...request, + params: request.params.slice(0, 2), + }; + } + } + return request; +} + function getErrorMessage(error: unknown) { if (error instanceof Error) { return `${error.name}: ${error.message}`; @@ -236,15 +258,23 @@ async function validateWithController( request: SecurityAlertsAPIRequest | JsonRpcRequest, chainId: string, ): Promise { - const response = (await ppomController.usePPOM( - (ppom: PPOM) => ppom.validateJsonRpc(request), - chainId, - )) as SecurityAlertResponse; + try { + const response = (await ppomController.usePPOM( + (ppom: PPOM) => ppom.validateJsonRpc(request), + chainId, + )) as SecurityAlertResponse; - return { - ...response, - source: SecurityAlertSource.Local, - }; + return { + ...response, + source: SecurityAlertSource.Local, + }; + } catch (error: unknown) { + return handlePPOMError( + error, + `Error validating request with PPOM controller`, + SecurityAlertSource.Local, + ); + } } async function validateWithAPI( diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index bbc06e7033f5..9be36a3dbced 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -1,19 +1,32 @@ -import { permissionRpcMethods } from '@metamask/permission-controller'; import { rpcErrors } from '@metamask/rpc-errors'; import { selectHooks } from '@metamask/snaps-rpc-methods'; import { hasProperty } from '@metamask/utils'; -import { handlers as localHandlers, legacyHandlers } from './handlers'; -const allHandlers = [...localHandlers, ...permissionRpcMethods.handlers]; +import { + handlers as localHandlers, + eip1193OnlyHandlers, + ethAccountsHandler, +} from './handlers'; +import { getPermissionsHandler } from './handlers/wallet-getPermissions'; +import { requestPermissionsHandler } from './handlers/wallet-requestPermissions'; +import { revokePermissionsHandler } from './handlers/wallet-revokePermissions'; -// The primary home of RPC method implementations in MetaMask. MUST be subsequent -// to our permissioning logic in the JSON-RPC middleware pipeline. -export const createMethodMiddleware = makeMethodMiddlewareMaker(allHandlers); +// The primary home of RPC method implementations for the injected 1193 provider API. MUST be subsequent +// to our permissioning logic in the EIP-1193 JSON-RPC middleware pipeline. +export const createEip1193MethodMiddleware = makeMethodMiddlewareMaker([ + ...localHandlers, + ...eip1193OnlyHandlers, + // EIP-2255 Permission handlers + getPermissionsHandler, + requestPermissionsHandler, + revokePermissionsHandler, +]); // A collection of RPC method implementations that, for legacy reasons, MAY precede -// our permissioning logic in the JSON-RPC middleware pipeline. -export const createLegacyMethodMiddleware = - makeMethodMiddlewareMaker(legacyHandlers); +// our permissioning logic in the EIP-1193 JSON-RPC middleware pipeline. +export const createEthAccountsMethodMiddleware = makeMethodMiddlewareMaker([ + ethAccountsHandler, +]); /** * Creates a method middleware factory function given a set of method handlers. diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js index 48ea5ae90d58..4a3b9f958a16 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.test.js @@ -3,49 +3,63 @@ import { assertIsJsonRpcFailure, assertIsJsonRpcSuccess, } from '@metamask/utils'; -import { createMethodMiddleware, createLegacyMethodMiddleware } from '.'; +import { + createEip1193MethodMiddleware, + createEthAccountsMethodMiddleware, +} from '.'; + +const getHandler = () => ({ + implementation: (req, res, _next, end, hooks) => { + if (Array.isArray(req.params)) { + switch (req.params[0]) { + case 1: + res.result = hooks.hook1(); + break; + case 2: + res.result = hooks.hook2(); + break; + case 3: + return end(new Error('test error')); + case 4: + throw new Error('test error'); + case 5: + // eslint-disable-next-line no-throw-literal + throw 'foo'; + default: + throw new Error(`unexpected param "${req.params[0]}"`); + } + } + return end(); + }, + hookNames: { hook1: true, hook2: true }, + methodNames: ['method1', 'method2'], +}); jest.mock('@metamask/permission-controller', () => ({ - permissionRpcMethods: { handlers: [] }, + ...jest.requireActual('@metamask/permission-controller'), })); -jest.mock('./handlers', () => { - const getHandler = () => ({ - implementation: (req, res, _next, end, hooks) => { - if (Array.isArray(req.params)) { - switch (req.params[0]) { - case 1: - res.result = hooks.hook1(); - break; - case 2: - res.result = hooks.hook2(); - break; - case 3: - return end(new Error('test error')); - case 4: - throw new Error('test error'); - case 5: - // eslint-disable-next-line no-throw-literal - throw 'foo'; - default: - throw new Error(`unexpected param "${req.params[0]}"`); - } - } - return end(); - }, - hookNames: { hook1: true, hook2: true }, - methodNames: ['method1', 'method2'], - }); +jest.mock('./handlers/wallet-getPermissions', () => ({ + getPermissionsHandler: getHandler(), +})); - return { - handlers: [getHandler()], - legacyHandlers: [getHandler()], - }; -}); +jest.mock('./handlers/wallet-requestPermissions', () => ({ + requestPermissionsHandler: getHandler(), +})); + +jest.mock('./handlers/wallet-revokePermissions', () => ({ + revokePermissionsHandler: getHandler(), +})); + +jest.mock('./handlers', () => ({ + handlers: [getHandler()], + eip1193OnlyHandlers: [getHandler()], + ethAccountsHandler: getHandler(), +})); describe.each([ - ['createMethodMiddleware', createMethodMiddleware], - ['createLegacyMethodMiddleware', createLegacyMethodMiddleware], + ['createEip1193MethodMiddleware', createEip1193MethodMiddleware], + ['createEthAccountsMethodMiddleware', createEthAccountsMethodMiddleware], ])('%s', (_name, createMiddleware) => { const method1 = 'method1'; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js index 2ae9df7bd15a..2fb3dc6c25c0 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js @@ -20,8 +20,8 @@ const addEthereumChain = { requestUserApproval: true, getCurrentChainIdForDomain: true, getCaveat: true, - requestPermittedChainsPermission: true, - grantPermittedChainsPermissionIncremental: true, + requestPermittedChainsPermissionForOrigin: true, + requestPermittedChainsPermissionIncrementalForOrigin: true, }, }; @@ -40,13 +40,13 @@ async function addEthereumChainHandler( requestUserApproval, getCurrentChainIdForDomain, getCaveat, - requestPermittedChainsPermission, - grantPermittedChainsPermissionIncremental, + requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin, }, ) { let validParams; try { - validParams = validateAddEthereumChainParams(req.params[0], end); + validParams = validateAddEthereumChainParams(req.params[0]); } catch (error) { return end(error); } @@ -189,8 +189,8 @@ async function addEthereumChainHandler( isAddFlow: true, setActiveNetwork, getCaveat, - requestPermittedChainsPermission, - grantPermittedChainsPermissionIncremental, + requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin, }); } diff --git a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.test.js b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.test.js index 8dc8f69e9761..dd01322b0249 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.test.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.test.js @@ -1,6 +1,13 @@ import { rpcErrors } from '@metamask/rpc-errors'; import { CHAIN_IDS } from '../../../../../shared/constants/network'; import addEthereumChain from './add-ethereum-chain'; +import EthChainUtils from './ethereum-chain-utils'; + +jest.mock('./ethereum-chain-utils', () => ({ + ...jest.requireActual('./ethereum-chain-utils'), + validateAddEthereumChainParams: jest.fn(), + switchChain: jest.fn(), +})); const NON_INFURA_CHAIN_ID = '0x123456789'; @@ -52,609 +59,280 @@ const createMockNonInfuraConfiguration = () => ({ defaultBlockExplorerUrlIndex: 0, }); -describe('addEthereumChainHandler', () => { - const addEthereumChainHandler = addEthereumChain.implementation; - const makeMocks = ({ permissionedChainIds = [], overrides = {} } = {}) => { - return { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(NON_INFURA_CHAIN_ID), - setNetworkClientIdForDomain: jest.fn(), - getNetworkConfigurationByChainId: jest.fn(), - setActiveNetwork: jest.fn(), - requestUserApproval: jest.fn().mockResolvedValue(123), - requestPermittedChainsPermission: jest.fn(), - grantPermittedChainsPermissionIncremental: jest.fn(), - getCaveat: jest.fn().mockReturnValue({ value: permissionedChainIds }), - addNetwork: jest.fn().mockResolvedValue({ - defaultRpcEndpointIndex: 0, - rpcEndpoints: [{ networkClientId: 123 }], - }), - updateNetwork: jest.fn().mockResolvedValue({ - defaultRpcEndpointIndex: 0, - rpcEndpoints: [{ networkClientId: 123 }], - }), - ...overrides, - }; +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const mocks = { + getCurrentChainIdForDomain: jest.fn().mockReturnValue(NON_INFURA_CHAIN_ID), + setNetworkClientIdForDomain: jest.fn(), + getNetworkConfigurationByChainId: jest.fn(), + setActiveNetwork: jest.fn(), + requestUserApproval: jest.fn().mockResolvedValue(123), + getCaveat: jest.fn(), + addNetwork: jest.fn().mockResolvedValue({ + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 123 }], + }), + updateNetwork: jest.fn().mockResolvedValue({ + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 123 }], + }), + requestPermittedChainsPermissionForOrigin: jest.fn(), + requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(), + }; + const response = {}; + const handler = (request) => + addEthereumChain.implementation(request, response, next, end, mocks); + + return { + mocks, + response, + next, + end, + handler, }; +}; + +describe('addEthereumChainHandler', () => { + beforeEach(() => { + EthChainUtils.validateAddEthereumChainParams.mockImplementation( + (params) => { + const { + chainId, + chainName, + blockExplorerUrls, + rpcUrls, + nativeCurrency, + } = params; + return { + chainId, + chainName, + firstValidBlockExplorerUrl: blockExplorerUrls[0] ?? null, + firstValidRPCUrl: rpcUrls[0], + ticker: nativeCurrency.symbol, + }; + }, + ); + }); afterEach(() => { jest.clearAllMocks(); }); - describe('with `endowment:permitted-chains` permissioning inactive', () => { - it('creates a new network configuration for the given chainid and switches to it if none exists', async () => { - const mocks = makeMocks(); - await addEthereumChainHandler( + it('should validate the request params', async () => { + const { handler } = createMockedHandler(); + + const request = { + origin: 'example.com', + params: [ { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.OPTIMISM, - chainName: 'Optimism Mainnet', - rpcUrls: ['https://optimism.llamarpc.com'], - nativeCurrency: { - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://optimistic.etherscan.io'], - iconUrls: ['https://optimism.icon.com'], - }, - ], + foo: true, }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); + ], + }; - expect(mocks.requestUserApproval).toHaveBeenCalledTimes(1); - expect(mocks.addNetwork).toHaveBeenCalledTimes(1); - expect(mocks.addNetwork).toHaveBeenCalledWith({ - blockExplorerUrls: ['https://optimistic.etherscan.io'], - defaultBlockExplorerUrlIndex: 0, - chainId: '0xa', - defaultRpcEndpointIndex: 0, - name: 'Optimism Mainnet', - nativeCurrency: 'ETH', - rpcEndpoints: [ - { - name: 'Optimism Mainnet', - url: 'https://optimism.llamarpc.com', - type: 'custom', - }, - ], - }); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith(123); + await handler(request); + + expect(EthChainUtils.validateAddEthereumChainParams).toHaveBeenCalledWith( + request.params[0], + ); + }); + + it('should return an error if request params validation fails', async () => { + const { end, handler } = createMockedHandler(); + EthChainUtils.validateAddEthereumChainParams.mockImplementation(() => { + throw new Error('failed to validate params'); }); - it('creates a new networkConfiguration when called without "blockExplorerUrls" property', async () => { - const mocks = makeMocks(); - await addEthereumChainHandler( + await handler({ + origin: 'example.com', + params: [{}], + }); + + expect(end).toHaveBeenCalledWith( + rpcErrors.invalidParams(new Error('failed to validate params')), + ); + }); + + it('creates a new network configuration for the given chainid and switches to it if no networkConfigurations with the same chainId exist', async () => { + const nonInfuraConfiguration = createMockNonInfuraConfiguration(); + + const { mocks, end, handler } = createMockedHandler(); + mocks.getCurrentChainIdForDomain.mockReturnValue(CHAIN_IDS.MAINNET); + + await handler({ + origin: 'example.com', + params: [ { + chainId: nonInfuraConfiguration.chainId, + chainName: nonInfuraConfiguration.name, + rpcUrls: nonInfuraConfiguration.rpcEndpoints.map((rpc) => rpc.url), + nativeCurrency: { + symbol: nonInfuraConfiguration.nativeCurrency, + decimals: 18, + }, + blockExplorerUrls: nonInfuraConfiguration.blockExplorerUrls, + }, + ], + }); + + expect(mocks.addNetwork).toHaveBeenCalledWith(nonInfuraConfiguration); + expect(EthChainUtils.switchChain).toHaveBeenCalledTimes(1); + expect(EthChainUtils.switchChain).toHaveBeenCalledWith( + {}, + end, + NON_INFURA_CHAIN_ID, + 123, + { + isAddFlow: true, + getCaveat: mocks.getCaveat, + setActiveNetwork: mocks.setActiveNetwork, + requestPermittedChainsPermissionForOrigin: + mocks.requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin: + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + }, + ); + }); + + describe('if a networkConfiguration for the given chainId already exists', () => { + describe('if the proposed networkConfiguration has a different rpcUrl from the one already in state', () => { + it('updates the network with a new networkConfiguration and switches to it', async () => { + const { mocks, end, handler } = createMockedHandler(); + mocks.getCurrentChainIdForDomain.mockReturnValue(CHAIN_IDS.SEPOLIA); + mocks.getNetworkConfigurationByChainId.mockReturnValue( + createMockMainnetConfiguration(), + ); + + await handler({ origin: 'example.com', params: [ { - chainId: CHAIN_IDS.OPTIMISM, - chainName: 'Optimism Mainnet', - rpcUrls: ['https://optimism.llamarpc.com'], + chainId: CHAIN_IDS.MAINNET, + chainName: 'Ethereum Mainnet', + rpcUrls: ['https://eth.llamarpc.com'], nativeCurrency: { symbol: 'ETH', decimals: 18, }, - iconUrls: ['https://optimism.icon.com'], + blockExplorerUrls: ['https://etherscan.io'], }, ], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - expect(mocks.addNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - }); - - describe('if a networkConfiguration for the given chainId already exists', () => { - it('updates the existing networkConfiguration with the new rpc url if it doesnt already exist', async () => { - const mocks = makeMocks({ - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - // Start with just infura endpoint - .mockReturnValue(createMockMainnetConfiguration()), - }, }); - // Add a custom endpoint - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.MAINNET, - chainName: 'Ethereum Mainnet', - rpcUrls: ['https://eth.llamarpc.com'], - nativeCurrency: { - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://etherscan.io'], - }, - ], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.updateNetwork).toHaveBeenCalledTimes(1); expect(mocks.updateNetwork).toHaveBeenCalledWith( '0x1', { + blockExplorerUrls: ['https://etherscan.io'], chainId: '0x1', + defaultBlockExplorerUrlIndex: 0, + defaultRpcEndpointIndex: 1, name: 'Ethereum Mainnet', - // Expect both endpoints + nativeCurrency: 'ETH', rpcEndpoints: [ { networkClientId: 'mainnet', - url: 'https://mainnet.infura.io/v3/', type: 'infura', + url: 'https://mainnet.infura.io/v3/', }, { name: 'Ethereum Mainnet', - url: 'https://eth.llamarpc.com', type: 'custom', + url: 'https://eth.llamarpc.com', }, ], - // and the new one is the default - defaultRpcEndpointIndex: 1, - nativeCurrency: 'ETH', - blockExplorerUrls: ['https://etherscan.io'], - defaultBlockExplorerUrlIndex: 0, }, undefined, ); - }); - - it('makes the rpc url the default if it already exists', async () => { - const existingNetwork = { - chainId: '0x1', - name: 'Ethereum Mainnet', - // Start with infura + custom endpoint - rpcEndpoints: [ - { - networkClientId: 'mainnet', - url: 'https://mainnet.infura.io/v3/', - type: 'infura', - }, - { - name: 'Ethereum Mainnet', - url: 'https://eth.llamarpc.com', - type: 'custom', - }, - ], - // Infura is the default - defaultRpcEndpointIndex: 0, - nativeCurrency: 'ETH', - blockExplorerUrls: ['https://etherscan.io'], - defaultBlockExplorerUrlIndex: 0, - }; - - const mocks = makeMocks({ - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(existingNetwork), - }, - }); - - // Add the same custom endpoint - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.MAINNET, - chainName: 'Ethereum Mainnet', - rpcUrls: ['https://eth.llamarpc.com'], - nativeCurrency: { - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://etherscan.io'], - }, - ], - }, + expect(EthChainUtils.switchChain).toHaveBeenCalledTimes(1); + expect(EthChainUtils.switchChain).toHaveBeenCalledWith( {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.updateNetwork).toHaveBeenCalledTimes(1); - expect(mocks.updateNetwork).toHaveBeenCalledWith( + end, '0x1', + 123, { - ...existingNetwork, - // Verify the custom endpoint becomes the default - defaultRpcEndpointIndex: 1, + isAddFlow: true, + getCaveat: mocks.getCaveat, + setActiveNetwork: mocks.setActiveNetwork, + requestPermittedChainsPermissionForOrigin: + mocks.requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin: + mocks.requestPermittedChainsPermissionIncrementalForOrigin, }, - undefined, - ); - }); - - it('switches to the network if its not already the currently selected chain id', async () => { - const existingNetwork = createMockMainnetConfiguration(); - - const mocks = makeMocks({ - overrides: { - // Start on sepolia - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.SEPOLIA), - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(existingNetwork), - }, - }); - - // Add with rpc + block explorers that already exist - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.MAINNET, - chainName: 'Ethereum Mainnet', - rpcUrls: [existingNetwork.rpcEndpoints[0].url], - nativeCurrency: { - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://etherscan.io'], - }, - ], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - - // No updates, network already had all the info - expect(mocks.updateNetwork).toHaveBeenCalledTimes(0); - - // User should be prompted to switch chains - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); - }); - - it('should return error for invalid chainId', async () => { - const mocks = makeMocks(); - const mockEnd = jest.fn(); - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [{ chainId: 'invalid_chain_id' }], - }, - {}, - jest.fn(), - mockEnd, - mocks, - ); - - expect(mockEnd).toHaveBeenCalledWith( - rpcErrors.invalidParams({ - message: `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\ninvalid_chain_id`, - }), ); }); }); - }); - - describe('with `endowment:permitted-chains` permissioning active', () => { - it('creates a new network configuration for the given chainid, requests `endowment:permitted-chains` permission and switches to it if no networkConfigurations with the same chainId exist', async () => { - const nonInfuraConfiguration = createMockNonInfuraConfiguration(); - const mocks = makeMocks({ - permissionedChainIds: [], - overrides: { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.MAINNET), - }, - }); - await addEthereumChainHandler( - { + describe('if the proposed networkConfiguration does not have a different rpcUrl from the one already in state', () => { + it('should only switch to the existing networkConfiguration if one already exists for the given chain id', async () => { + const { mocks, end, handler } = createMockedHandler(); + mocks.getCurrentChainIdForDomain.mockReturnValue(CHAIN_IDS.MAINNET); + mocks.getNetworkConfigurationByChainId.mockReturnValue( + createMockOptimismConfiguration(), + ); + await handler({ origin: 'example.com', params: [ { - chainId: nonInfuraConfiguration.chainId, - chainName: nonInfuraConfiguration.name, - rpcUrls: nonInfuraConfiguration.rpcEndpoints.map( + chainId: createMockOptimismConfiguration().chainId, + chainName: createMockOptimismConfiguration().name, + rpcUrls: createMockOptimismConfiguration().rpcEndpoints.map( (rpc) => rpc.url, ), nativeCurrency: { - symbol: nonInfuraConfiguration.nativeCurrency, + symbol: createMockOptimismConfiguration().nativeCurrency, decimals: 18, }, - blockExplorerUrls: nonInfuraConfiguration.blockExplorerUrls, + blockExplorerUrls: + createMockOptimismConfiguration().blockExplorerUrls, }, ], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.addNetwork).toHaveBeenCalledWith(nonInfuraConfiguration); - expect( - mocks.grantPermittedChainsPermissionIncremental, - ).toHaveBeenCalledTimes(1); - expect( - mocks.grantPermittedChainsPermissionIncremental, - ).toHaveBeenCalledWith([createMockNonInfuraConfiguration().chainId]); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith(123); - }); - - describe('if a networkConfiguration for the given chainId already exists', () => { - describe('if the proposed networkConfiguration has a different rpcUrl from the one already in state', () => { - it('create a new networkConfiguration and switches to it without requesting permissions, if the requested chainId has `endowment:permitted-chains` permission granted for requesting origin', async () => { - const mocks = makeMocks({ - permissionedChainIds: [CHAIN_IDS.MAINNET], - overrides: { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.SEPOLIA), - }, - }); - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.MAINNET, - chainName: 'Ethereum Mainnet', - rpcUrls: ['https://eth.llamarpc.com'], - nativeCurrency: { - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://etherscan.io'], - }, - ], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.requestUserApproval).toHaveBeenCalledTimes(1); - expect(mocks.requestPermittedChainsPermission).not.toHaveBeenCalled(); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith(123); - }); - - it('create a new networkConfiguration, requests permissions and switches to it, if the requested chainId does not have permittedChains permission granted for requesting origin', async () => { - const mocks = makeMocks({ - permissionedChainIds: [], - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(createMockNonInfuraConfiguration()), - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.MAINNET), - }, - }); - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: NON_INFURA_CHAIN_ID, - chainName: 'Custom Network', - rpcUrls: ['https://new-custom.network'], - nativeCurrency: { - symbol: 'CUST', - decimals: 18, - }, - blockExplorerUrls: ['https://custom.blockexplorer'], - }, - ], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.updateNetwork).toHaveBeenCalledTimes(1); - expect( - mocks.grantPermittedChainsPermissionIncremental, - ).toHaveBeenCalledTimes(1); - expect( - mocks.grantPermittedChainsPermissionIncremental, - ).toHaveBeenCalledWith([NON_INFURA_CHAIN_ID]); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - }); - }); - - it('should switch to the existing networkConfiguration if one already exsits for the given chain id', async () => { - const mocks = makeMocks({ - permissionedChainIds: [ - createMockOptimismConfiguration().chainId, - CHAIN_IDS.MAINNET, - ], - overrides: { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.MAINNET), - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(createMockOptimismConfiguration()), - }, }); - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: createMockOptimismConfiguration().chainId, - chainName: createMockOptimismConfiguration().name, - rpcUrls: createMockOptimismConfiguration().rpcEndpoints.map( - (rpc) => rpc.url, - ), - nativeCurrency: { - symbol: createMockOptimismConfiguration().nativeCurrency, - decimals: 18, - }, - blockExplorerUrls: - createMockOptimismConfiguration().blockExplorerUrls, - }, - ], - }, + expect(mocks.addNetwork).not.toHaveBeenCalled(); + expect(mocks.updateNetwork).not.toHaveBeenCalled(); + expect(EthChainUtils.switchChain).toHaveBeenCalledTimes(1); + expect(EthChainUtils.switchChain).toHaveBeenCalledWith( {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.requestPermittedChainsPermission).not.toHaveBeenCalled(); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( + end, + '0xa', createMockOptimismConfiguration().rpcEndpoints[0].networkClientId, + { + isAddFlow: true, + getCaveat: mocks.getCaveat, + setActiveNetwork: mocks.setActiveNetwork, + requestPermittedChainsPermissionForOrigin: + mocks.requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin: + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + }, ); }); }); }); - it('should return an error if an unexpected parameter is provided', async () => { - const mocks = makeMocks(); - const mockEnd = jest.fn(); - - const unexpectedParam = 'unexpected'; - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: createMockNonInfuraConfiguration().chainId, - chainName: createMockNonInfuraConfiguration().nickname, - rpcUrls: [createMockNonInfuraConfiguration().rpcUrl], - nativeCurrency: { - symbol: createMockNonInfuraConfiguration().ticker, - decimals: 18, - }, - blockExplorerUrls: [ - createMockNonInfuraConfiguration().blockExplorerUrls[0], - ], - [unexpectedParam]: 'parameter', - }, - ], - }, - {}, - jest.fn(), - mockEnd, - mocks, - ); - - expect(mockEnd).toHaveBeenCalledWith( - rpcErrors.invalidParams({ - message: `Received unexpected keys on object parameter. Unsupported keys:\n${unexpectedParam}`, - }), + it('should return an error if nativeCurrency.symbol does not match an existing network with the same chainId', async () => { + const { mocks, end, handler } = createMockedHandler(); + mocks.getNetworkConfigurationByChainId.mockReturnValue( + createMockMainnetConfiguration(), ); - }); - - it('should handle errors during the switch network permission request', async () => { - const mockError = new Error('Permission request failed'); - const mocks = makeMocks({ - permissionedChainIds: [], - overrides: { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.SEPOLIA), - grantPermittedChainsPermissionIncremental: jest - .fn() - .mockRejectedValue(mockError), - }, - }); - const mockEnd = jest.fn(); - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.MAINNET, - chainName: 'Ethereum Mainnet', - rpcUrls: ['https://mainnet.infura.io/v3/'], - nativeCurrency: { - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://etherscan.io'], + await handler({ + origin: 'example.com', + params: [ + { + chainId: CHAIN_IDS.MAINNET, + chainName: 'Ethereum Mainnet', + rpcUrls: ['https://mainnet.infura.io/v3/'], + nativeCurrency: { + symbol: 'WRONG', + decimals: 18, }, - ], - }, - {}, - jest.fn(), - mockEnd, - mocks, - ); - - expect( - mocks.grantPermittedChainsPermissionIncremental, - ).toHaveBeenCalledTimes(1); - expect(mockEnd).toHaveBeenCalledWith(mockError); - expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); - }); - - it('should return an error if nativeCurrency.symbol does not match an existing network with the same chainId', async () => { - const mocks = makeMocks({ - permissionedChainIds: [CHAIN_IDS.MAINNET], - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(createMockMainnetConfiguration()), - }, + blockExplorerUrls: ['https://etherscan.io'], + }, + ], }); - const mockEnd = jest.fn(); - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CHAIN_IDS.MAINNET, - chainName: 'Ethereum Mainnet', - rpcUrls: ['https://mainnet.infura.io/v3/'], - nativeCurrency: { - symbol: 'WRONG', - decimals: 18, - }, - blockExplorerUrls: ['https://etherscan.io'], - }, - ], - }, - {}, - jest.fn(), - mockEnd, - mocks, - ); - expect(mockEnd).toHaveBeenCalledWith( + expect(end).toHaveBeenCalledWith( rpcErrors.invalidParams({ message: `nativeCurrency.symbol does not match currency symbol for a network the user already has added with the same chainId. Received:\nWRONG`, }), @@ -664,39 +342,26 @@ describe('addEthereumChainHandler', () => { it('should add result set to null to response object if the requested rpcUrl (and chainId) is currently selected', async () => { const CURRENT_RPC_CONFIG = createMockNonInfuraConfiguration(); - const mocks = makeMocks({ - overrides: { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CURRENT_RPC_CONFIG.chainId), - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(CURRENT_RPC_CONFIG), - }, - }); - const res = {}; - - await addEthereumChainHandler( - { - origin: 'example.com', - params: [ - { - chainId: CURRENT_RPC_CONFIG.chainId, - chainName: 'Custom Network', - rpcUrls: [CURRENT_RPC_CONFIG.rpcEndpoints[0].url], - nativeCurrency: { - symbol: CURRENT_RPC_CONFIG.nativeCurrency, - decimals: 18, - }, - blockExplorerUrls: ['https://custom.blockexplorer'], - }, - ], - }, - res, - jest.fn(), - jest.fn(), - mocks, + const { mocks, response, handler } = createMockedHandler(); + mocks.getCurrentChainIdForDomain.mockReturnValue( + CURRENT_RPC_CONFIG.chainId, ); - expect(res.result).toBeNull(); + mocks.getNetworkConfigurationByChainId.mockReturnValue(CURRENT_RPC_CONFIG); + await handler({ + origin: 'example.com', + params: [ + { + chainId: CURRENT_RPC_CONFIG.chainId, + chainName: 'Custom Network', + rpcUrls: [CURRENT_RPC_CONFIG.rpcEndpoints[0].url], + nativeCurrency: { + symbol: CURRENT_RPC_CONFIG.nativeCurrency, + decimals: 18, + }, + blockExplorerUrls: ['https://custom.blockexplorer'], + }, + ], + }); + expect(response.result).toBeNull(); }); }); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.test.ts new file mode 100644 index 000000000000..7aa367ec6873 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.test.ts @@ -0,0 +1,51 @@ +import { + JsonRpcParams, + JsonRpcRequest, + PendingJsonRpcResponse, +} from '@metamask/utils'; +import ethereumAccounts from './eth-accounts'; + +const baseRequest = { + jsonrpc: '2.0' as const, + id: 0, + method: 'eth_accounts', + origin: 'http://test.com', +}; + +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const getAccounts = jest.fn().mockReturnValue(['0xdead', '0xbeef']); + const response: PendingJsonRpcResponse = { + jsonrpc: '2.0' as const, + id: 0, + }; + const handler = (request: JsonRpcRequest) => + ethereumAccounts.implementation(request, response, next, end, { + getAccounts, + }); + + return { + response, + next, + end, + getAccounts, + handler, + }; +}; + +describe('ethAccountsHandler', () => { + it('gets sorted eth accounts from the CAIP-25 permission via the getAccounts hook', async () => { + const { handler, getAccounts } = createMockedHandler(); + + await handler(baseRequest); + expect(getAccounts).toHaveBeenCalled(); + }); + + it('returns the accounts', async () => { + const { handler, response } = createMockedHandler(); + + await handler(baseRequest); + expect(response.result).toStrictEqual(['0xdead', '0xbeef']); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.ts b/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.ts index 47c2f0c2e318..1efa92121076 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.ts +++ b/app/scripts/lib/rpc-method-middleware/handlers/eth-accounts.ts @@ -8,17 +8,16 @@ import type { PendingJsonRpcResponse, } from '@metamask/utils'; import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; -import { AccountAddress } from '../../../controllers/account-order'; import { HandlerWrapper } from './types'; type EthAccountsHandlerOptions = { - getAccounts: () => Promise; + getAccounts: () => string[]; }; type EthAccountsConstraint = { implementation: ( _req: JsonRpcRequest, - res: PendingJsonRpcResponse, + res: PendingJsonRpcResponse, _next: JsonRpcEngineNextCallback, end: JsonRpcEngineEndCallback, { getAccounts }: EthAccountsHandlerOptions, @@ -44,16 +43,15 @@ export default ethAccounts; * @param _next - The json-rpc-engine 'next' callback. * @param end - The json-rpc-engine 'end' callback. * @param options - The RPC method hooks. - * @param options.getAccounts - Gets the accounts for the requesting - * origin. + * @param options.getAccounts - A hook that returns the permitted eth accounts for the origin sorted by lastSelected. */ async function ethAccountsHandler( _req: JsonRpcRequest, - res: PendingJsonRpcResponse, + res: PendingJsonRpcResponse, _next: JsonRpcEngineNextCallback, end: JsonRpcEngineEndCallback, { getAccounts }: EthAccountsHandlerOptions, ): Promise { - res.result = await getAccounts(); + res.result = getAccounts(); return end(); } diff --git a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js index 5a8dba32e337..db605e5fcc7a 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js @@ -1,31 +1,35 @@ import { rpcErrors } from '@metamask/rpc-errors'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, + getPermittedEthChainIds, +} from '@metamask/multichain'; import { isPrefixedFormattedHexString, isSafeChainId, } from '../../../../../shared/modules/network.utils'; -import { CaveatTypes } from '../../../../../shared/constants/permissions'; import { UNKNOWN_TICKER_SYMBOL } from '../../../../../shared/constants/app'; -import { PermissionNames } from '../../../controllers/permissions'; import { getValidUrl } from '../../util'; export function validateChainId(chainId) { - const _chainId = typeof chainId === 'string' && chainId.toLowerCase(); - if (!isPrefixedFormattedHexString(_chainId)) { + const lowercasedChainId = + typeof chainId === 'string' ? chainId.toLowerCase() : null; + if (!isPrefixedFormattedHexString(lowercasedChainId)) { throw rpcErrors.invalidParams({ message: `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\n${chainId}`, }); } - if (!isSafeChainId(parseInt(_chainId, 16))) { + if (!isSafeChainId(parseInt(chainId, 16))) { throw rpcErrors.invalidParams({ - message: `Invalid chain ID "${_chainId}": numerical value greater than max safe value. Received:\n${chainId}`, + message: `Invalid chain ID "${lowercasedChainId}": numerical value greater than max safe value. Received:\n${chainId}`, }); } - return _chainId; + return lowercasedChainId; } -export function validateSwitchEthereumChainParams(req, end) { +export function validateSwitchEthereumChainParams(req) { if (!req.params?.[0] || typeof req.params[0] !== 'object') { throw rpcErrors.invalidParams({ message: `Expected single, object parameter. Received:\n${JSON.stringify( @@ -43,10 +47,10 @@ export function validateSwitchEthereumChainParams(req, end) { }); } - return validateChainId(chainId, end); + return validateChainId(chainId); } -export function validateAddEthereumChainParams(params, end) { +export function validateAddEthereumChainParams(params) { if (!params || typeof params !== 'object') { throw rpcErrors.invalidParams({ message: `Expected single, object parameter. Received:\n${JSON.stringify( @@ -75,7 +79,7 @@ export function validateAddEthereumChainParams(params, end) { }); } - const _chainId = validateChainId(chainId, end); + const _chainId = validateChainId(chainId); if (!rpcUrls || !Array.isArray(rpcUrls) || rpcUrls.length === 0) { throw rpcErrors.invalidParams({ message: `Expected an array with at least one valid string HTTPS url 'rpcUrls', Received:\n${rpcUrls}`, @@ -152,8 +156,24 @@ export function validateAddEthereumChainParams(params, end) { }; } +/** + * Switches the active network for the origin if already permitted + * otherwise requests approval to update permission first. + * + * @param response - The JSON RPC request's response object. + * @param end - The JSON RPC request's end callback. + * @param {string} chainId - The chainId being switched to. + * @param {string} networkClientId - The network client being switched to. + * @param {object} hooks - The hooks object. + * @param {boolean} hooks.isAddFlow - The boolean determining if this call originates from wallet_addEthereumChain. + * @param {Function} hooks.setActiveNetwork - The callback to change the current network for the origin. + * @param {Function} hooks.getCaveat - The callback to get the CAIP-25 caveat for the origin. + * @param {Function} hooks.requestPermittedChainsPermissionForOrigin - The callback to request a new permittedChains-equivalent CAIP-25 permission. + * @param {Function} hooks.requestPermittedChainsPermissionIncrementalForOrigin - The callback to add a new chain to the permittedChains-equivalent CAIP-25 permission. + * @returns a null response on success or an error if user rejects an approval when isAddFlow is false or on unexpected errors. + */ export async function switchChain( - res, + response, end, chainId, networkClientId, @@ -161,30 +181,34 @@ export async function switchChain( isAddFlow, setActiveNetwork, getCaveat, - requestPermittedChainsPermission, - grantPermittedChainsPermissionIncremental, + requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin, }, ) { try { - const { value: permissionedChainIds } = - getCaveat({ - target: PermissionNames.permittedChains, - caveatType: CaveatTypes.restrictNetworkSwitching, - }) ?? {}; - - if ( - permissionedChainIds === undefined || - !permissionedChainIds.includes(chainId) - ) { - if (isAddFlow) { - await grantPermittedChainsPermissionIncremental([chainId]); - } else { - await requestPermittedChainsPermission([chainId]); + const caip25Caveat = getCaveat({ + target: Caip25EndowmentPermissionName, + caveatType: Caip25CaveatType, + }); + + if (caip25Caveat) { + const ethChainIds = getPermittedEthChainIds(caip25Caveat.value); + + if (!ethChainIds.includes(chainId)) { + await requestPermittedChainsPermissionIncrementalForOrigin({ + chainId, + autoApprove: isAddFlow, + }); } + } else { + await requestPermittedChainsPermissionForOrigin({ + chainId, + autoApprove: isAddFlow, + }); } await setActiveNetwork(networkClientId); - res.result = null; + response.result = null; return end(); } catch (error) { return end(error); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts new file mode 100644 index 000000000000..c0a8f3c493a0 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts @@ -0,0 +1,358 @@ +import { rpcErrors } from '@metamask/rpc-errors'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; +import { Hex } from '@metamask/utils'; +import * as EthChainUtils from './ethereum-chain-utils'; + +describe('Ethereum Chain Utils', () => { + const createMockedSwitchChain = () => { + const end = jest.fn(); + const mocks = { + isAddFlow: false, + setActiveNetwork: jest.fn(), + getCaveat: jest.fn(), + requestPermittedChainsPermissionForOrigin: jest.fn(), + requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(), + }; + const response: { result?: true } = {}; + const switchChain = (chainId: Hex, networkClientId: string) => + EthChainUtils.switchChain(response, end, chainId, networkClientId, mocks); + + return { + mocks, + response, + end, + switchChain, + }; + }; + + describe('switchChain', () => { + it('gets the CAIP-25 caveat', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + await switchChain('0x1', 'mainnet'); + + expect(mocks.getCaveat).toHaveBeenCalledWith({ + target: Caip25EndowmentPermissionName, + caveatType: Caip25CaveatType, + }); + }); + + it('passes through unexpected errors', async () => { + const { mocks, end, switchChain } = createMockedSwitchChain(); + mocks.requestPermittedChainsPermissionForOrigin.mockRejectedValueOnce( + new Error('unexpected error'), + ); + + await switchChain('0x1', 'mainnet'); + + expect(end).toHaveBeenCalledWith(new Error('unexpected error')); + }); + + describe('with no existing CAIP-25 permission', () => { + it('requests a switch chain approval without autoApprove if isAddFlow: false', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.isAddFlow = false; + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionForOrigin, + ).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: false }); + }); + + it('switches to the chain', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + await switchChain('0x1', 'mainnet'); + + expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + }); + + it('should handle errors if the switch chain grant fails', async () => { + const { mocks, end, switchChain } = createMockedSwitchChain(); + mocks.requestPermittedChainsPermissionForOrigin.mockRejectedValueOnce( + new Error('failed to grant permittedChains'), + ); + + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionForOrigin, + ).toHaveBeenCalled(); + expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); + expect(end).toHaveBeenCalledWith( + new Error('failed to grant permittedChains'), + ); + }); + }); + + describe('with an existing CAIP-25 permission granted from the legacy flow (isMultichainOrigin: false) and the chainId is not already permissioned', () => { + it('requests a switch chain approval with autoApprove and switches to it if isAddFlow: true', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.isAddFlow = true; + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + ).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: true }); + expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + }); + + it('requests permittedChains approval without autoApprove then switches to it if isAddFlow: false', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.isAddFlow = false; + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + ).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: false }); + expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + }); + + it('should handle errors if the permittedChains grant fails', async () => { + const { mocks, end, switchChain } = createMockedSwitchChain(); + mocks.requestPermittedChainsPermissionIncrementalForOrigin.mockRejectedValueOnce( + new Error('failed to grant permittedChains'), + ); + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + ).toHaveBeenCalled(); + expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); + expect(end).toHaveBeenCalledWith( + new Error('failed to grant permittedChains'), + ); + }); + }); + + describe('with an existing CAIP-25 permission granted from the multichain flow (isMultichainOrigin: true) and the chainId is not already permissioned', () => { + it('requests permittedChains approval', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.requestPermittedChainsPermissionIncrementalForOrigin.mockRejectedValue( + new Error( + "Cannot switch to or add permissions for chainId '0x1' because permissions were granted over the Multichain API.", + ), + ); + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: true, + }, + }); + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + ).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: false }); + }); + + it('does not switch the active network', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: true, + }, + }); + mocks.requestPermittedChainsPermissionIncrementalForOrigin.mockRejectedValue( + new Error( + "Cannot switch to or add permissions for chainId '0x1' because permissions were granted over the Multichain API.", + ), + ); + + await switchChain('0x1', 'mainnet'); + + expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); + }); + + it('return error about not being able to switch chain', async () => { + const { mocks, end, switchChain } = createMockedSwitchChain(); + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: true, + }, + }); + mocks.requestPermittedChainsPermissionIncrementalForOrigin.mockRejectedValue( + new Error( + "Cannot switch to or add permissions for chainId '0x1' because permissions were granted over the Multichain API.", + ), + ); + + await switchChain('0x1', 'mainnet'); + + expect(end).toHaveBeenCalledWith( + new Error( + "Cannot switch to or add permissions for chainId '0x1' because permissions were granted over the Multichain API.", + ), + ); + }); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + describe.each([ + ['legacy', false], + ['multichain', true], + ])( + 'with an existing CAIP-25 permission granted from the %s flow (isMultichainOrigin: %s) and the chainId is already permissioned', + (_type: string, isMultichainOrigin: boolean) => { + it('does not request permittedChains approval', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + }, + optionalScopes: {}, + isMultichainOrigin, + }, + }); + await switchChain('0x1', 'mainnet'); + + expect( + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + ).not.toHaveBeenCalled(); + }); + + it('switches the active network', async () => { + const { mocks, switchChain } = createMockedSwitchChain(); + mocks.getCaveat.mockReturnValue({ + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + }, + optionalScopes: {}, + isMultichainOrigin, + }, + }); + await switchChain('0x1', 'mainnet'); + + expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + }); + }, + ); + }); + + describe('validateAddEthereumChainParams', () => { + it('throws an error if an unexpected parameter is provided', () => { + const unexpectedParam = 'unexpected'; + + expect(() => { + EthChainUtils.validateAddEthereumChainParams({ + chainId: '0x1', + chainName: 'Mainnet', + rpcUrls: ['https://test.com/rpc'], + nativeCurrency: { + symbol: 'ETH', + decimals: 18, + }, + blockExplorerUrls: ['https://explorer.test.com/'], + [unexpectedParam]: 'parameter', + }); + }).toThrow( + rpcErrors.invalidParams({ + message: `Received unexpected keys on object parameter. Unsupported keys:\n${unexpectedParam}`, + }), + ); + }); + + it('returns a flattened version of params if it is valid', () => { + expect( + EthChainUtils.validateAddEthereumChainParams({ + chainId: '0x1', + chainName: 'Mainnet', + rpcUrls: ['https://test.com/rpc'], + nativeCurrency: { + symbol: 'ETH', + decimals: 18, + }, + blockExplorerUrls: ['https://explorer.test.com/'], + }), + ).toStrictEqual({ + chainId: '0x1', + chainName: 'Mainnet', + firstValidBlockExplorerUrl: 'https://explorer.test.com/', + firstValidRPCUrl: 'https://test.com/rpc', + ticker: 'ETH', + }); + }); + }); + + describe('validateSwitchEthereumChainParams', () => { + it('throws an error if an unexpected parameter is provided', () => { + const unexpectedParam = 'unexpected'; + + expect(() => { + EthChainUtils.validateSwitchEthereumChainParams({ + params: [ + { + chainId: '0x1', + [unexpectedParam]: 'parameter', + }, + ], + }); + }).toThrow( + rpcErrors.invalidParams({ + message: `Received unexpected keys on object parameter. Unsupported keys:\n${unexpectedParam}`, + }), + ); + }); + + it('throws an error for invalid chainId', async () => { + expect(() => { + EthChainUtils.validateSwitchEthereumChainParams({ + params: [ + { + chainId: 'invalid_chain_id', + }, + ], + }); + }).toThrow( + rpcErrors.invalidParams({ + message: `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\ninvalid_chain_id`, + }), + ); + }); + + it('returns the chainId if it is valid', () => { + expect( + EthChainUtils.validateSwitchEthereumChainParams({ + params: [ + { + chainId: '0x1', + }, + ], + }), + ).toStrictEqual('0x1'); + }); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/index.ts b/app/scripts/lib/rpc-method-middleware/handlers/index.ts index 09bca12b5b67..521cb32bec64 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/index.ts +++ b/app/scripts/lib/rpc-method-middleware/handlers/index.ts @@ -20,9 +20,7 @@ export const handlers = [ addEthereumChain, getProviderState, logWeb3ShimUsage, - requestAccounts, sendMetadata, - switchEthereumChain, watchAsset, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) mmiAuthenticate, @@ -34,4 +32,10 @@ export const handlers = [ ///: END:ONLY_INCLUDE_IF ]; -export const legacyHandlers = [ethAccounts]; +export const eip1193OnlyHandlers = [ + switchEthereumChain, + ethAccounts, + requestAccounts, +]; + +export const ethAccountsHandler = ethAccounts; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.js b/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.js index 68b52ea75549..0217ec104558 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.js @@ -6,26 +6,16 @@ import { } from '../../../../../shared/constants/metametrics'; import { shouldEmitDappViewedEvent } from '../../util'; -/** - * This method attempts to retrieve the Ethereum accounts available to the - * requester, or initiate a request for account access if none are currently - * available. It is essentially a wrapper of wallet_requestPermissions that - * only errors if the user rejects the request. We maintain the method for - * backwards compatibility reasons. - */ - const requestEthereumAccounts = { methodNames: [MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS], implementation: requestEthereumAccountsHandler, hookNames: { - origin: true, getAccounts: true, getUnlockPromise: true, - hasPermission: true, - requestAccountsPermission: true, sendMetrics: true, - getPermissionsForOrigin: true, metamaskState: true, + requestCaip25ApprovalForOrigin: true, + grantPermissionsForOrigin: true, }, }; export default requestEthereumAccounts; @@ -34,42 +24,40 @@ export default requestEthereumAccounts; const locks = new Set(); /** - * @typedef {Record} RequestEthereumAccountsOptions - * @property {string} origin - The requesting origin. - * @property {Function} getAccounts - Gets the accounts for the requesting - * origin. - * @property {Function} getUnlockPromise - Gets a promise that resolves when - * the extension unlocks. - * @property {Function} hasPermission - Returns whether the requesting origin - * has the specified permission. - * @property {Function} requestAccountsPermission - Requests the `eth_accounts` - * permission for the requesting origin. - */ - -/** + * This method attempts to retrieve the Ethereum accounts available to the + * requester, or initiate a request for account access if none are currently + * available. It is essentially a wrapper of wallet_requestPermissions that + * only errors if the user rejects the request. We maintain the method for + * backwards compatibility reasons. * - * @param {import('@metamask/utils').JsonRpcRequest} _req - The JSON-RPC request object. - * @param {import('@metamask/utils').JsonRpcResponse} res - The JSON-RPC response object. - * @param {Function} _next - The json-rpc-engine 'next' callback. - * @param {Function} end - The json-rpc-engine 'end' callback. - * @param {RequestEthereumAccountsOptions} options - The RPC method hooks. + * @param req - The JsonRpcEngine request + * @param res - The JsonRpcEngine result object + * @param _next - JsonRpcEngine next() callback - unused + * @param end - JsonRpcEngine end() callback + * @param options - Method hooks passed to the method implementation + * @param options.getAccounts - A hook that returns the permitted eth accounts for the origin sorted by lastSelected. + * @param options.getUnlockPromise - A hook that resolves when the wallet is unlocked. + * @param options.sendMetrics - A hook that helps track metric events. + * @param options.metamaskState - The MetaMask app state. + * @param options.requestCaip25ApprovalForOrigin - A hook that requests approval for the CAIP-25 permission for the origin. + * @param options.grantPermissionsForOrigin - A hook that grants permission for the approved permissions for the origin. + * @returns A promise that resolves to nothing */ async function requestEthereumAccountsHandler( - _req, + req, res, _next, end, { - origin, getAccounts, getUnlockPromise, - hasPermission, - requestAccountsPermission, sendMetrics, - getPermissionsForOrigin, metamaskState, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, }, ) { + const { origin } = req; if (locks.has(origin)) { res.error = rpcErrors.resourceUnavailable( `Already processing ${MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS}. Please wait.`, @@ -77,14 +65,15 @@ async function requestEthereumAccountsHandler( return end(); } - if (hasPermission(MESSAGE_TYPE.ETH_ACCOUNTS)) { + let ethAccounts = getAccounts({ ignoreLock: true }); + if (ethAccounts.length > 0) { // We wait for the extension to unlock in this case only, because permission // requests are handled when the extension is unlocked, regardless of the // lock state when they were received. try { locks.add(origin); await getUnlockPromise(true); - res.result = await getAccounts(); + res.result = ethAccounts; end(); } catch (error) { end(error); @@ -94,48 +83,38 @@ async function requestEthereumAccountsHandler( return undefined; } - // If no accounts, request the accounts permission try { - await requestAccountsPermission(); - } catch (err) { - res.error = err; - return end(); + const caip25Approval = await requestCaip25ApprovalForOrigin(); + await grantPermissionsForOrigin(caip25Approval); + } catch (error) { + return end(error); } - // Get the approved accounts - const accounts = await getAccounts(); - /* istanbul ignore else: too hard to induce, see below comment */ - if (accounts.length > 0) { - res.result = accounts; - const numberOfConnectedAccounts = - getPermissionsForOrigin(origin).eth_accounts.caveats[0].value.length; - // first time connection to dapp will lead to no log in the permissionHistory - // and if user has connected to dapp before, the dapp origin will be included in the permissionHistory state - // we will leverage that to identify `is_first_visit` for metrics + // We cannot derive ethAccounts directly from the CAIP-25 permission + // because the accounts will not be in order of lastSelected + ethAccounts = getAccounts({ ignoreLock: true }); + + // first time connection to dapp will lead to no log in the permissionHistory + // and if user has connected to dapp before, the dapp origin will be included in the permissionHistory state + // we will leverage that to identify `is_first_visit` for metrics + if (shouldEmitDappViewedEvent(metamaskState.metaMetricsId)) { const isFirstVisit = !Object.keys(metamaskState.permissionHistory).includes( origin, ); - if (shouldEmitDappViewedEvent(metamaskState.metaMetricsId)) { - sendMetrics({ - event: MetaMetricsEventName.DappViewed, - category: MetaMetricsEventCategory.InpageProvider, - referrer: { - url: origin, - }, - properties: { - is_first_visit: isFirstVisit, - number_of_accounts: Object.keys(metamaskState.accounts).length, - number_of_accounts_connected: numberOfConnectedAccounts, - }, - }); - } - } else { - // This should never happen, because it should be caught in the - // above catch clause - res.error = rpcErrors.internal( - 'Accounts unexpectedly unavailable. Please report this bug.', - ); + sendMetrics({ + event: MetaMetricsEventName.DappViewed, + category: MetaMetricsEventCategory.InpageProvider, + referrer: { + url: origin, + }, + properties: { + is_first_visit: isFirstVisit, + number_of_accounts: Object.keys(metamaskState.accounts).length, + number_of_accounts_connected: ethAccounts.length, + }, + }); } + res.result = ethAccounts; return end(); } diff --git a/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.test.ts new file mode 100644 index 000000000000..72cce380ab96 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/request-accounts.test.ts @@ -0,0 +1,208 @@ +import { rpcErrors } from '@metamask/rpc-errors'; +import { + JsonRpcParams, + JsonRpcRequest, + PendingJsonRpcResponse, +} from '@metamask/utils'; +import { deferredPromise } from '../../util'; +import * as Util from '../../util'; +import { flushPromises } from '../../../../../test/lib/timer-helpers'; +import requestEthereumAccounts from './request-accounts'; + +jest.mock('../../util', () => ({ + ...jest.requireActual('../../util'), + shouldEmitDappViewedEvent: jest.fn(), +})); +const MockUtil = jest.mocked(Util); + +const baseRequest = { + jsonrpc: '2.0' as const, + id: 0, + method: 'eth_requestAccounts', + networkClientId: 'mainnet', + origin: 'http://test.com', + params: [], +}; + +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const getAccounts = jest.fn().mockReturnValue([]); + const getUnlockPromise = jest.fn(); + const sendMetrics = jest.fn(); + const metamaskState = { + permissionHistory: {}, + metaMetricsId: 'metaMetricsId', + accounts: { + '0x1': {}, + '0x2': {}, + '0x3': {}, + }, + }; + const requestCaip25ApprovalForOrigin = jest.fn().mockResolvedValue({}); + const grantPermissionsForOrigin = jest.fn().mockReturnValue({}); + const response: PendingJsonRpcResponse = { + jsonrpc: '2.0' as const, + id: 0, + result: undefined, + }; + const handler = ( + request: JsonRpcRequest & { origin: string }, + ) => + requestEthereumAccounts.implementation(request, response, next, end, { + getAccounts, + getUnlockPromise, + sendMetrics, + metamaskState, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + }); + + return { + response, + next, + end, + getAccounts, + getUnlockPromise, + sendMetrics, + metamaskState, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + handler, + }; +}; + +describe('requestEthereumAccountsHandler', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('checks if there are any eip155 accounts permissioned', async () => { + const { handler, getAccounts } = createMockedHandler(); + + await handler(baseRequest); + expect(getAccounts).toHaveBeenCalledWith({ ignoreLock: true }); + }); + + describe('eip155 account permissions exist', () => { + it('waits for the wallet to unlock', async () => { + const { handler, getUnlockPromise, getAccounts } = createMockedHandler(); + getAccounts.mockReturnValue(['0xdead', '0xbeef']); + + await handler(baseRequest); + expect(getUnlockPromise).toHaveBeenCalledWith(true); + }); + + it('returns the accounts', async () => { + const { handler, response, getAccounts } = createMockedHandler(); + getAccounts.mockReturnValue(['0xdead', '0xbeef']); + + await handler(baseRequest); + expect(response.result).toStrictEqual(['0xdead', '0xbeef']); + }); + + it('blocks subsequent requests if there is currently a request waiting for the wallet to be unlocked', async () => { + const { handler, getUnlockPromise, getAccounts, end, response } = + createMockedHandler(); + const { promise, resolve } = deferredPromise(); + getUnlockPromise.mockReturnValue(promise); + getAccounts.mockReturnValue(['0xdead', '0xbeef']); + + handler(baseRequest); + expect(response).toStrictEqual({ + id: 0, + jsonrpc: '2.0', + result: undefined, + }); + expect(end).not.toHaveBeenCalled(); + + await flushPromises(); + + await handler(baseRequest); + expect(response.error).toStrictEqual( + rpcErrors.resourceUnavailable( + `Already processing eth_requestAccounts. Please wait.`, + ), + ); + expect(end).toHaveBeenCalledTimes(1); + resolve?.(); + }); + }); + + describe('eip155 account permissions do not exist', () => { + it('requests the CAIP-25 approval', async () => { + const { handler, requestCaip25ApprovalForOrigin } = createMockedHandler(); + + await handler({ ...baseRequest, origin: 'http://test.com' }); + expect(requestCaip25ApprovalForOrigin).toHaveBeenCalledWith(); + }); + + it('throws an error if the CAIP-25 approval is rejected', async () => { + const { handler, requestCaip25ApprovalForOrigin, end } = + createMockedHandler(); + requestCaip25ApprovalForOrigin.mockRejectedValue( + new Error('approval rejected'), + ); + + await handler(baseRequest); + expect(end).toHaveBeenCalledWith(new Error('approval rejected')); + }); + + it('grants the CAIP-25 approval', async () => { + const { + handler, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + } = createMockedHandler(); + + requestCaip25ApprovalForOrigin.mockResolvedValue({ foo: 'bar' }); + + await handler({ ...baseRequest, origin: 'http://test.com' }); + expect(grantPermissionsForOrigin).toHaveBeenCalledWith({ foo: 'bar' }); + }); + + it('returns the newly granted and properly ordered eth accounts', async () => { + const { handler, getAccounts, response } = createMockedHandler(); + getAccounts + .mockReturnValueOnce([]) + .mockReturnValueOnce(['0xdead', '0xbeef']); + + await handler(baseRequest); + expect(response.result).toStrictEqual(['0xdead', '0xbeef']); + expect(getAccounts).toHaveBeenCalledTimes(2); + }); + + it('emits the dapp viewed metrics event when shouldEmitDappViewedEvent returns true', async () => { + const { handler, getAccounts, sendMetrics } = createMockedHandler(); + getAccounts + .mockReturnValueOnce([]) + .mockReturnValueOnce(['0xdead', '0xbeef']); + MockUtil.shouldEmitDappViewedEvent.mockReturnValue(true); + + await handler(baseRequest); + expect(sendMetrics).toHaveBeenCalledWith({ + category: 'inpage_provider', + event: 'Dapp Viewed', + properties: { + is_first_visit: true, + number_of_accounts: 3, + number_of_accounts_connected: 2, + }, + referrer: { + url: 'http://test.com', + }, + }); + }); + + it('does not emit the dapp viewed metrics event when shouldEmitDappViewedEvent returns false', async () => { + const { handler, getAccounts, sendMetrics } = createMockedHandler(); + getAccounts + .mockReturnValueOnce([]) + .mockReturnValueOnce(['0xdead', '0xbeef']); + MockUtil.shouldEmitDappViewedEvent.mockReturnValue(false); + + await handler(baseRequest); + expect(sendMetrics).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js index 48e66b047def..9bed994ed33a 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js @@ -12,9 +12,9 @@ const switchEthereumChain = { getNetworkConfigurationByChainId: true, setActiveNetwork: true, getCaveat: true, - requestPermittedChainsPermission: true, getCurrentChainIdForDomain: true, - grantPermittedChainsPermissionIncremental: true, + requestPermittedChainsPermissionForOrigin: true, + requestPermittedChainsPermissionIncrementalForOrigin: true, }, }; @@ -28,15 +28,15 @@ async function switchEthereumChainHandler( { getNetworkConfigurationByChainId, setActiveNetwork, - requestPermittedChainsPermission, getCaveat, getCurrentChainIdForDomain, - grantPermittedChainsPermissionIncremental, + requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin, }, ) { let chainId; try { - chainId = validateSwitchEthereumChainParams(req, end); + chainId = validateSwitchEthereumChainParams(req); } catch (error) { return end(error); } @@ -67,7 +67,7 @@ async function switchEthereumChainHandler( return switchChain(res, end, chainId, networkClientIdToSwitchTo, { setActiveNetwork, getCaveat, - requestPermittedChainsPermission, - grantPermittedChainsPermissionIncremental, + requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin, }); } diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js index be612fbc7d8e..fedf456eb61c 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js @@ -1,8 +1,16 @@ +import { providerErrors } from '@metamask/rpc-errors'; import { CHAIN_IDS, NETWORK_TYPES, } from '../../../../../shared/constants/network'; import switchEthereumChain from './switch-ethereum-chain'; +import EthChainUtils from './ethereum-chain-utils'; + +jest.mock('./ethereum-chain-utils', () => ({ + ...jest.requireActual('./ethereum-chain-utils'), + validateSwitchEthereumChainParams: jest.fn(), + switchChain: jest.fn(), +})); const NON_INFURA_CHAIN_ID = '0x123456789'; @@ -26,257 +34,144 @@ const createMockLineaMainnetConfiguration = () => ({ ], }); -describe('switchEthereumChainHandler', () => { - const makeMocks = ({ - permissionedChainIds = [], - overrides = {}, - mockedGetNetworkConfigurationByChainIdReturnValue = createMockMainnetConfiguration(), - mockedGetCurrentChainIdForDomainReturnValue = NON_INFURA_CHAIN_ID, - } = {}) => { - const mockGetCaveat = jest.fn(); - mockGetCaveat.mockReturnValue({ value: permissionedChainIds }); - - return { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(mockedGetCurrentChainIdForDomainReturnValue), - setNetworkClientIdForDomain: jest.fn(), - setActiveNetwork: jest.fn(), - requestPermittedChainsPermission: jest.fn(), - getCaveat: mockGetCaveat, - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(mockedGetNetworkConfigurationByChainIdReturnValue), - ...overrides, - }; +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const mocks = { + getNetworkConfigurationByChainId: jest + .fn() + .mockReturnValue(createMockMainnetConfiguration()), + setActiveNetwork: jest.fn(), + getCaveat: jest.fn(), + getCurrentChainIdForDomain: jest.fn().mockReturnValue(NON_INFURA_CHAIN_ID), + requestPermittedChainsPermissionForOrigin: jest.fn(), + requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(), + }; + const response = {}; + const handler = (request) => + switchEthereumChain.implementation(request, response, next, end, mocks); + + return { + mocks, + response, + next, + end, + handler, }; +}; + +describe('switchEthereumChainHandler', () => { + beforeEach(() => { + EthChainUtils.validateSwitchEthereumChainParams.mockImplementation( + (request) => { + return request.params[0].chainId; + }, + ); + }); afterEach(() => { jest.clearAllMocks(); }); - describe('with permittedChains permissioning inactive', () => { - it('should call setActiveNetwork when switching to a built-in infura network', async () => { - const mocks = makeMocks({ - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(createMockMainnetConfiguration()), - }, - }); - const switchEthereumChainHandler = switchEthereumChain.implementation; - await switchEthereumChainHandler( - { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.MAINNET }], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( - createMockMainnetConfiguration().rpcEndpoints[0].networkClientId, - ); - }); + it('should validate the request params', async () => { + const { handler } = createMockedHandler(); - it('should call setActiveNetwork when switching to a built-in infura network, when chainId from request is lower case', async () => { - const mocks = makeMocks({ - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(createMockLineaMainnetConfiguration()), - }, - }); - const switchEthereumChainHandler = switchEthereumChain.implementation; - await switchEthereumChainHandler( + const request = { + origin: 'example.com', + params: [ { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.LINEA_MAINNET.toLowerCase() }], + foo: true, }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( - createMockLineaMainnetConfiguration().rpcEndpoints[0].networkClientId, - ); - }); + ], + }; - it('should call setActiveNetwork when switching to a built-in infura network, when chainId from request is upper case', async () => { - const mocks = makeMocks({ - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(createMockLineaMainnetConfiguration()), - }, - }); - const switchEthereumChainHandler = switchEthereumChain.implementation; - await switchEthereumChainHandler( - { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.LINEA_MAINNET.toUpperCase() }], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( - createMockLineaMainnetConfiguration().rpcEndpoints[0].networkClientId, - ); - }); + await handler(request); - it('should call setActiveNetwork when switching to a custom network', async () => { - const mocks = makeMocks({ - overrides: { - getCurrentChainIdForDomain: jest - .fn() - .mockReturnValue(CHAIN_IDS.MAINNET), - }, - }); - const switchEthereumChainHandler = switchEthereumChain.implementation; - await switchEthereumChainHandler( - { - origin: 'example.com', - params: [{ chainId: NON_INFURA_CHAIN_ID }], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( - createMockMainnetConfiguration().rpcEndpoints[0].networkClientId, - ); - }); + expect( + EthChainUtils.validateSwitchEthereumChainParams, + ).toHaveBeenCalledWith(request); + }); - it('should handle missing networkConfiguration', async () => { - // Mock a network configuration that has an undefined or missing rpcEndpoints - const mockNetworkConfiguration = undefined; + it('should return an error if request params validation fails', async () => { + const { end, handler } = createMockedHandler(); + EthChainUtils.validateSwitchEthereumChainParams.mockImplementation(() => { + throw new Error('failed to validate params'); + }); - const mocks = makeMocks({ - overrides: { - getNetworkConfigurationByChainId: jest - .fn() - .mockReturnValue(mockNetworkConfiguration), - }, - }); + await handler({ + origin: 'example.com', + params: [{}], + }); - const switchEthereumChainHandler = switchEthereumChain.implementation; + expect(end).toHaveBeenCalledWith(new Error('failed to validate params')); + }); - const mockEnd = jest.fn(); - await switchEthereumChainHandler( + it('returns null and does not try to switch the network if the current chain id for the domain matches the chainId in the params', async () => { + const { end, response, handler } = createMockedHandler(); + await handler({ + origin: 'example.com', + params: [ { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.MAINNET }], + chainId: NON_INFURA_CHAIN_ID, }, - {}, - jest.fn(), - mockEnd, - mocks, - ); - - // Check that the function handled the missing rpcEndpoints and did not attempt to call setActiveNetwork - expect(mockEnd).toHaveBeenCalledWith( - expect.objectContaining({ - code: 4902, - message: expect.stringContaining('Unrecognized chain ID'), - }), - ); - expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); + ], }); - }); - describe('with permittedChains permissioning active', () => { - it('should call requestPermittedChainsPermission and setActiveNetwork when chainId is not in `endowment:permitted-chains`', async () => { - const mockrequestPermittedChainsPermission = jest - .fn() - .mockResolvedValue(); - const mocks = makeMocks({ - overrides: { - requestPermittedChainsPermission: - mockrequestPermittedChainsPermission, - }, - }); - const switchEthereumChainHandler = switchEthereumChain.implementation; - await switchEthereumChainHandler( - { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.MAINNET }], - }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); + expect(response.result).toStrictEqual(null); + expect(end).toHaveBeenCalled(); + expect(EthChainUtils.switchChain).not.toHaveBeenCalled(); + }); - expect(mocks.requestPermittedChainsPermission).toHaveBeenCalledTimes(1); - expect(mocks.requestPermittedChainsPermission).toHaveBeenCalledWith([ - CHAIN_IDS.MAINNET, - ]); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( - createMockMainnetConfiguration().rpcEndpoints[0].networkClientId, - ); - }); + it('throws an error and does not try to switch the network if unable to find a network matching the chainId in the params', async () => { + const { mocks, end, handler } = createMockedHandler(); + mocks.getCurrentChainIdForDomain.mockReturnValue('0x1'); + mocks.getNetworkConfigurationByChainId.mockReturnValue(undefined); - it('should call setActiveNetwork without calling requestPermittedChainsPermission when requested chainId is in `endowment:permitted-chains`', async () => { - const mocks = makeMocks({ - permissionedChainIds: [CHAIN_IDS.MAINNET], - }); - const switchEthereumChainHandler = switchEthereumChain.implementation; - await switchEthereumChainHandler( + await handler({ + origin: 'example.com', + params: [ { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.MAINNET }], + chainId: NON_INFURA_CHAIN_ID, }, - {}, - jest.fn(), - jest.fn(), - mocks, - ); - - expect(mocks.requestPermittedChainsPermission).not.toHaveBeenCalled(); - expect(mocks.setActiveNetwork).toHaveBeenCalledTimes(1); - expect(mocks.setActiveNetwork).toHaveBeenCalledWith( - createMockMainnetConfiguration().rpcEndpoints[0].networkClientId, - ); + ], }); - it('should handle errors during the switch network permission request', async () => { - const mockError = new Error('Permission request failed'); - const mockrequestPermittedChainsPermission = jest - .fn() - .mockRejectedValue(mockError); - const mocks = makeMocks({ - overrides: { - requestPermittedChainsPermission: - mockrequestPermittedChainsPermission, - }, - }); - const mockEnd = jest.fn(); - const switchEthereumChainHandler = switchEthereumChain.implementation; + expect(end).toHaveBeenCalledWith( + providerErrors.custom({ + code: 4902, + message: `Unrecognized chain ID "${NON_INFURA_CHAIN_ID}". Try adding the chain using wallet_addEthereumChain first.`, + }), + ); + expect(EthChainUtils.switchChain).not.toHaveBeenCalled(); + }); - await switchEthereumChainHandler( + it('tries to switch the network', async () => { + const { mocks, end, handler } = createMockedHandler(); + mocks.getNetworkConfigurationByChainId + .mockReturnValueOnce(createMockMainnetConfiguration()) + .mockReturnValueOnce(createMockLineaMainnetConfiguration()); + await handler({ + origin: 'example.com', + params: [ { - origin: 'example.com', - params: [{ chainId: CHAIN_IDS.MAINNET }], + chainId: '0xdeadbeef', }, - {}, - jest.fn(), - mockEnd, - mocks, - ); - - expect(mocks.requestPermittedChainsPermission).toHaveBeenCalledTimes(1); - expect(mockEnd).toHaveBeenCalledWith(mockError); - expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); + ], }); + + expect(EthChainUtils.switchChain).toHaveBeenCalledWith( + {}, + end, + '0xdeadbeef', + 'mainnet', + { + setActiveNetwork: mocks.setActiveNetwork, + getCaveat: mocks.getCaveat, + requestPermittedChainsPermissionForOrigin: + mocks.requestPermittedChainsPermissionForOrigin, + requestPermittedChainsPermissionIncrementalForOrigin: + mocks.requestPermittedChainsPermissionIncrementalForOrigin, + }, + ); }); }); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.test.ts new file mode 100644 index 000000000000..2cc8f19fb3ca --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.test.ts @@ -0,0 +1,357 @@ +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; +import * as Multichain from '@metamask/multichain'; +import { Json, JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; +import { + CaveatTypes, + RestrictedMethods, +} from '../../../../../shared/constants/permissions'; +import { PermissionNames } from '../../../controllers/permissions'; +import { getPermissionsHandler } from './wallet-getPermissions'; + +jest.mock('@metamask/multichain', () => ({ + ...jest.requireActual('@metamask/multichain'), + getPermittedEthChainIds: jest.fn(), +})); +const MockMultichain = jest.mocked(Multichain); + +const baseRequest = { + jsonrpc: '2.0' as const, + id: 0, + method: 'wallet_getPermissions', +}; + +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const getPermissionsForOrigin = jest.fn().mockReturnValue( + Object.freeze({ + [Caip25EndowmentPermissionName]: { + id: '1', + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:1': { + accounts: ['eip155:1:0x1', 'eip155:1:0x2'], + }, + 'eip155:5': { + accounts: ['eip155:5:0x1', 'eip155:5:0x3'], + }, + }, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdeadbeef'], + }, + }, + }, + }, + ], + }, + otherPermission: { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + }), + ); + const getAccounts = jest.fn().mockReturnValue([]); + const response: PendingJsonRpcResponse = { + jsonrpc: '2.0' as const, + id: 0, + }; + const handler = (request: JsonRpcRequest) => + getPermissionsHandler.implementation(request, response, next, end, { + getPermissionsForOrigin, + getAccounts, + }); + + return { + response, + next, + end, + getPermissionsForOrigin, + getAccounts, + handler, + }; +}; + +describe('getPermissionsHandler', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + beforeEach(() => { + MockMultichain.getPermittedEthChainIds.mockReturnValue([]); + }); + + it('gets the permissions for the origin', async () => { + const { handler, getPermissionsForOrigin } = createMockedHandler(); + + await handler(baseRequest); + expect(getPermissionsForOrigin).toHaveBeenCalled(); + }); + + it('returns permissions unmodified if no CAIP-25 endowment permission has been granted', async () => { + const { handler, getPermissionsForOrigin, response } = + createMockedHandler(); + + getPermissionsForOrigin.mockReturnValue( + Object.freeze({ + otherPermission: { + id: '1', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + }), + ); + + await handler(baseRequest); + expect(response.result).toStrictEqual([ + { + id: '1', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + ]); + }); + + describe('CAIP-25 endowment permissions has been granted', () => { + it('returns the permissions with the CAIP-25 permission removed', async () => { + const { handler, getAccounts, getPermissionsForOrigin, response } = + createMockedHandler(); + getPermissionsForOrigin.mockReturnValue( + Object.freeze({ + [Caip25EndowmentPermissionName]: { + id: '1', + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: {}, + }, + }, + ], + }, + otherPermission: { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + }), + ); + getAccounts.mockReturnValue([]); + MockMultichain.getPermittedEthChainIds.mockReturnValue([]); + await handler(baseRequest); + expect(response.result).toStrictEqual([ + { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + ]); + }); + + it('gets the lastSelected sorted permissioned eth accounts for the origin', async () => { + const { handler, getAccounts } = createMockedHandler(); + await handler(baseRequest); + expect(getAccounts).toHaveBeenCalledWith({ ignoreLock: true }); + }); + + it('returns the permissions with an eth_accounts permission if some eth accounts are permissioned', async () => { + const { handler, getAccounts, response } = createMockedHandler(); + getAccounts.mockReturnValue(['0x1', '0x2', '0x3', '0xdeadbeef']); + + await handler(baseRequest); + expect(response.result).toStrictEqual([ + { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + { + id: '1', + parentCapability: RestrictedMethods.eth_accounts, + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x2', '0x3', '0xdeadbeef'], + }, + ], + }, + ]); + }); + + it('gets the permitted eip155 chainIds from the CAIP-25 caveat value', async () => { + const { handler, getPermissionsForOrigin } = createMockedHandler(); + getPermissionsForOrigin.mockReturnValue( + Object.freeze({ + [Caip25EndowmentPermissionName]: { + id: '1', + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:5': { + accounts: [], + }, + }, + optionalScopes: { + 'eip155:1': { + accounts: [], + }, + }, + }, + }, + ], + }, + otherPermission: { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + }), + ); + await handler(baseRequest); + expect(MockMultichain.getPermittedEthChainIds).toHaveBeenCalledWith({ + requiredScopes: { + 'eip155:1': { + accounts: [], + }, + 'eip155:5': { + accounts: [], + }, + }, + optionalScopes: { + 'eip155:1': { + accounts: [], + }, + }, + }); + }); + + it('returns the permissions with a permittedChains permission if some eip155 chainIds are permissioned', async () => { + const { handler, response } = createMockedHandler(); + MockMultichain.getPermittedEthChainIds.mockReturnValue(['0x1', '0x64']); + + await handler(baseRequest); + expect(response.result).toStrictEqual([ + { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + { + id: '1', + parentCapability: PermissionNames.permittedChains, + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x1', '0x64'], + }, + ], + }, + ]); + }); + + it('returns the permissions with a eth_accounts and permittedChains permission if some eip155 accounts and chainIds are permissioned', async () => { + const { handler, getAccounts, response } = createMockedHandler(); + getAccounts.mockReturnValue(['0x1', '0x2', '0xdeadbeef']); + MockMultichain.getPermittedEthChainIds.mockReturnValue(['0x1', '0x64']); + + await handler(baseRequest); + expect(response.result).toStrictEqual([ + { + id: '2', + parentCapability: 'otherPermission', + caveats: [ + { + value: { + foo: 'bar', + }, + }, + ], + }, + { + id: '1', + parentCapability: RestrictedMethods.eth_accounts, + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x2', '0xdeadbeef'], + }, + ], + }, + { + id: '1', + parentCapability: PermissionNames.permittedChains, + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x1', '0x64'], + }, + ], + }, + ]); + }); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.ts new file mode 100644 index 000000000000..0ff95c327a59 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-getPermissions.ts @@ -0,0 +1,106 @@ +import { + CaveatSpecificationConstraint, + MethodNames, + PermissionController, + PermissionSpecificationConstraint, +} from '@metamask/permission-controller'; +import { + Caip25CaveatType, + Caip25CaveatValue, + Caip25EndowmentPermissionName, + getPermittedEthChainIds, +} from '@metamask/multichain'; +import { + AsyncJsonRpcEngineNextCallback, + JsonRpcEngineEndCallback, +} from '@metamask/json-rpc-engine'; +import { Json, JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; +import { PermissionNames } from '../../../controllers/permissions'; +import { + CaveatTypes, + RestrictedMethods, +} from '../../../../../shared/constants/permissions'; + +export const getPermissionsHandler = { + methodNames: [MethodNames.GetPermissions], + implementation: getPermissionsImplementation, + hookNames: { + getPermissionsForOrigin: true, + getAccounts: true, + }, +}; + +/** + * Get Permissions implementation to be used in JsonRpcEngine middleware. + * + * @param _req - The JsonRpcEngine request - unused + * @param res - The JsonRpcEngine result object + * @param _next - JsonRpcEngine next() callback - unused + * @param end - JsonRpcEngine end() callback + * @param options - Method hooks passed to the method implementation + * @param options.getPermissionsForOrigin - The specific method hook needed for this method implementation + * @param options.getAccounts - A hook that returns the permitted eth accounts for the origin sorted by lastSelected. + * @returns A promise that resolves to nothing + */ +async function getPermissionsImplementation( + _req: JsonRpcRequest, + res: PendingJsonRpcResponse, + _next: AsyncJsonRpcEngineNextCallback, + end: JsonRpcEngineEndCallback, + { + getPermissionsForOrigin, + getAccounts, + }: { + getPermissionsForOrigin: () => ReturnType< + PermissionController< + PermissionSpecificationConstraint, + CaveatSpecificationConstraint + >['getPermissions'] + >; + getAccounts: (options?: { ignoreLock?: boolean }) => string[]; + }, +) { + const permissions = { ...getPermissionsForOrigin() }; + const caip25Endowment = permissions[Caip25EndowmentPermissionName]; + const caip25CaveatValue = caip25Endowment?.caveats?.find( + ({ type }) => type === Caip25CaveatType, + )?.value as Caip25CaveatValue | undefined; + delete permissions[Caip25EndowmentPermissionName]; + + if (caip25CaveatValue) { + // We cannot derive ethAccounts directly from the CAIP-25 permission + // because the accounts will not be in order of lastSelected + const ethAccounts = getAccounts({ ignoreLock: true }); + + if (ethAccounts.length > 0) { + permissions[RestrictedMethods.eth_accounts] = { + ...caip25Endowment, + parentCapability: RestrictedMethods.eth_accounts, + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ethAccounts, + }, + ], + }; + } + + const ethChainIds = getPermittedEthChainIds(caip25CaveatValue); + + if (ethChainIds.length > 0) { + permissions[PermissionNames.permittedChains] = { + ...caip25Endowment, + parentCapability: PermissionNames.permittedChains, + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ethChainIds, + }, + ], + }; + } + } + + res.result = Object.values(permissions); + return end(); +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.test.ts new file mode 100644 index 000000000000..6ce5c9a7264c --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.test.ts @@ -0,0 +1,651 @@ +import { + invalidParams, + RequestedPermissions, +} from '@metamask/permission-controller'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; +import { Json, JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; +import { + CaveatTypes, + RestrictedMethods, +} from '../../../../../shared/constants/permissions'; +import { PermissionNames } from '../../../controllers/permissions'; +import { requestPermissionsHandler } from './wallet-requestPermissions'; + +const getBaseRequest = (overrides = {}) => ({ + jsonrpc: '2.0' as const, + id: 0, + method: 'wallet_requestPermissions', + networkClientId: 'mainnet', + origin: 'http://test.com', + params: [ + { + eth_accounts: {}, + }, + ], + ...overrides, +}); + +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const requestPermissionsForOrigin = jest.fn().mockResolvedValue({}); + const getAccounts = jest.fn().mockReturnValue([]); + const requestCaip25ApprovalForOrigin = jest.fn().mockResolvedValue({}); + const grantPermissionsForOrigin = jest.fn().mockReturnValue({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: {}, + }, + }, + ], + }, + }); + const response: PendingJsonRpcResponse = { + jsonrpc: '2.0' as const, + id: 0, + }; + const handler = (request: unknown) => + requestPermissionsHandler.implementation( + request as JsonRpcRequest<[RequestedPermissions]> & { origin: string }, + response, + next, + end, + { + getAccounts, + requestPermissionsForOrigin, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + }, + ); + + return { + response, + next, + end, + getAccounts, + requestPermissionsForOrigin, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + handler, + }; +}; + +describe('requestPermissionsHandler', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('returns an error if params is malformed', async () => { + const { handler, end } = createMockedHandler(); + + const malformedRequest = getBaseRequest({ params: [] }); + await handler(malformedRequest); + expect(end).toHaveBeenCalledWith( + invalidParams({ data: { request: malformedRequest } }), + ); + }); + + describe('only other permissions (non CAIP-25 equivalent) requested', () => { + it('it treats "endowment:caip25" as an other permission', async () => { + const { + handler, + requestPermissionsForOrigin, + requestCaip25ApprovalForOrigin, + } = createMockedHandler(); + + await handler( + getBaseRequest({ + params: [ + { + [Caip25EndowmentPermissionName]: {}, + }, + ], + }), + ); + + expect(requestPermissionsForOrigin).toHaveBeenCalledWith({ + [Caip25EndowmentPermissionName]: {}, + }); + expect(requestCaip25ApprovalForOrigin).not.toHaveBeenCalled(); + }); + + it('requests the permission for the other permissions', async () => { + const { handler, requestPermissionsForOrigin } = createMockedHandler(); + + await handler( + getBaseRequest({ + params: [ + { + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(requestPermissionsForOrigin).toHaveBeenCalledWith({ + otherPermissionA: {}, + otherPermissionB: {}, + }); + }); + + it('returns an error if requesting other permissions fails', async () => { + const { handler, requestPermissionsForOrigin, end } = + createMockedHandler(); + + requestPermissionsForOrigin.mockRejectedValue( + new Error('failed to request other permissions'), + ); + + await handler( + getBaseRequest({ + params: [ + { + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(end).toHaveBeenCalledWith( + new Error('failed to request other permissions'), + ); + }); + + it('returns the other permissions that are granted', async () => { + const { handler, requestPermissionsForOrigin, response } = + createMockedHandler(); + + requestPermissionsForOrigin.mockResolvedValue([ + { + otherPermissionA: { foo: 'bar' }, + otherPermissionB: { hello: true }, + }, + ]); + + await handler( + getBaseRequest({ + params: [ + { + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(response.result).toStrictEqual([{ foo: 'bar' }, { hello: true }]); + }); + }); + + describe('only CAIP-25 equivalent permissions ("eth_accounts" and/or "endowment:permittedChains") requested', () => { + it('requests the CAIP-25 permission using eth_accounts when only eth_accounts is specified in params', async () => { + const { handler, requestCaip25ApprovalForOrigin } = createMockedHandler(); + + await handler( + getBaseRequest({ + params: [ + { + [RestrictedMethods.eth_accounts]: { + foo: 'bar', + }, + }, + ], + }), + ); + + expect(requestCaip25ApprovalForOrigin).toHaveBeenCalledWith({ + [RestrictedMethods.eth_accounts]: { + foo: 'bar', + }, + }); + }); + + it('requests the CAIP-25 permission for permittedChains when only permittedChains is specified in params', async () => { + const { handler, requestCaip25ApprovalForOrigin } = createMockedHandler(); + + await handler( + getBaseRequest({ + params: [ + { + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }, + ], + }), + ); + + expect(requestCaip25ApprovalForOrigin).toHaveBeenCalledWith({ + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }); + }); + + it('requests the CAIP-25 permission for eth_accounts and permittedChains when both are specified in params', async () => { + const { handler, requestCaip25ApprovalForOrigin } = createMockedHandler(); + + await handler( + getBaseRequest({ + params: [ + { + [RestrictedMethods.eth_accounts]: { + foo: 'bar', + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }, + ], + }), + ); + + expect(requestCaip25ApprovalForOrigin).toHaveBeenCalledWith({ + [RestrictedMethods.eth_accounts]: { + foo: 'bar', + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }); + }); + + it('returns an error if requesting the CAIP-25 approval fails', async () => { + const { handler, requestCaip25ApprovalForOrigin, end } = + createMockedHandler(); + requestCaip25ApprovalForOrigin.mockRejectedValue( + new Error('failed to request caip25 approval'), + ); + + await handler( + getBaseRequest({ + params: [ + { + [RestrictedMethods.eth_accounts]: { + foo: 'bar', + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }, + ], + }), + ); + + expect(end).toHaveBeenCalledWith( + new Error('failed to request caip25 approval'), + ); + }); + + it('grants the CAIP-25 approval', async () => { + const { + handler, + getAccounts, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + } = createMockedHandler(); + getAccounts.mockReturnValue(['0xdeadbeef']); + requestCaip25ApprovalForOrigin.mockResolvedValue({ + foo: 'bar', + }); + + await handler(getBaseRequest()); + expect(grantPermissionsForOrigin).toHaveBeenCalledWith({ foo: 'bar' }); + }); + + it('returns both eth_accounts and permittedChains permissions that were granted if there are permitted chains', async () => { + const { handler, getAccounts, grantPermissionsForOrigin, response } = + createMockedHandler(); + getAccounts.mockReturnValue(['0xdeadbeef']); + grantPermissionsForOrigin.mockReturnValue({ + [Caip25EndowmentPermissionName]: { + id: 'new', + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['0xdeadbeef'], + }, + 'eip155:5': { + accounts: ['0xdeadbeef'], + }, + }, + }, + }, + ], + }, + }); + + await handler(getBaseRequest()); + expect(response.result).toStrictEqual([ + { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0xdeadbeef'], + }, + ], + id: 'new', + parentCapability: RestrictedMethods.eth_accounts, + }, + { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x1', '0x5'], + }, + ], + id: 'new', + parentCapability: PermissionNames.permittedChains, + }, + ]); + }); + + it('returns only eth_accounts permission that was granted if there are no permitted chains', async () => { + const { handler, getAccounts, grantPermissionsForOrigin, response } = + createMockedHandler(); + getAccounts.mockReturnValue(['0xdeadbeef']); + grantPermissionsForOrigin.mockReturnValue({ + [Caip25EndowmentPermissionName]: { + id: 'new', + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['0xdeadbeef'], + }, + }, + }, + }, + ], + }, + }); + + await handler(getBaseRequest()); + expect(response.result).toStrictEqual([ + { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0xdeadbeef'], + }, + ], + id: 'new', + parentCapability: RestrictedMethods.eth_accounts, + }, + ]); + }); + }); + + describe('both CAIP-25 equivalent and other permissions requested', () => { + describe('both CAIP-25 equivalent permissions and other permissions are approved', () => { + it('returns eth_accounts, permittedChains, and other permissions that were granted', async () => { + const { + handler, + getAccounts, + requestPermissionsForOrigin, + grantPermissionsForOrigin, + response, + } = createMockedHandler(); + requestPermissionsForOrigin.mockResolvedValue([ + { + otherPermissionA: { foo: 'bar' }, + otherPermissionB: { hello: true }, + }, + ]); + getAccounts.mockReturnValue(['0xdeadbeef']); + grantPermissionsForOrigin.mockReturnValue({ + [Caip25EndowmentPermissionName]: { + id: 'new', + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['0xdeadbeef'], + }, + 'eip155:5': { + accounts: ['0xdeadbeef'], + }, + }, + }, + }, + ], + }, + }); + + await handler( + getBaseRequest({ + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + expect(response.result).toStrictEqual([ + { foo: 'bar' }, + { hello: true }, + { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0xdeadbeef'], + }, + ], + id: 'new', + parentCapability: RestrictedMethods.eth_accounts, + }, + { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x1', '0x5'], + }, + ], + id: 'new', + parentCapability: PermissionNames.permittedChains, + }, + ]); + }); + }); + + describe('CAIP-25 equivalent permissions are approved, but other permissions are not approved', () => { + it('does not grant the CAIP-25 permission', async () => { + const { + handler, + requestPermissionsForOrigin, + grantPermissionsForOrigin, + } = createMockedHandler(); + requestPermissionsForOrigin.mockRejectedValue( + new Error('other permissions rejected'), + ); + + await handler( + getBaseRequest({ + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(grantPermissionsForOrigin).not.toHaveBeenCalled(); + }); + + it('returns an error that the other permissions were not approved', async () => { + const { handler, requestPermissionsForOrigin, end } = + createMockedHandler(); + requestPermissionsForOrigin.mockRejectedValue( + new Error('other permissions rejected'), + ); + + await handler( + getBaseRequest({ + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(end).toHaveBeenCalledWith( + new Error('other permissions rejected'), + ); + }); + }); + + describe('CAIP-25 equivalent permissions are not approved', () => { + it('does not grant the CAIP-25 permission', async () => { + const { + handler, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + } = createMockedHandler(); + requestCaip25ApprovalForOrigin.mockRejectedValue( + new Error('caip25 approval rejected'), + ); + + await handler( + getBaseRequest({ + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(grantPermissionsForOrigin).not.toHaveBeenCalled(); + }); + + it('does not request approval for the other permissions', async () => { + const { + handler, + requestCaip25ApprovalForOrigin, + requestPermissionsForOrigin, + } = createMockedHandler(); + requestCaip25ApprovalForOrigin.mockRejectedValue( + new Error('caip25 approval rejected'), + ); + + await handler( + getBaseRequest({ + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(requestPermissionsForOrigin).not.toHaveBeenCalled(); + }); + + it('returns an error that the CAIP-25 permissions were not approved', async () => { + const { handler, requestCaip25ApprovalForOrigin, end } = + createMockedHandler(); + requestCaip25ApprovalForOrigin.mockRejectedValue( + new Error('caip25 approval rejected'), + ); + + await handler( + getBaseRequest({ + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + otherPermissionA: {}, + otherPermissionB: {}, + }, + ], + }), + ); + + expect(end).toHaveBeenCalledWith(new Error('caip25 approval rejected')); + }); + }); + }); + + describe('no permissions requested', () => { + it('returns an error by requesting empty permissions in params from the PermissionController if no permissions specified', async () => { + const { handler, requestPermissionsForOrigin, end } = + createMockedHandler(); + requestPermissionsForOrigin.mockRejectedValue( + new Error('failed to request unexpected permission'), + ); + + await handler( + getBaseRequest({ + params: [{}], + }), + ); + expect(requestPermissionsForOrigin).toHaveBeenCalledWith({}); + expect(end).toHaveBeenCalledWith( + new Error('failed to request unexpected permission'), + ); + }); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.ts new file mode 100644 index 000000000000..ab0e1870c972 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-requestPermissions.ts @@ -0,0 +1,187 @@ +import { pick } from 'lodash'; +import { isPlainObject } from '@metamask/controller-utils'; +import { + Caveat, + CaveatSpecificationConstraint, + invalidParams, + MethodNames, + PermissionController, + PermissionSpecificationConstraint, + RequestedPermissions, + ValidPermission, +} from '@metamask/permission-controller'; +import { + Caip25CaveatType, + Caip25CaveatValue, + Caip25EndowmentPermissionName, + getPermittedEthChainIds, +} from '@metamask/multichain'; +import { Json, JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; +import { + AsyncJsonRpcEngineNextCallback, + JsonRpcEngineEndCallback, +} from '@metamask/json-rpc-engine'; +import { + CaveatTypes, + RestrictedMethods, +} from '../../../../../shared/constants/permissions'; +import { PermissionNames } from '../../../controllers/permissions'; + +export const requestPermissionsHandler = { + methodNames: [MethodNames.RequestPermissions], + implementation: requestPermissionsImplementation, + hookNames: { + getAccounts: true, + requestPermissionsForOrigin: true, + requestCaip25ApprovalForOrigin: true, + grantPermissionsForOrigin: true, + }, +}; + +type AbstractPermissionController = PermissionController< + PermissionSpecificationConstraint, + CaveatSpecificationConstraint +>; + +type GrantedPermissions = Awaited< + ReturnType +>[0]; + +/** + * Request Permissions implementation to be used in JsonRpcEngine middleware. + * + * @param req - The JsonRpcEngine request + * @param res - The JsonRpcEngine result object + * @param _next - JsonRpcEngine next() callback - unused + * @param end - JsonRpcEngine end() callback + * @param options - Method hooks passed to the method implementation + * @param options.getAccounts - A hook that returns the permitted eth accounts for the origin sorted by lastSelected. + * @param options.requestCaip25ApprovalForOrigin - A hook that requests approval for the CAIP-25 permission for the origin. + * @param options.grantPermissionsForOrigin - A hook that grants permission for the approved permissions for the origin. + * @param options.requestPermissionsForOrigin - A hook that requests permissions for the origin. + * @returns A promise that resolves to nothing + */ +async function requestPermissionsImplementation( + req: JsonRpcRequest<[RequestedPermissions]> & { origin: string }, + res: PendingJsonRpcResponse, + _next: AsyncJsonRpcEngineNextCallback, + end: JsonRpcEngineEndCallback, + { + getAccounts, + requestPermissionsForOrigin, + requestCaip25ApprovalForOrigin, + grantPermissionsForOrigin, + }: { + getAccounts: () => string[]; + requestPermissionsForOrigin: ( + requestedPermissions: RequestedPermissions, + ) => Promise<[GrantedPermissions]>; + requestCaip25ApprovalForOrigin: ( + requestedPermissions?: RequestedPermissions, + ) => Promise; + grantPermissionsForOrigin: (approvedPermissions: RequestedPermissions) => { + [Caip25EndowmentPermissionName]: ValidPermission< + typeof Caip25EndowmentPermissionName, + Caveat + >; + }; + }, +) { + const { params } = req; + + if (!Array.isArray(params) || !isPlainObject(params[0])) { + return end(invalidParams({ data: { request: req } })); + } + + const [requestedPermissions] = params; + const caip25EquivalentPermissions: Partial< + Pick + > = pick(requestedPermissions, [ + RestrictedMethods.eth_accounts, + PermissionNames.permittedChains, + ]); + delete requestedPermissions[RestrictedMethods.eth_accounts]; + delete requestedPermissions[PermissionNames.permittedChains]; + + const hasCaip25EquivalentPermissions = + Object.keys(caip25EquivalentPermissions).length > 0; + const hasOtherRequestedPermissions = + Object.keys(requestedPermissions).length > 0; + + let grantedPermissions: GrantedPermissions = {}; + + let caip25Approval; + if (hasCaip25EquivalentPermissions) { + try { + caip25Approval = await requestCaip25ApprovalForOrigin( + caip25EquivalentPermissions, + ); + } catch (error) { + return end(error as unknown as Error); + } + } + + if (hasOtherRequestedPermissions || !hasCaip25EquivalentPermissions) { + try { + const [frozenGrantedPermissions] = await requestPermissionsForOrigin( + requestedPermissions, + ); + grantedPermissions = { ...frozenGrantedPermissions }; + } catch (error) { + return end(error as unknown as Error); + } + } + + if (caip25Approval) { + const grantedCaip25Permissions = grantPermissionsForOrigin(caip25Approval); + const caip25Endowment = + grantedCaip25Permissions[Caip25EndowmentPermissionName]; + + const caip25CaveatValue = caip25Endowment?.caveats?.find( + ({ type }) => type === Caip25CaveatType, + )?.value as Caip25CaveatValue | undefined; + + if (!caip25CaveatValue) { + throw new Error( + `could not find ${Caip25CaveatType} in granted ${Caip25EndowmentPermissionName} permission.`, + ); + } + + // We cannot derive correct eth_accounts value directly from the CAIP-25 permission + // because the accounts will not be in order of lastSelected + const ethAccounts = getAccounts(); + + grantedPermissions[RestrictedMethods.eth_accounts] = { + ...caip25Endowment, + parentCapability: RestrictedMethods.eth_accounts, + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ethAccounts, + }, + ], + }; + + const ethChainIds = getPermittedEthChainIds(caip25CaveatValue); + if (ethChainIds.length > 0) { + grantedPermissions[PermissionNames.permittedChains] = { + ...caip25Endowment, + parentCapability: PermissionNames.permittedChains, + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ethChainIds, + }, + ], + }; + } + } + + res.result = Object.values(grantedPermissions).filter( + ( + permission: ValidPermission> | undefined, + ): permission is ValidPermission> => + permission !== undefined, + ); + return end(); +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.test.ts new file mode 100644 index 000000000000..380db50222de --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.test.ts @@ -0,0 +1,150 @@ +import { invalidParams } from '@metamask/permission-controller'; +import { Caip25EndowmentPermissionName } from '@metamask/multichain'; +import { Json, JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; +import { PermissionNames } from '../../../controllers/permissions'; +import { RestrictedMethods } from '../../../../../shared/constants/permissions'; +import { revokePermissionsHandler } from './wallet-revokePermissions'; + +const baseRequest = { + jsonrpc: '2.0' as const, + id: 0, + method: 'wallet_revokePermissions', + params: [ + { + [Caip25EndowmentPermissionName]: {}, + otherPermission: {}, + }, + ], +}; + +const createMockedHandler = () => { + const next = jest.fn(); + const end = jest.fn(); + const revokePermissionsForOrigin = jest.fn(); + + const response: PendingJsonRpcResponse = { + jsonrpc: '2.0' as const, + id: 0, + }; + const handler = (request: JsonRpcRequest) => + revokePermissionsHandler.implementation(request, response, next, end, { + revokePermissionsForOrigin, + }); + + return { + response, + next, + end, + revokePermissionsForOrigin, + handler, + }; +}; + +describe('revokePermissionsHandler', () => { + it('returns an error if params is malformed', () => { + const { handler, end } = createMockedHandler(); + + const malformedRequest = { + ...baseRequest, + params: [], + }; + handler(malformedRequest); + expect(end).toHaveBeenCalledWith( + invalidParams({ data: { request: malformedRequest } }), + ); + }); + + it('returns an error if params are empty', () => { + const { handler, end } = createMockedHandler(); + + const emptyRequest = { + ...baseRequest, + params: [{}], + }; + handler(emptyRequest); + expect(end).toHaveBeenCalledWith( + invalidParams({ data: { request: emptyRequest } }), + ); + }); + + it('returns an error if params only contains the CAIP-25 permission', () => { + const { handler, end } = createMockedHandler(); + + const emptyRequest = { + ...baseRequest, + params: [ + { + [Caip25EndowmentPermissionName]: {}, + }, + ], + }; + handler(emptyRequest); + expect(end).toHaveBeenCalledWith( + invalidParams({ data: { request: emptyRequest } }), + ); + }); + + // @ts-expect-error This is missing from the Mocha type definitions + describe.each([ + [RestrictedMethods.eth_accounts], + [PermissionNames.permittedChains], + ])('%s permission is specified', (permission: string) => { + it('revokes the CAIP-25 endowment permission', () => { + const { handler, revokePermissionsForOrigin } = createMockedHandler(); + + handler({ + ...baseRequest, + params: [ + { + [permission]: {}, + }, + ], + }); + expect(revokePermissionsForOrigin).toHaveBeenCalledWith([ + Caip25EndowmentPermissionName, + ]); + }); + + it('revokes other permissions specified', () => { + const { handler, revokePermissionsForOrigin } = createMockedHandler(); + + handler({ + ...baseRequest, + params: [ + { + [permission]: {}, + otherPermission: {}, + }, + ], + }); + expect(revokePermissionsForOrigin).toHaveBeenCalledWith([ + 'otherPermission', + Caip25EndowmentPermissionName, + ]); + }); + }); + + it('revokes permissions other than eth_accounts, permittedChains, CAIP-25 if specified', () => { + const { handler, revokePermissionsForOrigin } = createMockedHandler(); + + handler({ + ...baseRequest, + params: [ + { + [Caip25EndowmentPermissionName]: {}, + otherPermission: {}, + }, + ], + }); + expect(revokePermissionsForOrigin).toHaveBeenCalledWith([ + 'otherPermission', + ]); + }); + + it('returns null', () => { + const { handler, response } = createMockedHandler(); + + handler(baseRequest); + expect(response.result).toStrictEqual(null); + }); +}); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.ts b/app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.ts new file mode 100644 index 000000000000..0d30087ac24e --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/wallet-revokePermissions.ts @@ -0,0 +1,85 @@ +import { invalidParams, MethodNames } from '@metamask/permission-controller'; +import { + isNonEmptyArray, + Json, + JsonRpcRequest, + PendingJsonRpcResponse, +} from '@metamask/utils'; +import { Caip25EndowmentPermissionName } from '@metamask/multichain'; +import { + AsyncJsonRpcEngineNextCallback, + JsonRpcEngineEndCallback, +} from '@metamask/json-rpc-engine'; +import { RestrictedMethods } from '../../../../../shared/constants/permissions'; +import { PermissionNames } from '../../../controllers/permissions'; + +export const revokePermissionsHandler = { + methodNames: [MethodNames.RevokePermissions], + implementation: revokePermissionsImplementation, + hookNames: { + revokePermissionsForOrigin: true, + updateCaveat: true, + }, +}; + +/** + * Revoke Permissions implementation to be used in JsonRpcEngine middleware. + * + * @param req - The JsonRpcEngine request + * @param res - The JsonRpcEngine result object + * @param _next - JsonRpcEngine next() callback - unused + * @param end - JsonRpcEngine end() callback + * @param options - Method hooks passed to the method implementation + * @param options.revokePermissionsForOrigin - A hook that revokes given permission keys for an origin + * @returns A promise that resolves to nothing + */ +function revokePermissionsImplementation( + req: JsonRpcRequest, + res: PendingJsonRpcResponse, + _next: AsyncJsonRpcEngineNextCallback, + end: JsonRpcEngineEndCallback, + { + revokePermissionsForOrigin, + }: { + revokePermissionsForOrigin: (permissionKeys: string[]) => void; + }, +) { + const { params } = req; + + const param = params?.[0]; + + if (!param) { + return end(invalidParams({ data: { request: req } })); + } + + // For now, this API revokes the entire permission key + // even if caveats are specified. + const permissionKeys = Object.keys(param).filter( + (name) => name !== Caip25EndowmentPermissionName, + ); + + if (!isNonEmptyArray(permissionKeys)) { + return end(invalidParams({ data: { request: req } })); + } + + const caip25EquivalentPermissions: string[] = [ + RestrictedMethods.eth_accounts, + PermissionNames.permittedChains, + ]; + const relevantPermissionKeys = permissionKeys.filter( + (name: string) => !caip25EquivalentPermissions.includes(name), + ); + + const shouldRevokeLegacyPermission = + relevantPermissionKeys.length !== permissionKeys.length; + + if (shouldRevokeLegacyPermission) { + relevantPermissionKeys.push(Caip25EndowmentPermissionName); + } + + revokePermissionsForOrigin(relevantPermissionKeys); + + res.result = null; + + return end(); +} diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index e268d25b2810..76d66eba88d2 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -86,7 +86,12 @@ function getClientOptions() { integrations: [ Sentry.dedupeIntegration(), Sentry.extraErrorDataIntegration(), - Sentry.browserTracingIntegration(), + Sentry.browserTracingIntegration({ + shouldCreateSpanForRequest: (url) => { + // Do not create spans for outgoing requests to a 'sentry.io' domain. + return !url.match(/^https?:\/\/([\w\d.@-]+\.)?sentry\.io(\/|$)/u); + }, + }), filterEvents({ getMetaMetricsEnabled, log }), ], release: RELEASE, @@ -122,9 +127,9 @@ function getTracesSampleRate(sentryTarget) { } if (flags.circleci) { - // Report very frequently on develop branch, and never on other branches + // Report very frequently on main branch, and never on other branches // (Unless you use a `flags = {"sentry": {"tracesSampleRate": x.xx}}` override) - if (flags.circleci.branch === 'develop') { + if (flags.circleci.branch === 'main') { return 0.015; } return 0; diff --git a/app/scripts/lib/snap-keyring/snap-keyring.test.ts b/app/scripts/lib/snap-keyring/snap-keyring.test.ts index 4136fd1fd1fc..c2ea66cff4fe 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.test.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.test.ts @@ -1,5 +1,6 @@ import { ControllerMessenger } from '@metamask/base-controller'; -import { EthAccountType, InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; import { MetaMetricsEventCategory, @@ -39,6 +40,7 @@ const mockAccount = { id: '3afa663e-0600-4d93-868a-61c2e553013b', address, methods: [], + scopes: ['eip155'], options: {}, }; const mockInternalAccount = { diff --git a/app/scripts/lib/transaction/decode/proxy.test.ts b/app/scripts/lib/transaction/decode/proxy.test.ts index d137fe567c73..b98839d16743 100644 --- a/app/scripts/lib/transaction/decode/proxy.test.ts +++ b/app/scripts/lib/transaction/decode/proxy.test.ts @@ -1,44 +1,46 @@ -import EthQuery from '@metamask/eth-query'; +import type { Provider } from '@metamask/network-controller'; import { getContractProxyAddress } from './proxy'; const CONTRACT_ADDRESS_MOCK = '0x456'; -function createEthQueryMock(storageValues: string[]): EthQuery { - const ethQuery = { - eth_getStorageAt: jest.fn(), - }; +function createProviderMock(storageValues: string[]): Provider { + const ethGetStorageAt = jest.fn(); for (const storageValue of storageValues) { - ethQuery.eth_getStorageAt.mockImplementationOnce( - (_contractAddress, _storageSlot, _blockNumber, cb) => - cb(null, storageValue), - ); + ethGetStorageAt.mockImplementationOnce(() => storageValue); } - return ethQuery as unknown as EthQuery; + return { + request: async (request) => { + if (request.method === 'eth_getStorageAt') { + return ethGetStorageAt(request); + } + throw new Error(`Unexpected method: ${request.method}`); + }, + } as Provider; } describe('Proxy', () => { describe('getContractProxyAddress', () => { it('returns undefined if all responses empty', async () => { - const ethQuery = createEthQueryMock([ + const provider = createProviderMock([ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ]); expect( - await getContractProxyAddress(CONTRACT_ADDRESS_MOCK, ethQuery), + await getContractProxyAddress(CONTRACT_ADDRESS_MOCK, provider), ).toBeUndefined(); }); it('returns first non-empty response', async () => { - const ethQuery = createEthQueryMock([ + const provider = createProviderMock([ '0x0000000000000000000000000000000000000000000000000000000000000000', '00000000000000000000000000123', ]); expect( - await getContractProxyAddress(CONTRACT_ADDRESS_MOCK, ethQuery), + await getContractProxyAddress(CONTRACT_ADDRESS_MOCK, provider), ).toBe('0x123'); }); }); diff --git a/app/scripts/lib/transaction/decode/proxy.ts b/app/scripts/lib/transaction/decode/proxy.ts index 19d5887acd13..3f0367abc648 100644 --- a/app/scripts/lib/transaction/decode/proxy.ts +++ b/app/scripts/lib/transaction/decode/proxy.ts @@ -1,6 +1,5 @@ -import { query } from '@metamask/controller-utils'; -import EthQuery from '@metamask/eth-query'; -import { Hex } from '@metamask/utils'; +import type { Provider } from '@metamask/network-controller'; +import type { Hex, JsonRpcParams } from '@metamask/utils'; import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util'; const IMPLEMENTATION_STORAGE_SLOTS = [ @@ -15,16 +14,14 @@ const EMPTY_RESULT = '0'.padEnd(64, '0'); export async function getContractProxyAddress( contractAddress: Hex, - ethQuery: EthQuery, + provider: Provider, ): Promise { const responses = await Promise.all( - IMPLEMENTATION_STORAGE_SLOTS.map( - (storageSlot) => - query(ethQuery, 'eth_getStorageAt', [ - contractAddress, - storageSlot, - 'latest', - ]) as Promise, + IMPLEMENTATION_STORAGE_SLOTS.map((storageSlot) => + provider.request({ + method: 'eth_getStorageAt', + params: [contractAddress, storageSlot, 'latest'], + }), ), ); diff --git a/app/scripts/lib/transaction/decode/util.test.ts b/app/scripts/lib/transaction/decode/util.test.ts index 66984b29cfe4..d114b5cc4fb7 100644 --- a/app/scripts/lib/transaction/decode/util.test.ts +++ b/app/scripts/lib/transaction/decode/util.test.ts @@ -1,4 +1,4 @@ -import EthQuery from '@metamask/eth-query'; +import type { Provider } from '@metamask/network-controller'; import { TRANSACTION_DATA_FOUR_BYTE, TRANSACTION_DATA_SOURCIFY, @@ -20,7 +20,7 @@ jest.mock('./proxy'); const CONTRACT_ADDRESS_MOCK = '0x456'; const CHAIN_ID_MOCK = '0x123'; -const ETH_QUERY_MOCK = {} as EthQuery; +const PROVIDER_MOCK = {} as Provider; describe('Transaction Decode Utils', () => { const decodeUniswapRouterTransactionDataMock = jest.mocked( @@ -56,7 +56,7 @@ describe('Transaction Decode Utils', () => { transactionData: TRANSACTION_DATA_UNISWAP, contractAddress: CONTRACT_ADDRESS_MOCK, chainId: CHAIN_ID_MOCK, - ethQuery: ETH_QUERY_MOCK, + provider: PROVIDER_MOCK, }); expect(result).toStrictEqual(TRANSACTION_DECODE_UNISWAP); @@ -71,7 +71,7 @@ describe('Transaction Decode Utils', () => { transactionData: TRANSACTION_DATA_SOURCIFY, contractAddress: CONTRACT_ADDRESS_MOCK, chainId: CHAIN_ID_MOCK, - ethQuery: ETH_QUERY_MOCK, + provider: PROVIDER_MOCK, }); expect(result).toStrictEqual(TRANSACTION_DECODE_SOURCIFY); @@ -86,7 +86,7 @@ describe('Transaction Decode Utils', () => { transactionData: TRANSACTION_DATA_FOUR_BYTE, contractAddress: CONTRACT_ADDRESS_MOCK, chainId: CHAIN_ID_MOCK, - ethQuery: ETH_QUERY_MOCK, + provider: PROVIDER_MOCK, }); expect(result).toStrictEqual(TRANSACTION_DECODE_FOUR_BYTE); @@ -97,7 +97,7 @@ describe('Transaction Decode Utils', () => { transactionData: TRANSACTION_DATA_FOUR_BYTE, contractAddress: CONTRACT_ADDRESS_MOCK, chainId: CHAIN_ID_MOCK, - ethQuery: ETH_QUERY_MOCK, + provider: PROVIDER_MOCK, }); expect(result).toBeUndefined(); diff --git a/app/scripts/lib/transaction/decode/util.ts b/app/scripts/lib/transaction/decode/util.ts index d8bca103ff73..cb5aca019516 100644 --- a/app/scripts/lib/transaction/decode/util.ts +++ b/app/scripts/lib/transaction/decode/util.ts @@ -1,5 +1,5 @@ import { Hex, createProjectLogger } from '@metamask/utils'; -import EthQuery from '@metamask/eth-query'; +import type { Provider } from '@metamask/network-controller'; import { DecodedTransactionDataMethod, DecodedTransactionDataParam, @@ -17,12 +17,12 @@ export async function decodeTransactionData({ transactionData, contractAddress, chainId, - ethQuery, + provider, }: { transactionData: Hex; contractAddress: Hex; chainId: Hex; - ethQuery: EthQuery; + provider: Provider; }): Promise { log('Decoding transaction data', { transactionData, @@ -45,7 +45,7 @@ export async function decodeTransactionData({ }; } - const proxyAddress = await getContractProxyAddress(contractAddress, ethQuery); + const proxyAddress = await getContractProxyAddress(contractAddress, provider); if (proxyAddress) { log('Retrieved proxy implementation address', proxyAddress); diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index 7dcedd4e467e..ad28d22967f3 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -18,12 +18,14 @@ import { MetaMetricsTransactionEventSource, MetaMetricsEventCategory, MetaMetricsEventUiCustomization, + MetaMetricsEventTransactionEstimateType, } from '../../../../shared/constants/metametrics'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import { BlockaidReason, BlockaidResultType, } from '../../../../shared/constants/security-provider'; +import { decimalToHex } from '../../../../shared/modules/conversion.utils'; import { handleTransactionAdded, handleTransactionApproved, @@ -73,7 +75,6 @@ const mockTransactionMetricsRequest = { trackEvent: jest.fn(), getIsSmartTransaction: jest.fn(), getSmartTransactionByMinedTxHash: jest.fn(), - getRedesignedTransactionsEnabled: jest.fn(), getMethodData: jest.fn(), getIsRedesignedConfirmationsDeveloperEnabled: jest.fn(), getIsConfirmationAdvancedDetailsOpen: jest.fn(), @@ -115,6 +116,7 @@ describe('Transaction metrics', () => { type: TransactionType.simpleSend, origin: ORIGIN_METAMASK, chainId: mockChainId, + networkClientId: 'testNetworkClientId', time: 1624408066355, defaultGasEstimates: { gas: '0x7b0d', @@ -160,9 +162,11 @@ describe('Transaction metrics', () => { ui_customizations: ['redesigned_confirmation'], transaction_advanced_view: undefined, transaction_contract_method: undefined, + transaction_internal_id: '1', }; expectedSensitiveProperties = { + default_estimate: MetaMetricsEventTransactionEstimateType.DefaultEstimate, default_gas: '0.000031501', default_gas_price: '2', first_seen: 1624408066355, @@ -824,6 +828,67 @@ describe('Transaction metrics', () => { mockTransactionMetricsRequest.finalizeEventFragment, ).toHaveBeenCalledWith(expectedUniqueId); }); + + it('should create, update, finalize event fragment with completion_time_onchain', async () => { + mockTransactionMeta.txReceipt = { + gasUsed: '0x123', + status: '0x0', + }; + mockTransactionMeta.blockTimestamp = decimalToHex(124); + mockTransactionMeta.submittedTime = 123123; + + await handleTransactionConfirmed(mockTransactionMetricsRequest, { + ...mockTransactionMeta, + actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + completion_time: expect.any(String), + completion_time_onchain: '0.88', + gas_used: '0.000000291', + status: METRICS_STATUS_FAILED, + }, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: { + ...expectedSensitiveProperties, + completion_time: expect.any(String), + completion_time_onchain: '0.88', + gas_used: '0.000000291', + status: METRICS_STATUS_FAILED, + }, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); }); describe('handleTransactionDropped', () => { diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 375a8bd4a8ab..ddea334562ba 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -1,4 +1,4 @@ -import EthQuery, { Provider } from '@metamask/eth-query'; +import type { Provider } from '@metamask/network-controller'; import { FetchGasFeeEstimateOptions } from '@metamask/gas-fee-controller'; import { BigNumber } from 'bignumber.js'; import { isHexString } from 'ethereumjs-util'; @@ -9,12 +9,16 @@ import { TransactionType, } from '@metamask/transaction-controller'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; -import { GasRecommendations } from '../../../../shared/constants/gas'; +import { + GasRecommendations, + PriorityLevels, +} from '../../../../shared/constants/gas'; import { MetaMetricsEventCategory, MetaMetricsEventFragment, MetaMetricsEventName, MetaMetricsEventUiCustomization, + MetaMetricsEventTransactionEstimateType, MetaMetricsPageObject, MetaMetricsReferrerObject, } from '../../../../shared/constants/metametrics'; @@ -29,6 +33,7 @@ import { TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; import { + hexToDecimal, hexWEIToDecETH, hexWEIToDecGWEI, } from '../../../../shared/modules/conversion.utils'; @@ -97,7 +102,6 @@ export type TransactionMetricsRequest = { getSmartTransactionByMinedTxHash: ( txhash: string | undefined, ) => SmartTransaction; - getRedesignedTransactionsEnabled: () => boolean; getMethodData: (data: string) => Promise<{ name: string }>; getIsRedesignedConfirmationsDeveloperEnabled: () => boolean; getIsConfirmationAdvancedDetailsOpen: () => boolean; @@ -224,13 +228,22 @@ export const handleTransactionConfirmed = async ( const { txReceipt } = transactionMeta; extraParams.gas_used = txReceipt?.gasUsed; + extraParams.block_number = + txReceipt?.blockNumber && hexToDecimal(txReceipt.blockNumber); - const { submittedTime } = transactionMeta; + const { submittedTime, blockTimestamp } = transactionMeta; if (submittedTime) { extraParams.completion_time = getTransactionCompletionTime(submittedTime); } + if (submittedTime && blockTimestamp) { + extraParams.completion_time_onchain = getTransactionOnchainCompletionTime( + submittedTime, + blockTimestamp, + ); + } + if (txReceipt?.status === '0x0') { extraParams.status = METRICS_STATUS_FAILED; } @@ -792,13 +805,17 @@ async function buildEventFragmentProperties({ finalApprovalAmount, securityProviderResponse, simulationFails, + id, + userFeeLevel, } = transactionMeta; - const query = new EthQuery(transactionMetricsRequest.provider); const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp'; + const gasFeeSelected = + userFeeLevel === 'dappSuggested' ? 'dapp_proposed' : userFeeLevel; + const { assetType, tokenStandard } = await determineTransactionAssetType( transactionMeta, - query, + transactionMetricsRequest.provider, transactionMetricsRequest.getTokenStandardAndDetails, ); @@ -819,12 +836,18 @@ async function buildEventFragmentProperties({ gasParams.max_priority_fee_per_gas = maxPriorityFeePerGas; } else { gasParams.gas_price = gasPrice; + gasParams.default_estimate = + MetaMetricsEventTransactionEstimateType.DefaultEstimate; } if (defaultGasEstimates) { const { estimateType } = defaultGasEstimates; if (estimateType) { - gasParams.default_estimate = estimateType; + gasParams.default_estimate = + estimateType === PriorityLevels.dAppSuggested + ? MetaMetricsEventTransactionEstimateType.DappProposed + : estimateType; + let defaultMaxFeePerGas = transactionMeta.defaultGasEstimates?.maxFeePerGas; let defaultMaxPriorityFeePerGas = @@ -906,6 +929,7 @@ async function buildEventFragmentProperties({ let transactionApprovalAmountVsBalanceRatio; let transactionContractAddress; let transactionType = TransactionType.simpleSend; + let transactionContractMethod4Byte; if (type === TransactionType.swapAndSend) { transactionType = TransactionType.swapAndSend; } else if (type === TransactionType.cancel) { @@ -918,6 +942,10 @@ async function buildEventFragmentProperties({ transactionType = TransactionType.contractInteraction; transactionContractMethod = contractMethodName; transactionContractAddress = transactionMeta.txParams?.to; + transactionContractMethod4Byte = transactionMeta.txParams?.data?.slice( + 0, + 10, + ); if ( transactionContractMethod === contractMethodNames.APPROVE && tokenStandard === TokenStandard.ERC20 @@ -995,8 +1023,6 @@ async function buildEventFragmentProperties({ const isRedesignedForTransaction = shouldUseRedesignForTransactions({ transactionMetadataType: transactionMeta.type as TransactionType, - isRedesignedTransactionsUserSettingEnabled: - transactionMetricsRequest.getRedesignedTransactionsEnabled(), isRedesignedConfirmationsDeveloperEnabled: transactionMetricsRequest.getIsRedesignedConfirmationsDeveloperEnabled(), }); @@ -1038,6 +1064,8 @@ async function buildEventFragmentProperties({ token_standard: tokenStandard, transaction_type: transactionType, transaction_speed_up: type === TransactionType.retry, + transaction_internal_id: id, + gas_fee_selected: gasFeeSelected, ...blockaidProperties, // ui_customizations must come after ...blockaidProperties ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, @@ -1071,6 +1099,7 @@ async function buildEventFragmentProperties({ gas_limit: gasLimit, transaction_replaced: transactionReplaced, transaction_contract_address: transactionContractAddress, + transaction_contract_method_4byte: transactionContractMethod4Byte, ...extraParams, ...gasParamsInGwei, // TODO: Replace `any` with type @@ -1110,6 +1139,30 @@ function getTransactionCompletionTime(submittedTime: number) { return Math.round((Date.now() - submittedTime) / 1000).toString(); } +/** + * Returns number of seconds (rounded to the hundredths) between submitted time + * and the block timestamp. + * + * @param submittedTimeMs - The UNIX timestamp in milliseconds in which the + * transaction has been submitted + * @param blockTimestampHex - The UNIX timestamp in seconds in hexadecimal in which + * the transaction has been confirmed in a block + */ +function getTransactionOnchainCompletionTime( + submittedTimeMs: number, + blockTimestampHex: string, +): string { + const DECIMAL_DIGITS = 2; + + const blockTimestampSeconds = Number(hexToDecimal(blockTimestampHex)); + const completionTimeSeconds = blockTimestampSeconds - submittedTimeMs / 1000; + const completionTimeSecondsRounded = + Math.round(completionTimeSeconds * 10 ** DECIMAL_DIGITS) / + 10 ** DECIMAL_DIGITS; + + return completionTimeSecondsRounded.toString(); +} + /** * The allowance amount in relation to the dapp proposed amount for specific token * diff --git a/app/scripts/lib/transaction/smart-transactions-mocks.ts b/app/scripts/lib/transaction/smart-transactions-mocks.ts new file mode 100644 index 000000000000..4d819aa1b7c5 --- /dev/null +++ b/app/scripts/lib/transaction/smart-transactions-mocks.ts @@ -0,0 +1,35 @@ +/** + * Mock for waitForTransactionHash. Simply replace the waitForTransactionHash + * with this mock so that we can debug locally without spending gas on mainnet. + * + * @returns Promise + */ +export const mockWaitForTransactionHash: () => Promise = () => { + return new Promise((resolve) => { + setTimeout(() => { + // Need a real tx hash to pass some downstream validation + resolve( + '0xe3e223b9725765a7de557effdb2b507ace3534bcff2c1fe3a857e0791e56a518', + ); + }, 20_000_000); + }); +}; + +/** + * Mock for signAndSubmitTransactions. Simply replace the signAndSubmitTransactions + * with this mock so that we can debug locally without spending gas on mainnet. + * + * @returns Promise<{ uuid: string; txHash?: string }> + */ +export const mockSignAndSubmitTransactions: () => Promise<{ + uuid: string; + txHash?: string; +}> = () => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + uuid: 'uuid123456789', + }); + }, 100); + }); +}; diff --git a/app/scripts/lib/transaction/smart-transactions.test.ts b/app/scripts/lib/transaction/smart-transactions.test.ts index b3ff3aa8fa7c..25bb409dffa1 100644 --- a/app/scripts/lib/transaction/smart-transactions.test.ts +++ b/app/scripts/lib/transaction/smart-transactions.test.ts @@ -9,6 +9,7 @@ import SmartTransactionsController, { } from '@metamask/smart-transactions-controller'; import { NetworkControllerStateChangeEvent } from '@metamask/network-controller'; import type { SmartTransaction } from '@metamask/smart-transactions-controller/dist/types'; +import { ClientId } from '@metamask/smart-transactions-controller/dist/types'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { submitSmartTransactionHook } from './smart-transactions'; import type { @@ -107,13 +108,15 @@ function withRequest( }); const smartTransactionsController = new SmartTransactionsController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger, getNonceLock: jest.fn(), confirmExternalTransaction: jest.fn(), trackMetaMetricsEvent: jest.fn(), getTransactions: jest.fn(), getMetaMetricsProps: jest.fn(), + clientId: ClientId.Extension, + getFeatureFlags: jest.fn(), + updateTransaction: jest.fn(), }); jest.spyOn(smartTransactionsController, 'getFees').mockResolvedValue({ @@ -147,6 +150,7 @@ function withRequest( }, type: TransactionType.simpleSend, chainId: CHAIN_IDS.MAINNET, + networkClientId: 'testNetworkClientId', time: 1624408066355, defaultGasEstimates: { gas: '0x7b0d', @@ -169,7 +173,7 @@ function withRequest( smartTransactions: { expectedDeadline: 45, maxDeadline: 150, - returnTxHashAsap: false, + extensionReturnTxHashAsap: false, }, }, ...options, @@ -177,7 +181,6 @@ function withRequest( return fn({ request, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger, startFlowSpy, addRequestSpy, @@ -238,7 +241,7 @@ describe('submitSmartTransactionHook', () => { it('returns a txHash asap if the feature flag requires it', async () => { withRequest(async ({ request }) => { - request.featureFlags.smartTransactions.returnTxHashAsap = true; + request.featureFlags.smartTransactions.extensionReturnTxHashAsap = true; const result = await submitSmartTransactionHook(request); expect(result).toEqual({ transactionHash: txHash }); }); diff --git a/app/scripts/lib/transaction/smart-transactions.ts b/app/scripts/lib/transaction/smart-transactions.ts index 9f9d81567847..6f9acac1ce74 100644 --- a/app/scripts/lib/transaction/smart-transactions.ts +++ b/app/scripts/lib/transaction/smart-transactions.ts @@ -54,7 +54,7 @@ export type FeatureFlags = { smartTransactions: { expectedDeadline?: number; maxDeadline?: number; - returnTxHashAsap?: boolean; + extensionReturnTxHashAsap?: boolean; }; }; @@ -83,7 +83,7 @@ class SmartTransactionHook { smartTransactions: { expectedDeadline?: number; maxDeadline?: number; - returnTxHashAsap?: boolean; + extensionReturnTxHashAsap?: boolean; }; }; @@ -101,6 +101,8 @@ class SmartTransactionHook { #txParams: TransactionParams; + #shouldShowStatusPage: boolean; + constructor(request: SubmitSmartTransactionRequest) { const { transactionMeta, @@ -123,14 +125,18 @@ class SmartTransactionHook { this.#isDapp = transactionMeta.origin !== ORIGIN_METAMASK; this.#chainId = transactionMeta.chainId; this.#txParams = transactionMeta.txParams; + this.#shouldShowStatusPage = + transactionMeta.type !== TransactionType.bridge; } async submit() { const isUnsupportedTransactionTypeForSmartTransaction = this .#transactionMeta?.type - ? [TransactionType.swapAndSend, TransactionType.swapApproval].includes( - this.#transactionMeta.type, - ) + ? [ + TransactionType.swapAndSend, + TransactionType.swapApproval, + TransactionType.bridgeApproval, + ].includes(this.#transactionMeta.type) : false; // Will cause TransactionController to publish to the RPC provider as normal. @@ -141,10 +147,13 @@ class SmartTransactionHook { ) { return useRegularTransactionSubmit; } - const { id: approvalFlowId } = await this.#controllerMessenger.call( - 'ApprovalController:startFlow', - ); - this.#approvalFlowId = approvalFlowId; + + if (this.#shouldShowStatusPage) { + const { id: approvalFlowId } = await this.#controllerMessenger.call( + 'ApprovalController:startFlow', + ); + this.#approvalFlowId = approvalFlowId; + } let getFeesResponse; try { getFeesResponse = await this.#smartTransactionsController.getFees( @@ -167,16 +176,19 @@ class SmartTransactionHook { if (!uuid) { throw new Error('No smart transaction UUID'); } - const returnTxHashAsap = - this.#featureFlags?.smartTransactions?.returnTxHashAsap; - this.#addApprovalRequest({ - uuid, - }); - this.#addListenerToUpdateStatusPage({ - uuid, - }); + const extensionReturnTxHashAsap = + this.#featureFlags?.smartTransactions?.extensionReturnTxHashAsap; + + if (this.#shouldShowStatusPage) { + this.#addApprovalRequest({ + uuid, + }); + this.#addListenerToUpdateStatusPage({ + uuid, + }); + } let transactionHash: string | undefined | null; - if (returnTxHashAsap && submitTransactionResponse?.txHash) { + if (extensionReturnTxHashAsap && submitTransactionResponse?.txHash) { transactionHash = submitTransactionResponse.txHash; } else { transactionHash = await this.#waitForTransactionHash({ @@ -197,7 +209,7 @@ class SmartTransactionHook { } #onApproveOrReject() { - if (this.#approvalFlowEnded) { + if (!this.#shouldShowStatusPage || this.#approvalFlowEnded) { return; } this.#approvalFlowEnded = true; diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index fbbee025381b..0a941968d802 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -1,4 +1,4 @@ -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { TransactionParams } from '@metamask/eth-json-rpc-middleware'; import { TransactionController, @@ -50,6 +50,7 @@ const TRANSACTION_PARAMS_MOCK: TransactionParams = { const TRANSACTION_OPTIONS_MOCK: AddTransactionOptions = { actionId: 'mockActionId', + networkClientId: 'mockNetworkClientId', origin: 'mockOrigin', requireApproval: false, type: TransactionType.simpleSend, @@ -151,23 +152,6 @@ describe('Transaction Utils', () => { }); }); - it('adds transaction with networkClientId if process.env.TRANSACTION_MULTICHAIN is set', async () => { - process.env.TRANSACTION_MULTICHAIN = '1'; - - await addTransaction(request); - - expect( - request.transactionController.addTransaction, - ).toHaveBeenCalledTimes(1); - expect( - request.transactionController.addTransaction, - ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { - ...TRANSACTION_OPTIONS_MOCK, - networkClientId: 'mockNetworkClientId', - }); - process.env.TRANSACTION_MULTICHAIN = ''; - }); - it('returns transaction meta', async () => { const transactionMeta = await addTransaction(request); expect(transactionMeta).toStrictEqual(TRANSACTION_META_MOCK); @@ -541,27 +525,6 @@ describe('Transaction Utils', () => { }); }); - it('adds transaction with networkClientId if process.env.TRANSACTION_MULTICHAIN is set', async () => { - process.env.TRANSACTION_MULTICHAIN = '1'; - - await addDappTransaction(dappRequest); - - expect( - request.transactionController.addTransaction, - ).toHaveBeenCalledTimes(1); - expect( - request.transactionController.addTransaction, - ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { - ...TRANSACTION_OPTIONS_MOCK, - networkClientId: 'mockNetworkClientId', - method: DAPP_REQUEST_MOCK.method, - requireApproval: true, - securityAlertResponse: DAPP_REQUEST_MOCK.securityAlertResponse, - type: undefined, - }); - process.env.TRANSACTION_MULTICHAIN = ''; - }); - it('returns transaction hash', async () => { const transactionHash = await addDappTransaction(dappRequest); expect(transactionHash).toStrictEqual(TRANSACTION_META_MOCK.hash); diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 0bbf93afd8a8..a3d0b929aff8 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -1,4 +1,5 @@ -import { EthAccountType, InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { TransactionController, TransactionMeta, @@ -46,7 +47,7 @@ type BaseAddTransactionRequest = { }; type FinalAddTransactionRequest = BaseAddTransactionRequest & { - transactionOptions: AddTransactionOptions; + transactionOptions: Partial; }; export type AddTransactionRequest = FinalAddTransactionRequest & { @@ -66,7 +67,7 @@ export async function addDappTransaction( const { id: actionId, method, origin } = dappRequest; const { securityAlertResponse, traceContext } = dappRequest; - const transactionOptions: AddTransactionOptions = { + const transactionOptions: Partial = { actionId, method, origin, @@ -143,10 +144,11 @@ async function addTransactionWithController( transactionParams, networkClientId, } = request; + const { result, transactionMeta } = await transactionController.addTransaction(transactionParams, { ...transactionOptions, - ...(process.env.TRANSACTION_MULTICHAIN ? { networkClientId } : {}), + networkClientId, }); return { diff --git a/app/scripts/lib/tx-verification/tx-verification-middleware.ts b/app/scripts/lib/tx-verification/tx-verification-middleware.ts index 7abdf73e3637..e21a38f1e726 100644 --- a/app/scripts/lib/tx-verification/tx-verification-middleware.ts +++ b/app/scripts/lib/tx-verification/tx-verification-middleware.ts @@ -20,9 +20,7 @@ import { TRUSTED_SIGNERS, } from '../../../../shared/constants/verification'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { getCurrentChainId } from '../../../../ui/selectors'; +import { getCurrentChainId } from '../../../../shared/modules/selectors/networks'; export type TxParams = { chainId?: `0x${string}`; diff --git a/app/scripts/lib/util.ts b/app/scripts/lib/util.ts index e41b2a00b670..676be0e8f813 100644 --- a/app/scripts/lib/util.ts +++ b/app/scripts/lib/util.ts @@ -6,6 +6,7 @@ import { TransactionEnvelopeType, TransactionMeta, } from '@metamask/transaction-controller'; +import type { Provider } from '@metamask/network-controller'; import { ENVIRONMENT_TYPE_BACKGROUND, ENVIRONMENT_TYPE_FULLSCREEN, @@ -394,7 +395,7 @@ export const getMethodDataName = async ( use4ByteResolution: boolean, prefixedData: string, addKnownMethodData: (fourBytePrefix: string, methodData: MethodData) => void, - provider: object, + provider: Provider, ) => { if (!prefixedData || !use4ByteResolution) { return null; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 183a476a4d46..7a16ea7d6670 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -19,18 +19,14 @@ import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; import { ObservableStore } from '@metamask/obs-store'; import { storeAsStream } from '@metamask/obs-store/dist/asStream'; import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware'; -import { debounce, throttle, memoize, wrap } from 'lodash'; +import { debounce, throttle, memoize, wrap, pick } from 'lodash'; import { KeyringController, keyringBuilderFactory, } from '@metamask/keyring-controller'; import createFilterMiddleware from '@metamask/eth-json-rpc-filters'; import createSubscriptionManager from '@metamask/eth-json-rpc-filters/subscriptionManager'; -import { - errorCodes as rpcErrorCodes, - JsonRpcError, - providerErrors, -} from '@metamask/rpc-errors'; +import { JsonRpcError, providerErrors } from '@metamask/rpc-errors'; import { Mutex } from 'await-semaphore'; import log from 'loglevel'; @@ -45,9 +41,7 @@ import { import LatticeKeyring from 'eth-lattice-keyring'; import { rawChainData } from 'eth-chainlist'; import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring'; -import EthQuery from '@metamask/eth-query'; -import EthJSQuery from '@metamask/ethjs-query'; -import nanoid from 'nanoid'; +import { nanoid } from 'nanoid'; import { captureException } from '@sentry/browser'; import { AddressBookController } from '@metamask/address-book-controller'; import { @@ -64,6 +58,7 @@ import { } from '@metamask/network-controller'; import { GasFeeController } from '@metamask/gas-fee-controller'; import { + MethodNames, PermissionController, PermissionDoesNotExistError, PermissionsRequestNotFoundError, @@ -71,6 +66,7 @@ import { SubjectType, } from '@metamask/permission-controller'; import SmartTransactionsController from '@metamask/smart-transactions-controller'; +import { ClientId } from '@metamask/smart-transactions-controller/dist/types'; import { METAMASK_DOMAIN, SelectedNetworkController, @@ -80,7 +76,6 @@ import { LoggingController, LogType } from '@metamask/logging-controller'; import { PermissionLogController } from '@metamask/permission-log-controller'; import { RateLimitController } from '@metamask/rate-limit-controller'; -import { NotificationController } from '@metamask/notification-controller'; import { CronjobController, JsonSnapsRegistry, @@ -104,6 +99,13 @@ import { } from '@metamask/controller-utils'; import { AccountsController } from '@metamask/accounts-controller'; +import { + RemoteFeatureFlagController, + ClientConfigApiService, + ClientType, + DistributionType, + EnvironmentType, +} from '@metamask/remote-feature-flag-controller'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import { @@ -139,16 +141,19 @@ import { TransactionType, } from '@metamask/transaction-controller'; -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getLocalizedSnapManifest, stripSnapPrefix, + ///: END:ONLY_INCLUDE_IF + isSnapId, } from '@metamask/snaps-utils'; -///: END:ONLY_INCLUDE_IF import { Interface } from '@ethersproject/abi'; import { abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis'; import { isEvmAccountType } from '@metamask/keyring-api'; +import { hexToBigInt, toCaipChainId } from '@metamask/utils'; +import { normalize } from '@metamask/eth-sig-util'; import { AuthenticationController, UserStorageController, @@ -157,6 +162,15 @@ import { NotificationServicesPushController, NotificationServicesController, } from '@metamask/notification-services-controller'; +import { + Caip25CaveatMutators, + Caip25CaveatType, + Caip25EndowmentPermissionName, + getEthAccounts, + setPermittedEthChainIds, + setEthAccounts, + addPermittedEthChainId, +} from '@metamask/multichain'; import { methodsRequiringNetworkSwitch, methodsThatCanSwitchNetworkWithoutApproval, @@ -188,14 +202,14 @@ import { } from '../../shared/constants/hardware-wallets'; import { KeyringType } from '../../shared/constants/keyring'; import { - CaveatTypes, RestrictedMethods, EndowmentPermissions, ExcludedSnapPermissions, ExcludedSnapEndowments, + CaveatTypes, } from '../../shared/constants/permissions'; import { UI_NOTIFICATIONS } from '../../shared/notifications'; -import { MILLISECOND, SECOND } from '../../shared/constants/time'; +import { MILLISECOND, MINUTE, SECOND } from '../../shared/constants/time'; import { ORIGIN_METAMASK, POLLING_TOKEN_ENVIRONMENT_TYPES, @@ -236,14 +250,15 @@ import { TOKEN_TRANSFER_LOG_TOPIC_HASH, TRANSFER_SINFLE_LOG_TOPIC_HASH, } from '../../shared/lib/transactions-controller-utils'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { getCurrentChainId } from '../../ui/selectors/selectors'; import { getProviderConfig } from '../../shared/modules/selectors/networks'; import { endTrace, trace } from '../../shared/lib/trace'; -// eslint-disable-next-line import/no-restricted-paths -import { isSnapId } from '../../ui/helpers/utils/snaps'; import { BridgeStatusAction } from '../../shared/types/bridge-status'; +import { ENVIRONMENT } from '../../development/build/constants'; +import fetchWithCache from '../../shared/lib/fetch-with-cache'; +import { + BridgeUserAction, + BridgeBackgroundAction, +} from '../../shared/types/bridge'; import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController'; import { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -290,11 +305,12 @@ import AccountTrackerController from './controllers/account-tracker-controller'; import createDupeReqFilterStream from './lib/createDupeReqFilterStream'; import createLoggerMiddleware from './lib/createLoggerMiddleware'; import { - createLegacyMethodMiddleware, - createMethodMiddleware, + createEthAccountsMethodMiddleware, + createEip1193MethodMiddleware, createUnsupportedMethodMiddleware, } from './lib/rpc-method-middleware'; import createOriginMiddleware from './lib/createOriginMiddleware'; +import createMainFrameOriginMiddleware from './lib/createMainFrameOriginMiddleware'; import createTabIdMiddleware from './lib/createTabIdMiddleware'; import { NetworkOrderController } from './controllers/network-order'; import { AccountOrderController } from './controllers/account-order'; @@ -321,8 +337,6 @@ import EncryptionPublicKeyController from './controllers/encryption-public-key'; import AppMetadataController from './controllers/app-metadata'; import { - CaveatFactories, - CaveatMutatorFactories, getCaveatSpecifications, diffMap, getPermissionBackgroundApiMethods, @@ -330,8 +344,8 @@ import { getPermittedAccountsByOrigin, getPermittedChainsByOrigin, NOTIFICATION_NAMES, - PermissionNames, unrestrictedMethods, + PermissionNames, } from './controllers/permissions'; import { MetaMetricsDataDeletionController } from './controllers/metametrics-data-deletion/metametrics-data-deletion'; import { DataDeletionService } from './services/data-deletion-service'; @@ -359,10 +373,6 @@ import { updateSecurityAlertResponse } from './lib/ppom/ppom-util'; import createEvmMethodsToNonEvmAccountReqFilterMiddleware from './lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware'; import { isEthAddress } from './lib/multichain/address'; import { decodeTransactionData } from './lib/transaction/decode/util'; -import { - BridgeUserAction, - BridgeBackgroundAction, -} from './controllers/bridge/types'; import BridgeController from './controllers/bridge/bridge-controller'; import { BRIDGE_CONTROLLER_NAME } from './controllers/bridge/constants'; import { @@ -374,19 +384,21 @@ import { PatchStore } from './lib/PatchStore'; import { sanitizeUIState } from './lib/state-utils'; import BridgeStatusController from './controllers/bridge-status/bridge-status-controller'; import { BRIDGE_STATUS_CONTROLLER_NAME } from './controllers/bridge-status/constants'; +import { rejectAllApprovals } from './lib/approval/utils'; +const { TRIGGER_TYPES } = NotificationServicesController.Constants; export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) // The process of updating the badge happens in app/scripts/background.js. UPDATE_BADGE: 'updateBadge', // TODO: Add this and similar enums to the `controllers` repo and export them APPROVAL_STATE_CHANGE: 'ApprovalController:stateChange', + APP_STATE_UNLOCK_CHANGE: 'AppStateController:unlockChange', QUEUED_REQUEST_STATE_CHANGE: 'QueuedRequestController:stateChange', METAMASK_NOTIFICATIONS_LIST_UPDATED: 'NotificationServicesController:notificationsListUpdated', METAMASK_NOTIFICATIONS_MARK_AS_READ: 'NotificationServicesController:markNotificationsAsRead', - NOTIFICATIONS_STATE_CHANGE: 'NotificationController:stateChange', }; // stream channels @@ -395,6 +407,17 @@ const PHISHING_SAFELIST = 'metamask-phishing-safelist'; // OneKey devices can connect to Metamask using Trezor USB transport. They use a specific device minor version (99) to differentiate between genuine Trezor and OneKey devices. export const ONE_KEY_VIA_TREZOR_MINOR_VERSION = 99; +const environmentMappingForRemoteFeatureFlag = { + [ENVIRONMENT.DEVELOPMENT]: EnvironmentType.Development, + [ENVIRONMENT.RELEASE_CANDIDATE]: EnvironmentType.ReleaseCandidate, + [ENVIRONMENT.PRODUCTION]: EnvironmentType.Production, +}; + +const buildTypeMappingForRemoteFeatureFlag = { + flask: DistributionType.Flask, + main: DistributionType.Main, +}; + export default class MetamaskController extends EventEmitter { /** * @param {object} opts @@ -478,6 +501,11 @@ export default class MetamaskController extends EventEmitter { this.appMetadataController = new AppMetadataController({ state: initState.AppMetadataController, + messenger: this.controllerMessenger.getRestricted({ + name: 'AppMetadataController', + allowedActions: [], + allowedEvents: [], + }), currentMigrationVersion: this.currentMigrationVersion, currentAppVersion: version, }); @@ -646,7 +674,9 @@ export default class MetamaskController extends EventEmitter { }); this.tokenListController = new TokenListController({ - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId({ + metamask: this.networkController.state, + }), preventPollingOnNetworkRestart: !this.#isTokenListPollingRequired( this.preferencesController.state, ), @@ -670,7 +700,7 @@ export default class MetamaskController extends EventEmitter { }); this.assetsContractController = new AssetsContractController({ messenger: assetsContractControllerMessenger, - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId(), }); const tokensControllerMessenger = this.controllerMessenger.getRestricted({ @@ -693,7 +723,7 @@ export default class MetamaskController extends EventEmitter { state: initState.TokensController, provider: this.provider, messenger: tokensControllerMessenger, - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId(), }); const nftControllerMessenger = this.controllerMessenger.getRestricted({ @@ -719,7 +749,7 @@ export default class MetamaskController extends EventEmitter { this.nftController = new NftController({ state: initState.NftController, messenger: nftControllerMessenger, - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId(), onNftAdded: ({ address, symbol, tokenId, standard, source }) => this.metaMetricsController.trackEvent({ event: MetaMetricsEventName.NftAdded, @@ -735,8 +765,6 @@ export default class MetamaskController extends EventEmitter { }), }); - this.nftController.setApiKey(process.env.OPENSEA_KEY); - const nftDetectionControllerMessenger = this.controllerMessenger.getRestricted({ name: 'NftDetectionController', @@ -754,7 +782,7 @@ export default class MetamaskController extends EventEmitter { this.nftDetectionController = new NftDetectionController({ messenger: nftDetectionControllerMessenger, - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId(), getOpenSeaApiKey: () => this.nftController.openSeaApiKey, getBalancesInSingleCall: this.assetsContractController.getBalancesInSingleCall.bind( @@ -843,19 +871,16 @@ export default class MetamaskController extends EventEmitter { legacyAPIEndpoint: `${gasApiBaseUrl}/networks//gasPrices`, EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`, getCurrentNetworkLegacyGasAPICompatibility: () => { - const chainId = getCurrentChainId({ - metamask: this.networkController.state, - }); + const chainId = this.#getGlobalChainId(); return chainId === CHAIN_IDS.BSC; }, - getChainId: () => - getCurrentChainId({ metamask: this.networkController.state }), + getChainId: () => this.#getGlobalChainId(), }); this.appStateController = new AppStateController({ addUnlockListener: this.on.bind(this, 'unlock'), isUnlocked: this.isUnlocked.bind(this), - initState: initState.AppStateController, + state: initState.AppStateController, onInactiveTimeout: () => this.setLocked(), messenger: this.controllerMessenger.getRestricted({ name: 'AppStateController', @@ -944,7 +969,7 @@ export default class MetamaskController extends EventEmitter { ppomInit: () => PPOMModule.default(process.env.PPOM_URI), }, state: initState.PPOMController, - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId(), securityAlertsEnabled: this.preferencesController.state.securityAlertsEnabled, onPreferencesChange: preferencesMessenger.subscribe.bind( @@ -1052,10 +1077,12 @@ export default class MetamaskController extends EventEmitter { this.ensController = new EnsController({ messenger: this.controllerMessenger.getRestricted({ name: 'EnsController', - allowedActions: ['NetworkController:getNetworkClientById'], + allowedActions: [ + 'NetworkController:getNetworkClientById', + 'NetworkController:getState', + ], allowedEvents: [], }), - provider: this.provider, onNetworkDidChange: networkControllerMessenger.subscribe.bind( networkControllerMessenger, 'NetworkController:networkDidChange', @@ -1243,7 +1270,7 @@ export default class MetamaskController extends EventEmitter { }), state: initState.PermissionController, caveatSpecifications: getCaveatSpecifications({ - getInternalAccounts: this.accountsController.listAccounts.bind( + listAccounts: this.accountsController.listAccounts.bind( this.accountsController, ), findNetworkClientIdByChainId: @@ -1252,42 +1279,7 @@ export default class MetamaskController extends EventEmitter { ), }), permissionSpecifications: { - ...getPermissionSpecifications({ - getInternalAccounts: this.accountsController.listAccounts.bind( - this.accountsController, - ), - getAllAccounts: this.keyringController.getAccounts.bind( - this.keyringController, - ), - captureKeyringTypesWithMissingIdentities: ( - internalAccounts = [], - accounts = [], - ) => { - const accountsMissingIdentities = accounts.filter( - (address) => - !internalAccounts.some( - (account) => - account.address.toLowerCase() === address.toLowerCase(), - ), - ); - const keyringTypesWithMissingIdentities = - accountsMissingIdentities.map((address) => - this.keyringController.getAccountKeyringType(address), - ); - - const internalAccountCount = internalAccounts.length; - - const accountTrackerCount = Object.keys( - this.accountTrackerController.state.accounts || {}, - ).length; - - captureException( - new Error( - `Attempt to get permission specifications failed because their were ${accounts.length} accounts, but ${internalAccountCount} identities, and the ${keyringTypesWithMissingIdentities} keyrings included accounts with missing identities. Meanwhile, there are ${accountTrackerCount} accounts in the account tracker.`, - ), - ); - }, - }), + ...getPermissionSpecifications(), ...this.getSnapPermissionSpecifications(), }, unrestrictedMethods, @@ -1309,13 +1301,13 @@ export default class MetamaskController extends EventEmitter { ], }), state: initState.SelectedNetworkController, - useRequestQueuePreference: - this.preferencesController.state.useRequestQueue, - onPreferencesStateChange: (listener) => { - preferencesMessenger.subscribe( - 'PreferencesController:stateChange', - listener, - ); + useRequestQueuePreference: true, + onPreferencesStateChange: () => { + // noop + // we have removed the ability to toggle the useRequestQueue preference + // both useRequestQueue and onPreferencesStateChange will be removed + // once mobile supports per dapp network selection + // see https://github.com/MetaMask/core/pull/5065#issue-2736965186 }, domainProxyMap: new WeakRefObjectMap(), }); @@ -1367,6 +1359,7 @@ export default class MetamaskController extends EventEmitter { 'ExecutionService:unhandledError', 'ExecutionService:outboundRequest', 'ExecutionService:outboundResponse', + 'KeyringController:lock', ], allowedActions: [ `${this.permissionController.name}:getEndowments`, @@ -1404,6 +1397,7 @@ export default class MetamaskController extends EventEmitter { process.env.REJECT_INVALID_SNAPS_PLATFORM_VERSION; this.snapController = new SnapController({ + dynamicPermissions: ['endowment:caip25'], environmentEndowmentPermissions: Object.values(EndowmentPermissions), excludedPermissions: { ...ExcludedSnapPermissions, @@ -1429,13 +1423,6 @@ export default class MetamaskController extends EventEmitter { }, }); - this.notificationController = new NotificationController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'NotificationController', - }), - state: initState.NotificationController, - }); - this.rateLimitController = new RateLimitController({ state: initState.RateLimitController, messenger: this.controllerMessenger.getRestricted({ @@ -1463,11 +1450,28 @@ export default class MetamaskController extends EventEmitter { rateLimitTimeout: 300000, }, showInAppNotification: { - method: (origin, message) => { + method: (origin, args) => { + const { message, title, footerLink, interfaceId } = args; + + const detailedView = { + title, + ...(footerLink ? { footerLink } : {}), + interfaceId, + }; + + const notification = { + data: { + message, + origin, + ...(interfaceId ? { detailedView } : {}), + }, + type: TRIGGER_TYPES.SNAP, + readDate: null, + }; + this.controllerMessenger.call( - 'NotificationController:show', - origin, - message, + 'NotificationServicesController:updateMetamaskNotificationsList', + notification, ); return null; @@ -1598,7 +1602,15 @@ export default class MetamaskController extends EventEmitter { }, }); }, - onAccountSyncErroneousSituation: (profileId, situationMessage) => { + onAccountSyncErroneousSituation: ( + profileId, + situationMessage, + sentryContext, + ) => { + captureException( + new Error(`Account sync - ${situationMessage}`), + sentryContext, + ); this.metaMetricsController.trackEvent({ category: MetaMetricsEventCategory.ProfileSyncing, event: MetaMetricsEventName.AccountsSyncErroneousSituation, @@ -1628,16 +1640,54 @@ export default class MetamaskController extends EventEmitter { 'NotificationServicesController:selectIsNotificationServicesEnabled', 'AccountsController:listAccounts', 'AccountsController:updateAccountMetadata', + 'NetworkController:getState', + 'NetworkController:addNetwork', + 'NetworkController:removeNetwork', + 'NetworkController:updateNetwork', ], allowedEvents: [ 'KeyringController:lock', 'KeyringController:unlock', 'AccountsController:accountAdded', 'AccountsController:accountRenamed', + 'NetworkController:networkRemoved', ], }), }); + this.controllerMessenger.subscribe( + 'MetaMetricsController:stateChange', + previousValueComparator(async (prevState, currState) => { + const { participateInMetaMetrics: prevParticipateInMetaMetrics } = + prevState; + const { participateInMetaMetrics: currParticipateInMetaMetrics } = + currState; + + const metaMetricsWasDisabled = + prevParticipateInMetaMetrics && !currParticipateInMetaMetrics; + const metaMetricsWasEnabled = + !prevParticipateInMetaMetrics && currParticipateInMetaMetrics; + + if (!metaMetricsWasDisabled && !metaMetricsWasEnabled) { + return; + } + + const shouldPerformSignIn = + metaMetricsWasEnabled && + !this.authenticationController.state.isSignedIn; + const shouldPerformSignOut = + metaMetricsWasDisabled && + this.authenticationController.state.isSignedIn && + !this.userStorageController.state.isProfileSyncingEnabled; + + if (shouldPerformSignIn) { + await this.authenticationController.performSignIn(); + } else if (shouldPerformSignOut) { + await this.authenticationController.performSignOut(); + } + }, this.metaMetricsController.state), + ); + const notificationServicesPushControllerMessenger = this.controllerMessenger.getRestricted({ name: 'NotificationServicesPushController', @@ -1905,8 +1955,8 @@ export default class MetamaskController extends EventEmitter { ], allowedEvents: [`NetworkController:stateChange`], }); + this.txController = new TransactionController({ - blockTracker: this.blockTracker, getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind( this.networkController, @@ -1924,10 +1974,10 @@ export default class MetamaskController extends EventEmitter { ), getNetworkState: () => this.networkController.state, getPermittedAccounts: this.getPermittedAccounts.bind(this), - getSavedGasFees: () => - this.preferencesController.state.advancedGasFee[ - getCurrentChainId({ metamask: this.networkController.state }) - ], + getSavedGasFees: () => { + const globalChainId = this.#getGlobalChainId(); + return this.preferencesController.state.advancedGasFee[globalChainId]; + }, incomingTransactions: { etherscanApiKeysByChainId: { [CHAIN_IDS.MAINNET]: process.env.ETHERSCAN_API_KEY, @@ -1935,26 +1985,17 @@ export default class MetamaskController extends EventEmitter { }, includeTokenTransfers: false, isEnabled: () => - Boolean( - this.preferencesController.state.incomingTransactionsPreferences?.[ - getCurrentChainId({ metamask: this.networkController.state }) - ] && this.onboardingController.state.completedOnboarding, - ), + this.preferencesController.state.incomingTransactionsPreferences?.[ + this.#getGlobalChainId() + ] && this.onboardingController.state.completedOnboarding, queryEntireHistory: false, updateTransactions: false, }, isFirstTimeInteractionEnabled: () => this.preferencesController.state.securityAlertsEnabled, - isMultichainEnabled: process.env.TRANSACTION_MULTICHAIN, isSimulationEnabled: () => this.preferencesController.state.useTransactionSimulations, messenger: transactionControllerMessenger, - onNetworkStateChange: (listener) => { - networkControllerMessenger.subscribe( - 'NetworkController:networkDidChange', - () => listener(), - ); - }, pendingTransactions: { isResubmitEnabled: () => { const state = this._getMetaMaskState(); @@ -1964,7 +2005,6 @@ export default class MetamaskController extends EventEmitter { ); }, }, - provider: this.provider, testGasFeeFlows: process.env.TEST_GAS_FEE_FLOWS, trace, hooks: { @@ -2044,13 +2084,12 @@ export default class MetamaskController extends EventEmitter { decodingApiUrl: process.env.DECODING_API_URL, isDecodeSignatureRequestEnabled: () => this.preferencesController.state.useExternalServices === true && - this.preferencesController.state.useTransactionSimulations && - process.env.ENABLE_SIGNATURE_DECODING === true, + this.preferencesController.state.useTransactionSimulations, }); this.signatureController.hub.on( 'cancelWithReason', - ({ message, reason }) => { + ({ metadata: message, reason }) => { this.metaMetricsController.trackEvent({ event: reason, category: MetaMetricsEventCategory.Transactions, @@ -2145,12 +2184,12 @@ export default class MetamaskController extends EventEmitter { this.swapsController = new SwapsController( { messenger: swapsControllerMessenger, - // TODO: Remove once TransactionController exports this action type getBufferedGasLimit: async (txMeta, multiplier) => { const { gas: gasLimit, simulationFails } = await this.txController.estimateGasBuffered( txMeta.txParams, multiplier, + this.#getGlobalNetworkClientId(), ); return { gasLimit, simulationFails }; @@ -2196,6 +2235,7 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:getNetworkClientById', 'NetworkController:findNetworkClientIdByChainId', 'NetworkController:getState', + 'TransactionController:getState', ], allowedEvents: [], }); @@ -2207,12 +2247,20 @@ export default class MetamaskController extends EventEmitter { const smartTransactionsControllerMessenger = this.controllerMessenger.getRestricted({ name: 'SmartTransactionsController', - allowedActions: ['NetworkController:getNetworkClientById'], + allowedActions: [ + 'NetworkController:getNetworkClientById', + 'NetworkController:getState', + ], allowedEvents: ['NetworkController:stateChange'], }); this.smartTransactionsController = new SmartTransactionsController({ supportedChainIds: getAllowedSmartTransactionsChainIds(), - getNonceLock: this.txController.getNonceLock.bind(this.txController), + clientId: ClientId.Extension, + getNonceLock: (address) => + this.txController.getNonceLock( + address, + this.#getGlobalNetworkClientId(), + ), confirmExternalTransaction: this.txController.confirmExternalTransaction.bind(this.txController), trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( @@ -2223,6 +2271,13 @@ export default class MetamaskController extends EventEmitter { getTransactions: this.txController.getTransactions.bind( this.txController, ), + updateTransaction: this.txController.updateTransaction.bind( + this.txController, + ), + getFeatureFlags: () => { + const state = this._getMetaMaskState(); + return getFeatureFlagsByChainId(state); + }, getMetaMetricsProps: async () => { const selectedAddress = this.accountsController.getSelectedAccount().address; @@ -2334,6 +2389,41 @@ export default class MetamaskController extends EventEmitter { clearPendingConfirmations.bind(this), ); + // RemoteFeatureFlagController has subscription for preferences changes + this.controllerMessenger.subscribe( + 'PreferencesController:stateChange', + previousValueComparator((prevState, currState) => { + const { useExternalServices: prevUseExternalServices } = prevState; + const { useExternalServices: currUseExternalServices } = currState; + if (currUseExternalServices && !prevUseExternalServices) { + this.remoteFeatureFlagController.enable(); + this.remoteFeatureFlagController.updateRemoteFeatureFlags(); + } else if (!currUseExternalServices && prevUseExternalServices) { + this.remoteFeatureFlagController.disable(); + } + }, this.preferencesController.state), + ); + + // Initialize RemoteFeatureFlagController + this.remoteFeatureFlagController = new RemoteFeatureFlagController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'RemoteFeatureFlagController', + allowedActions: [], + allowedEvents: [], + }), + disabled: !this.preferencesController.state.useExternalServices, + getMetaMetricsId: () => this.metaMetricsController.getMetaMetricsId(), + clientConfigApiService: new ClientConfigApiService({ + fetch: globalThis.fetch.bind(globalThis), + config: { + client: ClientType.Extension, + distribution: + this._getConfigForRemoteFeatureFlagRequest().distribution, + environment: this._getConfigForRemoteFeatureFlagRequest().environment, + }, + }), + }); + this.metamaskMiddleware = createMetamaskMiddleware({ static: { eth_syncing: false, @@ -2341,18 +2431,13 @@ export default class MetamaskController extends EventEmitter { }, version, // account mgmt - getAccounts: async ( - { origin: innerOrigin }, - { suppressUnauthorizedError = true } = {}, - ) => { + getAccounts: ({ origin: innerOrigin }) => { if (innerOrigin === ORIGIN_METAMASK) { const selectedAddress = this.accountsController.getSelectedAccount().address; return selectedAddress ? [selectedAddress] : []; } else if (this.isUnlocked()) { - return await this.getPermittedAccounts(innerOrigin, { - suppressUnauthorizedError, - }); + return this.getPermittedAccounts(innerOrigin); } return []; // changing this is a breaking change }, @@ -2451,8 +2536,8 @@ export default class MetamaskController extends EventEmitter { this.store.updateStructure({ AccountsController: this.accountsController, - AppStateController: this.appStateController.store, - AppMetadataController: this.appMetadataController.store, + AppStateController: this.appStateController, + AppMetadataController: this.appMetadataController, MultichainBalancesController: this.multichainBalancesController, TransactionController: this.txController, KeyringController: this.keyringController, @@ -2483,7 +2568,6 @@ export default class MetamaskController extends EventEmitter { SnapController: this.snapController, CronjobController: this.cronjobController, SnapsRegistry: this.snapsRegistry, - NotificationController: this.notificationController, SnapInterfaceController: this.snapInterfaceController, SnapInsightsController: this.snapInsightsController, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -2501,14 +2585,15 @@ export default class MetamaskController extends EventEmitter { NotificationServicesController: this.notificationServicesController, NotificationServicesPushController: this.notificationServicesPushController, + RemoteFeatureFlagController: this.remoteFeatureFlagController, ...resetOnRestartStore, }); this.memStore = new ComposableObservableStore({ config: { AccountsController: this.accountsController, - AppStateController: this.appStateController.store, - AppMetadataController: this.appMetadataController.store, + AppStateController: this.appStateController, + AppMetadataController: this.appMetadataController, MultichainBalancesController: this.multichainBalancesController, NetworkController: this.networkController, KeyringController: this.keyringController, @@ -2539,7 +2624,6 @@ export default class MetamaskController extends EventEmitter { SnapController: this.snapController, CronjobController: this.cronjobController, SnapsRegistry: this.snapsRegistry, - NotificationController: this.notificationController, SnapInterfaceController: this.snapInterfaceController, SnapInsightsController: this.snapInsightsController, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -2557,6 +2641,7 @@ export default class MetamaskController extends EventEmitter { QueuedRequestController: this.queuedRequestController, NotificationServicesPushController: this.notificationServicesPushController, + RemoteFeatureFlagController: this.remoteFeatureFlagController, ...resetOnRestartStore, }, controllerMessenger: this.controllerMessenger, @@ -2635,6 +2720,29 @@ export default class MetamaskController extends EventEmitter { } } + // Provides a method for getting feature flags for the multichain + // initial rollout, such that we can remotely modify polling interval + getInfuraFeatureFlags() { + fetchWithCache({ + url: 'https://swap.api.cx.metamask.io/featureFlags', + cacheRefreshTime: MINUTE * 20, + }) + .then(this.onFeatureFlagResponseReceived) + .catch((e) => { + // API unreachable (?) + log.warn('Feature flag endpoint is unreachable', e); + }); + } + + onFeatureFlagResponseReceived(response) { + const { multiChainAssets = {} } = response; + const { pollInterval } = multiChainAssets; + // Polling interval is provided in seconds + if (pollInterval > 0) { + this.tokenBalancesController.setIntervalLength(pollInterval * SECOND); + } + } + postOnboardingInitialization() { const { usePhishDetect } = this.preferencesController.state; @@ -2665,8 +2773,14 @@ export default class MetamaskController extends EventEmitter { } triggerNetworkrequests() { - this.txController.startIncomingTransactionPolling(); + this.txController.stopIncomingTransactionPolling(); + + this.txController.startIncomingTransactionPolling([ + this.#getGlobalChainId(), + ]); + this.tokenDetectionController.enable(); + this.getInfuraFeatureFlags(); } stopNetworkRequests() { @@ -2790,6 +2904,17 @@ export default class MetamaskController extends EventEmitter { return currentLocale; } + /** + * Gets whether the privacy mode is enabled from the PreferencesController. + * + * @returns {boolean} Whether the privacy mode is enabled. + */ + getPrivacyMode() { + const { privacyMode } = this.preferencesController.state; + + return privacyMode; + } + /** * Constructor helper for getting Snap permission specifications. */ @@ -2802,7 +2927,8 @@ export default class MetamaskController extends EventEmitter { getPreferences: () => { const locale = this.getLocale(); const currency = this.currencyRateController.state.currentCurrency; - return { locale, currency }; + const hideBalances = this.getPrivacyMode(); + return { locale, currency, hideBalances }; }, clearSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, @@ -2833,14 +2959,22 @@ export default class MetamaskController extends EventEmitter { origin, args.message, ), - showInAppNotification: (origin, args) => - this.controllerMessenger.call( + showInAppNotification: (origin, args) => { + const { message, title, footerLink } = args; + const notificationArgs = { + interfaceId: args.content, + message, + title, + footerLink, + }; + return this.controllerMessenger.call( 'RateLimitController:call', origin, 'showInAppNotification', origin, - args.message, - ), + notificationArgs, + ); + }, updateSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:updateSnapState', @@ -2857,8 +2991,7 @@ export default class MetamaskController extends EventEmitter { ); }, isOnPhishingList: (url) => { - const { usePhishDetect } = - this.preferencesController.store.getState(); + const { usePhishDetect } = this.preferencesController.state; if (!usePhishDetect) { return false; @@ -2877,6 +3010,8 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapInterfaceController:getInterface', ), + // We don't currently use special cryptography for the extension client. + getClientCryptography: () => ({}), ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getSnapKeyring: this.getSnapKeyring.bind(this), ///: END:ONLY_INCLUDE_IF @@ -2885,24 +3020,6 @@ export default class MetamaskController extends EventEmitter { }; } - /** - * Deletes the specified notifications from state. - * - * @param {string[]} ids - The notifications ids to delete. - */ - dismissNotifications(ids) { - this.notificationController.dismiss(ids); - } - - /** - * Updates the readDate attribute of the specified notifications. - * - * @param {string[]} ids - The notifications ids to mark as read. - */ - markNotificationsAsRead(ids) { - this.notificationController.markRead(ids); - } - /** * Sets up BaseController V2 event subscriptions. Currently, this includes * the subscriptions necessary to notify permission subjects of account @@ -2921,13 +3038,16 @@ export default class MetamaskController extends EventEmitter { 'PreferencesController:stateChange', previousValueComparator(async (prevState, currState) => { const { currentLocale } = currState; - const chainId = getCurrentChainId({ - metamask: this.networkController.state, - }); + const chainId = this.#getGlobalChainId(); await updateCurrentLocale(currentLocale); + if (currState.incomingTransactionsPreferences?.[chainId]) { - this.txController.startIncomingTransactionPolling(); + this.txController.stopIncomingTransactionPolling(); + + this.txController.startIncomingTransactionPolling([ + this.#getGlobalChainId(), + ]); } else { this.txController.stopIncomingTransactionPolling(); } @@ -2997,7 +3117,15 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( 'NetworkController:networkDidChange', async () => { - await this.txController.updateIncomingTransactions(); + await this.txController.stopIncomingTransactionPolling(); + + await this.txController.updateIncomingTransactions([ + this.#getGlobalChainId(), + ]); + + await this.txController.startIncomingTransactionPolling([ + this.#getGlobalChainId(), + ]); }, ); @@ -3110,16 +3238,16 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( `${this.snapController.name}:snapUninstalled`, (truncatedSnap) => { - const notificationIds = Object.values( - this.notificationController.state.notifications, - ).reduce((idList, notification) => { - if (notification.origin === truncatedSnap.id) { - idList.push(notification.id); - } - return idList; - }, []); - - this.dismissNotifications(notificationIds); + const notificationIds = this.notificationServicesController + .getNotificationsByType(TRIGGER_TYPES.SNAP) + .filter( + (notification) => notification.data.origin === truncatedSnap.id, + ) + .map((notification) => notification.id); + + this.notificationServicesController.deleteNotificationsById( + notificationIds, + ); const snapId = truncatedSnap.id; const snapCategory = this._getSnapMetadata(snapId)?.category; @@ -3163,6 +3291,17 @@ export default class MetamaskController extends EventEmitter { ); this.multichainBalancesController.start(); this.multichainBalancesController.updateBalances(); + + this.controllerMessenger.subscribe( + 'CurrencyRateController:stateChange', + ({ currentCurrency }) => { + if ( + currentCurrency !== this.multichainRatesController.state.fiatCurrency + ) { + this.multichainRatesController.setFiatCurrency(currentCurrency); + } + }, + ); } /** @@ -3206,13 +3345,11 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise<{ isUnlocked: boolean, networkVersion: string, chainId: string, accounts: string[] }>} An object with relevant state properties. */ async getProviderState(origin) { - const providerNetworkState = await this.getProviderNetworkState( - this.preferencesController.getUseRequestQueue() ? origin : undefined, - ); + const providerNetworkState = await this.getProviderNetworkState(origin); return { isUnlocked: this.isUnlocked(), - accounts: await this.getPermittedAccounts(origin), + accounts: this.getPermittedAccounts(origin), ...providerNetworkState, }; } @@ -3240,17 +3377,16 @@ export default class MetamaskController extends EventEmitter { let networkVersion = this.deprecatedNetworkVersions[networkClientId]; if (networkVersion === undefined && completedOnboarding) { - const ethQuery = new EthQuery(networkClient.provider); - networkVersion = await new Promise((resolve) => { - ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { - if (error) { - console.error(error); - resolve(null); - } else { - resolve(convertNetworkId(result)); - } + try { + const result = await networkClient.provider.request({ + method: 'net_version', }); - }); + networkVersion = convertNetworkId(result); + } catch (error) { + console.error(error); + networkVersion = null; + } + this.deprecatedNetworkVersions[networkClientId] = networkVersion; } @@ -3363,9 +3499,6 @@ export default class MetamaskController extends EventEmitter { setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind( preferencesController, ), - getUseRequestQueue: this.preferencesController.getUseRequestQueue.bind( - this.preferencesController, - ), getProviderConfig: () => getProviderConfig({ metamask: this.networkController.state, @@ -3415,7 +3548,6 @@ export default class MetamaskController extends EventEmitter { preferencesController.setUseTransactionSimulations.bind( preferencesController, ), - setUseRequestQueue: this.setUseRequestQueue.bind(this), setIpfsGateway: preferencesController.setIpfsGateway.bind( preferencesController, ), @@ -3457,10 +3589,6 @@ export default class MetamaskController extends EventEmitter { markNotificationPopupAsAutomaticallyClosed: () => this.notificationManager.markAsAutomaticallyClosed(), - // approval - requestUserApproval: - approvalController.addAndShowApprovalRequest.bind(approvalController), - // primary keyring management addNewAccount: this.addNewAccount.bind(this), getSeedPhrase: this.getSeedPhrase.bind(this), @@ -3683,8 +3811,6 @@ export default class MetamaskController extends EventEmitter { appStateController.setShowNetworkBanner.bind(appStateController), updateNftDropDownState: appStateController.updateNftDropDownState.bind(appStateController), - setFirstTimeUsedNetwork: - appStateController.setFirstTimeUsedNetwork.bind(appStateController), setSwitchedNetworkDetails: appStateController.setSwitchedNetworkDetails.bind(appStateController), clearSwitchedNetworkDetails: @@ -3701,6 +3827,8 @@ export default class MetamaskController extends EventEmitter { appStateController.setLastInteractedConfirmationInfo.bind( appStateController, ), + updateSlides: appStateController.updateSlides.bind(appStateController), + removeSlide: appStateController.removeSlide.bind(appStateController), // EnsController tryReverseResolveAddress: @@ -3804,7 +3932,10 @@ export default class MetamaskController extends EventEmitter { removePermissionsFor: this.removePermissionsFor, approvePermissionsRequest: this.acceptPermissionsRequest, rejectPermissionsRequest: this.rejectPermissionsRequest, - ...getPermissionBackgroundApiMethods(permissionController), + ...getPermissionBackgroundApiMethods({ + permissionController, + approvalController, + }), ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) connectCustodyAddresses: this.mmiController.connectCustodyAddresses.bind( @@ -3892,8 +4023,6 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:revokeDynamicPermissions', ), - dismissNotifications: this.dismissNotifications.bind(this), - markNotificationsAsRead: this.markNotificationsAsRead.bind(this), disconnectOriginFromSnap: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:disconnectOrigin', @@ -4020,10 +4149,6 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, `${BRIDGE_CONTROLLER_NAME}:${BridgeBackgroundAction.GET_BRIDGE_ERC20_ALLOWANCE}`, ), - [BridgeUserAction.SELECT_SRC_NETWORK]: this.controllerMessenger.call.bind( - this.controllerMessenger, - `${BRIDGE_CONTROLLER_NAME}:${BridgeUserAction.SELECT_SRC_NETWORK}`, - ), [BridgeUserAction.SELECT_DEST_NETWORK]: this.controllerMessenger.call.bind( this.controllerMessenger, @@ -4088,9 +4213,12 @@ export default class MetamaskController extends EventEmitter { ), trackInsightSnapView: this.trackInsightSnapView.bind(this), - // approval controller - resolvePendingApproval: this.resolvePendingApproval, + // ApprovalController + rejectAllPendingApprovals: this.rejectAllPendingApprovals.bind(this), rejectPendingApproval: this.rejectPendingApproval, + requestUserApproval: + approvalController.addAndShowApprovalRequest.bind(approvalController), + resolvePendingApproval: this.resolvePendingApproval, // Notifications resetViewedNotifications: announcementController.resetViewed.bind( @@ -4146,8 +4274,7 @@ export default class MetamaskController extends EventEmitter { ), // GasFeeController - gasFeeStartPollingByNetworkClientId: - gasFeeController.startPollingByNetworkClientId.bind(gasFeeController), + gasFeeStartPolling: gasFeeController.startPolling.bind(gasFeeController), gasFeeStopPollingByPollingToken: gasFeeController.stopPollingByPollingToken.bind(gasFeeController), @@ -4233,6 +4360,14 @@ export default class MetamaskController extends EventEmitter { notificationServicesController.fetchAndUpdateMetamaskNotifications.bind( notificationServicesController, ), + deleteNotificationsById: + notificationServicesController.deleteNotificationsById.bind( + notificationServicesController, + ), + getNotificationsByType: + notificationServicesController.getNotificationsByType.bind( + notificationServicesController, + ), markMetamaskNotificationsAsRead: notificationServicesController.markMetamaskNotificationsAsRead.bind( notificationServicesController, @@ -4282,7 +4417,7 @@ export default class MetamaskController extends EventEmitter { decodeTransactionData: (request) => decodeTransactionData({ ...request, - ethQuery: new EthQuery(this.provider), + provider: this.provider, }), // metrics data deleteion createMetaMetricsDataDeletionTask: @@ -4363,39 +4498,46 @@ export default class MetamaskController extends EventEmitter { // or if it is true but the `fetchTokenBalance`` call failed. In either case, we should // attempt to retrieve details from `assetsContractController.getTokenStandardAndDetails` if (details === undefined) { - details = await this.assetsContractController.getTokenStandardAndDetails( - address, - userAddress, - tokenId, - ); + try { + details = + await this.assetsContractController.getTokenStandardAndDetails( + address, + userAddress, + tokenId, + ); + } catch (e) { + log.warn(`Failed to get token standard and details. Error: ${e}`); + } } - const tokenDetailsStandardIsERC1155 = isEqualCaseInsensitive( - details.standard, - TokenStandard.ERC1155, - ); + if (details) { + const tokenDetailsStandardIsERC1155 = isEqualCaseInsensitive( + details.standard, + TokenStandard.ERC1155, + ); - if (tokenDetailsStandardIsERC1155) { - try { - const balance = await fetchERC1155Balance( - address, - userAddress, - tokenId, - this.provider, - ); + if (tokenDetailsStandardIsERC1155) { + try { + const balance = await fetchERC1155Balance( + address, + userAddress, + tokenId, + this.provider, + ); - const balanceToUse = balance?._hex - ? parseInt(balance._hex, 16).toString() - : null; + const balanceToUse = balance?._hex + ? parseInt(balance._hex, 16).toString() + : null; - details = { - ...details, - balance: balanceToUse, - }; - } catch (e) { - // If the `fetchTokenBalance` call failed, `details` remains undefined, and we - // fall back to the below `assetsContractController.getTokenStandardAndDetails` call - log.warn('Failed to get token balance. Error:', e); + details = { + ...details, + balance: balanceToUse, + }; + } catch (e) { + // If the `fetchTokenBalance` call failed, `details` remains undefined, and we + // fall back to the below `assetsContractController.getTokenStandardAndDetails` call + log.warn('Failed to get token balance. Error:', e); + } } } @@ -4461,8 +4603,6 @@ export default class MetamaskController extends EventEmitter { // Clear snap state this.snapController.clearState(); - // Clear notification state - this.notificationController.clear(); // clear accounts in AccountTrackerController this.accountTrackerController.clearAccounts(); @@ -4486,7 +4626,10 @@ export default class MetamaskController extends EventEmitter { // keyring's iframe and have the setting initialized properly // Optimistically called to not block MetaMask login due to // Ledger Keyring GitHub downtime - this.setLedgerTransportPreference(); + this.#withKeyringForDevice( + { name: HardwareDeviceNames.ledger }, + async (keyring) => this.setLedgerTransportPreference(keyring), + ); } } finally { releaseLock(); @@ -4496,15 +4639,12 @@ export default class MetamaskController extends EventEmitter { async _addAccountsWithBalance() { try { // Scan accounts until we find an empty one - const chainId = getCurrentChainId({ - metamask: this.networkController.state, - }); - const ethQuery = new EthQuery(this.provider); + const chainId = this.#getGlobalChainId(); const accounts = await this.keyringController.getAccounts(); let address = accounts[accounts.length - 1]; for (let count = accounts.length; ; count++) { - const balance = await this.getBalance(address, ethQuery); + const balance = await this.getBalance(address, this.provider); if (balance === '0x0') { // This account has no balance, so check for tokens @@ -4574,25 +4714,25 @@ export default class MetamaskController extends EventEmitter { * Get an account balance from the AccountTrackerController or request it directly from the network. * * @param {string} address - The account address - * @param {EthQuery} ethQuery - The EthQuery instance to use when asking the network + * @param {Provider} provider - The provider instance to use when asking the network */ - getBalance(address, ethQuery) { - return new Promise((resolve, reject) => { - const cached = this.accountTrackerController.state.accounts[address]; + async getBalance(address, provider) { + const cached = this.accountTrackerController.state.accounts[address]; - if (cached && cached.balance) { - resolve(cached.balance); - } else { - ethQuery.getBalance(address, (error, balance) => { - if (error) { - reject(error); - log.error(error); - } else { - resolve(balance || '0x0'); - } - }); - } - }); + if (cached && cached.balance) { + return cached.balance; + } + + try { + const balance = await provider.request({ + method: 'eth_getBalance', + params: [address, 'latest'], + }); + return balance || '0x0'; + } catch (error) { + log.error(error); + throw error; + } } /** @@ -4627,7 +4767,10 @@ export default class MetamaskController extends EventEmitter { // Optimistically called to not block MetaMask login due to // Ledger Keyring GitHub downtime if (completedOnboarding) { - this.setLedgerTransportPreference(); + this.#withKeyringForDevice( + { name: HardwareDeviceNames.ledger }, + async (keyring) => this.setLedgerTransportPreference(keyring), + ); } } @@ -4731,52 +4874,11 @@ export default class MetamaskController extends EventEmitter { // Hardware // - async getKeyringForDevice(deviceName, hdPath = null) { - const keyringOverrides = this.opts.overrides?.keyrings; - let keyringName = null; - switch (deviceName) { - case HardwareDeviceNames.trezor: - keyringName = keyringOverrides?.trezor?.type || TrezorKeyring.type; - break; - case HardwareDeviceNames.ledger: - keyringName = keyringOverrides?.ledger?.type || LedgerKeyring.type; - break; - case HardwareDeviceNames.qr: - keyringName = QRHardwareKeyring.type; - break; - case HardwareDeviceNames.lattice: - keyringName = keyringOverrides?.lattice?.type || LatticeKeyring.type; - break; - default: - throw new Error( - 'MetamaskController:getKeyringForDevice - Unknown device', - ); - } - let [keyring] = await this.keyringController.getKeyringsByType(keyringName); - if (!keyring) { - keyring = await this.keyringController.addNewKeyring(keyringName); - } - if (hdPath && keyring.setHdPath) { - keyring.setHdPath(hdPath); - } - if (deviceName === HardwareDeviceNames.lattice) { - keyring.appName = 'MetaMask'; - } - if (deviceName === HardwareDeviceNames.trezor) { - const model = keyring.getModel(); - this.appStateController.setTrezorModel(model); - } - - keyring.network = getProviderConfig({ - metamask: this.networkController.state, - }).type; - - return keyring; - } - async attemptLedgerTransportCreation() { - const keyring = await this.getKeyringForDevice(HardwareDeviceNames.ledger); - return await keyring.attemptMakeApp(); + return await this.#withKeyringForDevice( + HardwareDeviceNames.ledger, + async (keyring) => keyring.attemptMakeApp(), + ); } /** @@ -4788,35 +4890,38 @@ export default class MetamaskController extends EventEmitter { * @returns [] accounts */ async connectHardware(deviceName, page, hdPath) { - const keyring = await this.getKeyringForDevice(deviceName, hdPath); - - if (deviceName === HardwareDeviceNames.ledger) { - await this.setLedgerTransportPreference(keyring); - } + return this.#withKeyringForDevice( + { name: deviceName, hdPath }, + async (keyring) => { + if (deviceName === HardwareDeviceNames.ledger) { + await this.setLedgerTransportPreference(keyring); + } - let accounts = []; - switch (page) { - case -1: - accounts = await keyring.getPreviousPage(); - break; - case 1: - accounts = await keyring.getNextPage(); - break; - default: - accounts = await keyring.getFirstPage(); - } + let accounts = []; + switch (page) { + case -1: + accounts = await keyring.getPreviousPage(); + break; + case 1: + accounts = await keyring.getNextPage(); + break; + default: + accounts = await keyring.getFirstPage(); + } - // Merge with existing accounts - // and make sure addresses are not repeated - const oldAccounts = await this.keyringController.getAccounts(); + // Merge with existing accounts + // and make sure addresses are not repeated + const oldAccounts = await this.keyringController.getAccounts(); - const accountsToTrack = [ - ...new Set( - oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), - ), - ]; - this.accountTrackerController.syncWithAddresses(accountsToTrack); - return accounts; + const accountsToTrack = [ + ...new Set( + oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), + ), + ]; + this.accountTrackerController.syncWithAddresses(accountsToTrack); + return accounts; + }, + ); } /** @@ -4827,8 +4932,12 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise} */ async checkHardwareStatus(deviceName, hdPath) { - const keyring = await this.getKeyringForDevice(deviceName, hdPath); - return keyring.isUnlocked(); + return this.#withKeyringForDevice( + { name: deviceName, hdPath }, + async (keyring) => { + return keyring.isUnlocked(); + }, + ); } /** @@ -4839,16 +4948,22 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise} */ async getDeviceNameForMetric(deviceName, hdPath) { - if (deviceName === HardwareDeviceNames.trezor) { - const keyring = await this.getKeyringForDevice(deviceName, hdPath); - const { minorVersion } = keyring.bridge; - // Specific case for OneKey devices, see `ONE_KEY_VIA_TREZOR_MINOR_VERSION` for further details. - if (minorVersion && minorVersion === ONE_KEY_VIA_TREZOR_MINOR_VERSION) { - return HardwareDeviceNames.oneKeyViaTrezor; - } + if (deviceName !== HardwareDeviceNames.trezor) { + return deviceName; } - return deviceName; + return await this.#withKeyringForDevice( + { name: deviceName, hdPath }, + (keyring) => { + const { minorVersion } = keyring.bridge; + // Specific case for OneKey devices, see `ONE_KEY_VIA_TREZOR_MINOR_VERSION` for further details. + if (minorVersion && minorVersion === ONE_KEY_VIA_TREZOR_MINOR_VERSION) { + return HardwareDeviceNames.oneKeyViaTrezor; + } + + return deviceName; + }, + ); } /** @@ -4858,14 +4973,15 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise} */ async forgetDevice(deviceName) { - const keyring = await this.getKeyringForDevice(deviceName); + return this.#withKeyringForDevice({ name: deviceName }, async (keyring) => { + for (const address of keyring.accounts) { + this._onAccountRemoved(address); + } - for (const address of keyring.accounts) { - await this.removeAccount(address); - } + keyring.forgetDevice(); - keyring.forgetDevice(); - return true; + return true; + }); } /** @@ -4903,21 +5019,22 @@ export default class MetamaskController extends EventEmitter { * @returns {'ledger' | 'lattice' | string | undefined} */ async getDeviceModel(address) { - const keyring = await this.keyringController.getKeyringForAccount(address); - switch (keyring.type) { - case KeyringType.trezor: - return keyring.getModel(); - case KeyringType.qr: - return keyring.getName(); - case KeyringType.ledger: - // TODO: get model after ledger keyring exposes method - return HardwareDeviceNames.ledger; - case KeyringType.lattice: - // TODO: get model after lattice keyring exposes method - return HardwareDeviceNames.lattice; - default: - return undefined; - } + return this.keyringController.withKeyring({ address }, async (keyring) => { + switch (keyring.type) { + case KeyringType.trezor: + return keyring.getModel(); + case KeyringType.qr: + return keyring.getName(); + case KeyringType.ledger: + // TODO: get model after ledger keyring exposes method + return HardwareDeviceNames.ledger; + case KeyringType.lattice: + // TODO: get model after lattice keyring exposes method + return HardwareDeviceNames.lattice; + default: + return undefined; + } + }); } /** @@ -4949,16 +5066,25 @@ export default class MetamaskController extends EventEmitter { hdPath, hdPathDescription, ) { - const keyring = await this.getKeyringForDevice(deviceName, hdPath); - - keyring.setAccountToUnlock(index); - const unlockedAccount = - await this.keyringController.addNewAccountForKeyring(keyring); - const label = this.getAccountLabel( - deviceName === HardwareDeviceNames.qr ? keyring.getName() : deviceName, - index, - hdPathDescription, - ); + const { address: unlockedAccount, label } = + await this.#withKeyringForDevice( + { name: deviceName, hdPath }, + async (keyring) => { + keyring.setAccountToUnlock(index); + const [address] = await keyring.addAccounts(1); + return { + address: normalize(address), + label: this.getAccountLabel( + deviceName === HardwareDeviceNames.qr + ? keyring.getName() + : deviceName, + index, + hdPathDescription, + ), + }; + }, + ); + // Set the account label to Trezor 1 / Ledger 1 / QR Hardware 1, etc this.preferencesController.setAccountLabel(unlockedAccount, label); // Select the account @@ -5027,67 +5153,169 @@ export default class MetamaskController extends EventEmitter { async resetAccount() { const selectedAddress = this.accountsController.getSelectedAccount().address; - this.txController.wipeTransactions(false, selectedAddress); + + const globalChainId = this.#getGlobalChainId(); + + this.txController.wipeTransactions({ + address: selectedAddress, + chainId: globalChainId, + }); + this.smartTransactionsController.wipeSmartTransactions({ address: selectedAddress, ignoreNetwork: false, }); + this.bridgeStatusController.wipeBridgeStatus({ address: selectedAddress, ignoreNetwork: false, }); + this.networkController.resetConnection(); return selectedAddress; } /** - * Gets the permitted accounts for the specified origin. Returns an empty - * array if no accounts are permitted. + * Checks that all accounts referenced have a matching InternalAccount. Sends + * an error to sentry for any accounts that were expected but are missing from the wallet. + * + * @param {InternalAccount[]} [internalAccounts] - The list of evm accounts the wallet knows about. + * @param {Hex[]} [accounts] - The list of evm accounts addresses that should exist. + */ + captureKeyringTypesWithMissingIdentities( + internalAccounts = [], + accounts = [], + ) { + const accountsMissingIdentities = accounts.filter( + (address) => + !internalAccounts.some( + (account) => account.address.toLowerCase() === address.toLowerCase(), + ), + ); + const keyringTypesWithMissingIdentities = accountsMissingIdentities.map( + (address) => this.keyringController.getAccountKeyringType(address), + ); + + const internalAccountCount = internalAccounts.length; + + const accountTrackerCount = Object.keys( + this.accountTrackerController.state.accounts || {}, + ).length; + + captureException( + new Error( + `Attempt to get permission specifications failed because their were ${accounts.length} accounts, but ${internalAccountCount} identities, and the ${keyringTypesWithMissingIdentities} keyrings included accounts with missing identities. Meanwhile, there are ${accountTrackerCount} accounts in the account tracker.`, + ), + ); + } + + /** + * Sorts a list of evm account addresses by most recently selected by using + * the lastSelected value for the matching InternalAccount object stored in state. + * + * @param {Hex[]} [accounts] - The list of evm accounts addresses to sort. + * @returns {Hex[]} The sorted evm accounts addresses. + */ + sortAccountsByLastSelected(accounts) { + const internalAccounts = this.accountsController.listAccounts(); + + return accounts.sort((firstAddress, secondAddress) => { + const firstAccount = internalAccounts.find( + (internalAccount) => + internalAccount.address.toLowerCase() === firstAddress.toLowerCase(), + ); + + const secondAccount = internalAccounts.find( + (internalAccount) => + internalAccount.address.toLowerCase() === secondAddress.toLowerCase(), + ); + + if (!firstAccount) { + this.captureKeyringTypesWithMissingIdentities( + internalAccounts, + accounts, + ); + throw new Error(`Missing identity for address: "${firstAddress}".`); + } else if (!secondAccount) { + this.captureKeyringTypesWithMissingIdentities( + internalAccounts, + accounts, + ); + throw new Error(`Missing identity for address: "${secondAddress}".`); + } else if ( + firstAccount.metadata.lastSelected === + secondAccount.metadata.lastSelected + ) { + return 0; + } else if (firstAccount.metadata.lastSelected === undefined) { + return 1; + } else if (secondAccount.metadata.lastSelected === undefined) { + return -1; + } + + return ( + secondAccount.metadata.lastSelected - firstAccount.metadata.lastSelected + ); + }); + } + + /** + * Gets the sorted permitted accounts for the specified origin. Returns an empty + * array if no accounts are permitted or the wallet is locked. Returns any permitted + * accounts if the wallet is locked and `ignoreLock` is true. This lock bypass is needed + * for the `eth_requestAccounts` & `wallet_getPermission` handlers both of which + * return permissioned accounts to the dapp when the wallet is locked. * * @param {string} origin - The origin whose exposed accounts to retrieve. - * @param {boolean} [suppressUnauthorizedError] - Suppresses the unauthorized error. + * @param {object} [options] - The options object + * @param {boolean} [options.ignoreLock] - If accounts should be returned even if the wallet is locked. * @returns {Promise} The origin's permitted accounts, or an empty * array. */ - async getPermittedAccounts( - origin, - { suppressUnauthorizedError = true } = {}, - ) { + getPermittedAccounts(origin, { ignoreLock } = {}) { + let caveat; try { - return await this.permissionController.executeRestrictedMethod( + caveat = this.permissionController.getCaveat( origin, - RestrictedMethods.eth_accounts, + Caip25EndowmentPermissionName, + Caip25CaveatType, ); - } catch (error) { - if ( - suppressUnauthorizedError && - error.code === rpcErrorCodes.provider.unauthorized - ) { - return []; + } catch (err) { + if (err instanceof PermissionDoesNotExistError) { + // suppress expected error in case that the origin + // does not have the target permission yet + } else { + throw err; } - throw error; } + + if (!caveat) { + return []; + } + + if (!this.isUnlocked() && !ignoreLock) { + return []; + } + + const ethAccounts = getEthAccounts(caveat.value); + return this.sortAccountsByLastSelected(ethAccounts); } /** * Stops exposing the specified chain ID to all third parties. - * Exposed chain IDs are stored in caveats of the `endowment:permitted-chains` - * permission. This method uses `PermissionController.updatePermissionsByCaveat` - * to remove the specified chain ID from every `endowment:permitted-chains` - * permission. If a permission only included this chain ID, the permission is - * revoked entirely. * * @param {string} targetChainId - The chain ID to stop exposing * to third parties. */ removeAllChainIdPermissions(targetChainId) { this.permissionController.updatePermissionsByCaveat( - CaveatTypes.restrictNetworkSwitching, - (existingChainIds) => - CaveatMutatorFactories[ - CaveatTypes.restrictNetworkSwitching - ].removeChainId(targetChainId, existingChainIds), + Caip25CaveatType, + (existingScopes) => + Caip25CaveatMutators[Caip25CaveatType].removeScope( + existingScopes, + toCaipChainId('eip155', hexToBigInt(targetChainId).toString(10)), + ), ); } @@ -5103,11 +5331,12 @@ export default class MetamaskController extends EventEmitter { */ removeAllAccountPermissions(targetAccount) { this.permissionController.updatePermissionsByCaveat( - CaveatTypes.restrictReturnedAccounts, - (existingAccounts) => - CaveatMutatorFactories[ - CaveatTypes.restrictReturnedAccounts - ].removeAccount(targetAccount, existingAccounts), + Caip25CaveatType, + (existingScopes) => + Caip25CaveatMutators[Caip25CaveatType].removeAccount( + existingScopes, + targetAccount, + ), ); } @@ -5117,20 +5346,8 @@ export default class MetamaskController extends EventEmitter { * @param {string[]} address - A hex address */ async removeAccount(address) { - // Remove all associated permissions - this.removeAllAccountPermissions(address); - - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - this.custodyController.removeAccount(address); - ///: END:ONLY_INCLUDE_IF(build-mmi) - - const keyring = await this.keyringController.getKeyringForAccount(address); - // Remove account from the keyring + this._onAccountRemoved(address); await this.keyringController.removeAccount(address); - const updatedKeyringAccounts = keyring ? await keyring.getAccounts() : {}; - if (updatedKeyringAccounts?.length === 0) { - keyring.destroy?.(); - } return address; } @@ -5150,6 +5367,210 @@ export default class MetamaskController extends EventEmitter { this.preferencesController.setSelectedAddress(importedAccountAddress); } + /** + * Prompts the user with permittedChains approval for given chainId. + * + * @param {string} origin - The origin to request approval for. + * @param {Hex} chainId - The chainId to add incrementally. + */ + async requestApprovalPermittedChainsPermission(origin, chainId) { + const id = nanoid(); + await this.approvalController.addAndShowApprovalRequest({ + id, + origin, + requestData: { + metadata: { + id, + origin, + }, + permissions: { + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: [chainId], + }, + ], + }, + }, + }, + type: MethodNames.RequestPermissions, + }); + } + + /** + * Requests permittedChains permission for the specified origin + * and replaces any existing CAIP-25 permission with a new one. + * Allows for granting without prompting for user approval which + * would be used as part of flows like `wallet_addEthereumChain` + * requests where the addition of the network and the permitting + * of the chain are combined into one approval. + * + * @param {object} options - The options object + * @param {string} options.origin - The origin to request approval for. + * @param {Hex} options.chainId - The chainId to permit. + * @param {boolean} options.autoApprove - If the chain should be granted without prompting for user approval. + */ + async requestPermittedChainsPermission({ origin, chainId, autoApprove }) { + if (isSnapId(origin)) { + throw new Error( + `Cannot request permittedChains permission for Snaps with origin "${origin}"`, + ); + } + + if (!autoApprove) { + await this.requestApprovalPermittedChainsPermission(origin, chainId); + } + + let caveatValue = { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }; + caveatValue = addPermittedEthChainId(caveatValue, chainId); + + this.permissionController.grantPermissions({ + subject: { origin }, + approvedPermissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: caveatValue, + }, + ], + }, + }, + }); + } + + /** + * Requests incremental permittedChains permission for the specified origin. + * and updates the existing CAIP-25 permission. + * Allows for granting without prompting for user approval which + * would be used as part of flows like `wallet_addEthereumChain` + * requests where the addition of the network and the permitting + * of the chain are combined into one approval. + * + * @param {object} options - The options object + * @param {string} options.origin - The origin to request approval for. + * @param {Hex} options.chainId - The chainId to add to the existing permittedChains. + * @param {boolean} options.autoApprove - If the chain should be granted without prompting for user approval. + */ + async requestPermittedChainsPermissionIncremental({ + origin, + chainId, + autoApprove, + }) { + if (isSnapId(origin)) { + throw new Error( + `Cannot request permittedChains permission for Snaps with origin "${origin}"`, + ); + } + + if (!autoApprove) { + await this.requestApprovalPermittedChainsPermission(origin, chainId); + } + + const caip25Caveat = this.permissionController.getCaveat( + origin, + Caip25EndowmentPermissionName, + Caip25CaveatType, + ); + + const caveatValueWithChainsAdded = addPermittedEthChainId( + caip25Caveat.value, + chainId, + ); + + const ethAccounts = getEthAccounts(caip25Caveat.value); + const caveatValueWithAccountsSynced = setEthAccounts( + caveatValueWithChainsAdded, + ethAccounts, + ); + + this.permissionController.updateCaveat( + origin, + Caip25EndowmentPermissionName, + Caip25CaveatType, + caveatValueWithAccountsSynced, + ); + } + + /** + * Requests user approval for the CAIP-25 permission for the specified origin + * and returns a permissions object that must be passed to + * PermissionController.grantPermissions() to complete the permission granting. + * + * @param {string} origin - The origin to request approval for. + * @param requestedPermissions - The legacy permissions to request approval for. + * @returns the approved permissions object that must then be granted by calling the PermissionController. + */ + async requestCaip25Approval(origin, requestedPermissions = {}) { + const permissions = pick(requestedPermissions, [ + RestrictedMethods.eth_accounts, + PermissionNames.permittedChains, + ]); + + if (!permissions[RestrictedMethods.eth_accounts]) { + permissions[RestrictedMethods.eth_accounts] = {}; + } + + if (!permissions[PermissionNames.permittedChains]) { + permissions[PermissionNames.permittedChains] = {}; + } + + if (isSnapId(origin)) { + delete permissions[PermissionNames.permittedChains]; + } + + const id = nanoid(); + const legacyApproval = + await this.approvalController.addAndShowApprovalRequest({ + id, + origin, + requestData: { + metadata: { + id, + origin, + }, + permissions, + }, + type: MethodNames.RequestPermissions, + }); + + const newCaveatValue = { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }; + + const caveatValueWithChains = setPermittedEthChainIds( + newCaveatValue, + isSnapId(origin) ? [] : legacyApproval.approvedChainIds, + ); + + const caveatValueWithAccounts = setEthAccounts( + caveatValueWithChains, + legacyApproval.approvedAccounts, + ); + + return { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: caveatValueWithAccounts, + }, + ], + }, + }; + } + // --------------------------------------------------------------------------- // Identity Management (signature operations) @@ -5163,8 +5584,7 @@ export default class MetamaskController extends EventEmitter { internalAccounts: this.accountsController.listAccounts(), dappRequest, networkClientId: - dappRequest?.networkClientId ?? - this.networkController.state.selectedNetworkClientId, + dappRequest?.networkClientId ?? this.#getGlobalNetworkClientId(), selectedAccount: this.accountsController.getAccountByAddress( transactionParams.from, ), @@ -5172,7 +5592,7 @@ export default class MetamaskController extends EventEmitter { transactionOptions, transactionParams, userOperationController: this.userOperationController, - chainId: getCurrentChainId({ metamask: this.networkController.state }), + chainId: this.#getGlobalChainId(), ppomController: this.ppomController, securityAlertsEnabled: this.preferencesController.state?.securityAlertsEnabled, @@ -5240,16 +5660,13 @@ export default class MetamaskController extends EventEmitter { async estimateGas(estimateGasParams) { return new Promise((resolve, reject) => { - return new EthJSQuery(this.provider).estimateGas( - estimateGasParams, - (err, res) => { - if (err) { - return reject(err); - } - - return resolve(res.toString(16)); - }, - ); + this.provider + .request({ + method: 'eth_estimateGas', + params: [estimateGasParams], + }) + .then((result) => resolve(result.toString(16))) + .catch((err) => reject(err)); }); } @@ -5304,14 +5721,6 @@ export default class MetamaskController extends EventEmitter { this.sendUpdate(); } - //============================================================================= - // REQUEST QUEUE - //============================================================================= - - setUseRequestQueue(value) { - this.preferencesController.setUseRequestQueue(value); - } - //============================================================================= // SETUP //============================================================================= @@ -5645,11 +6054,18 @@ export default class MetamaskController extends EventEmitter { tabId = sender.tab.id; } + let mainFrameOrigin = origin; + if (sender.tab && sender.tab.url) { + // If sender origin is an iframe, then get the top-level frame's origin + mainFrameOrigin = new URL(sender.tab.url).origin; + } + const engine = this.setupProviderEngineEip1193({ origin, sender, subjectType, tabId, + mainFrameOrigin, }); const dupeReqFilterStream = createDupeReqFilterStream(); @@ -5770,13 +6186,25 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender object. * @param {string} options.subjectType - The type of the sender subject. * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab + * @param {mainFrameOrigin} [options.mainFrameOrigin] - The origin of the main frame if the sender is an iframe */ - setupProviderEngineEip1193({ origin, subjectType, sender, tabId }) { + setupProviderEngineEip1193({ + origin, + subjectType, + sender, + tabId, + mainFrameOrigin, + }) { const engine = new JsonRpcEngine(); // Append origin to each request engine.push(createOriginMiddleware({ origin })); + // Append mainFrameOrigin to each request if present + if (mainFrameOrigin) { + engine.push(createMainFrameOriginMiddleware({ mainFrameOrigin })); + } + // Append selectedNetworkClientId to each request engine.push(createSelectedNetworkMiddleware(this.controllerMessenger)); @@ -5785,12 +6213,12 @@ export default class MetamaskController extends EventEmitter { enqueueRequest: this.queuedRequestController.enqueueRequest.bind( this.queuedRequestController, ), - useRequestQueue: this.preferencesController.getUseRequestQueue.bind( - this.preferencesController, - ), shouldEnqueueRequest: (request) => { return methodsThatShouldBeEnqueued.includes(request.method); }, + // This will be removed once we can actually remove useRequestQueue state + // i.e. unrevert https://github.com/MetaMask/core/pull/5065 + useRequestQueue: () => true, }); engine.push(requestQueueMiddleware); @@ -5839,8 +6267,6 @@ export default class MetamaskController extends EventEmitter { createRPCMethodTrackingMiddleware({ getAccountType: this.getAccountType.bind(this), getDeviceModel: this.getDeviceModel.bind(this), - isConfirmationRedesignEnabled: - this.isConfirmationRedesignEnabled.bind(this), isRedesignedConfirmationsDeveloperEnabled: this.isConfirmationRedesignDeveloperEnabled.bind(this), snapAndHardwareMessenger: this.controllerMessenger.getRestricted({ @@ -5861,7 +6287,7 @@ export default class MetamaskController extends EventEmitter { // Legacy RPC methods that need to be implemented _ahead of_ the permission // middleware. engine.push( - createLegacyMethodMiddleware({ + createEthAccountsMethodMiddleware({ getAccounts: this.getPermittedAccounts.bind(this, origin), }), ); @@ -5897,9 +6323,7 @@ export default class MetamaskController extends EventEmitter { // Unrestricted/permissionless RPC method implementations. // They must nevertheless be placed _behind_ the permission middleware. engine.push( - createMethodMiddleware({ - origin, - + createEip1193MethodMiddleware({ subjectType, // Miscellaneous @@ -5922,63 +6346,34 @@ export default class MetamaskController extends EventEmitter { ), // Permission-related getAccounts: this.getPermittedAccounts.bind(this, origin), - getPermissionsForOrigin: this.permissionController.getPermissions.bind( - this.permissionController, + requestCaip25ApprovalForOrigin: this.requestCaip25Approval.bind( + this, origin, ), - hasPermission: this.permissionController.hasPermission.bind( + grantPermissionsForOrigin: (approvedPermissions) => { + return this.permissionController.grantPermissions({ + subject: { origin }, + approvedPermissions, + }); + }, + getPermissionsForOrigin: this.permissionController.getPermissions.bind( this.permissionController, origin, ), - requestAccountsPermission: - this.permissionController.requestPermissions.bind( - this.permissionController, - { origin }, - { - eth_accounts: {}, - ...(!isSnapId(origin) && { - [PermissionNames.permittedChains]: {}, - }), - }, - ), - requestPermittedChainsPermission: (chainIds) => - this.permissionController.requestPermissionsIncremental( - { origin }, - { - [PermissionNames.permittedChains]: { - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]( - chainIds, - ), - ], - }, - }, - ), - grantPermittedChainsPermissionIncremental: (chainIds) => - this.permissionController.grantPermissionsIncremental({ - subject: { origin }, - approvedPermissions: { - [PermissionNames.permittedChains]: { - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]( - chainIds, - ), - ], - }, - }, + requestPermittedChainsPermissionForOrigin: (options) => + this.requestPermittedChainsPermission({ + ...options, + origin, + }), + requestPermittedChainsPermissionIncrementalForOrigin: (options) => + this.requestPermittedChainsPermissionIncremental({ + ...options, + origin, }), requestPermissionsForOrigin: (requestedPermissions) => this.permissionController.requestPermissions( { origin }, - { - ...(requestedPermissions[PermissionNames.eth_accounts] && { - [PermissionNames.permittedChains]: {}, - }), - ...(requestedPermissions[PermissionNames.permittedChains] && { - [PermissionNames.eth_accounts]: {}, - }), - ...requestedPermissions, - }, + requestedPermissions, ), revokePermissionsForOrigin: (permissionKeys) => { try { @@ -6015,12 +6410,12 @@ export default class MetamaskController extends EventEmitter { // network configuration-related setActiveNetwork: async (networkClientId) => { await this.networkController.setActiveNetwork(networkClientId); - // if the origin has the eth_accounts permission + // if the origin has the CAIP-25 permission // we set per dapp network selection state if ( this.permissionController.hasPermission( origin, - PermissionNames.eth_accounts, + Caip25EndowmentPermissionName, ) ) { this.selectedNetworkController.setNetworkClientIdForDomain( @@ -6057,6 +6452,10 @@ export default class MetamaskController extends EventEmitter { this.alertController.setWeb3ShimUsageRecorded.bind( this.alertController, ), + updateCaveat: this.permissionController.updateCaveat.bind( + this.permissionController, + origin, + ), ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) handleMmiAuthenticate: @@ -6082,6 +6481,11 @@ export default class MetamaskController extends EventEmitter { engine.push( createSnapsMethodMiddleware(subjectType === SubjectType.Snap, { + clearSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:clearSnapState', + origin, + ), getUnlockPromise: this.appStateController.getUnlockPromise.bind( this.appStateController, ), @@ -6104,6 +6508,16 @@ export default class MetamaskController extends EventEmitter { 'SnapController:getFile', origin, ), + getSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:getSnapState', + origin, + ), + updateSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:updateSnapState', + origin, + ), installSnaps: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:install', @@ -6165,11 +6579,26 @@ export default class MetamaskController extends EventEmitter { currency: fiatCurrency, }; }, - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) hasPermission: this.permissionController.hasPermission.bind( this.permissionController, origin, ), + scheduleBackgroundEvent: (event) => + this.controllerMessenger.call( + 'CronjobController:scheduleBackgroundEvent', + { ...event, snapId: origin }, + ), + cancelBackgroundEvent: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'CronjobController:cancelBackgroundEvent', + origin, + ), + getBackgroundEvents: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'CronjobController:getBackgroundEvents', + origin, + ), + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) handleSnapRpcRequest: (args) => this.handleSnapRequest({ ...args, origin }), getAllowedKeyringMethods: keyringSnapPermissionsBuilder( @@ -6402,12 +6831,12 @@ export default class MetamaskController extends EventEmitter { * account(s) are currently accessible, if any. */ _onUnlock() { - this.notifyAllConnections(async (origin) => { + this.notifyAllConnections((origin) => { return { method: NOTIFICATION_NAMES.unlockStateChanged, params: { isUnlocked: true, - accounts: await this.getPermittedAccounts(origin), + accounts: this.getPermittedAccounts(origin), }, }; }); @@ -6451,6 +6880,20 @@ export default class MetamaskController extends EventEmitter { this._notifyChainChange(); } + /** + * Execute side effects of a removed account. + * + * @param {string} address - The address of the account to remove. + */ + _onAccountRemoved(address) { + // Remove all associated permissions + this.removeAllAccountPermissions(address); + + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) + this.custodyController.removeAccount(address); + ///: END:ONLY_INCLUDE_IF(build-mmi) + } + // misc /** @@ -6480,16 +6923,6 @@ export default class MetamaskController extends EventEmitter { }); } - isConfirmationRedesignEnabled() { - return this.preferencesController.state.preferences - .redesignedConfirmationsEnabled; - } - - isTransactionsRedesignEnabled() { - return this.preferencesController.state.preferences - .redesignedTransactionsEnabled; - } - isConfirmationRedesignDeveloperEnabled() { return this.preferencesController.state.preferences .isRedesignedConfirmationsDeveloperEnabled; @@ -6516,13 +6949,13 @@ export default class MetamaskController extends EventEmitter { * Returns the nonce that will be associated with a transaction once approved * * @param {string} address - The hex string address for the transaction - * @param networkClientId - The optional networkClientId to get the nonce lock with + * @param networkClientId - The networkClientId to get the nonce lock with * @returns {Promise} */ async getPendingNonce(address, networkClientId) { const { nonceDetails, releaseLock } = await this.txController.getNonceLock( address, - process.env.TRANSACTION_MULTICHAIN ? networkClientId : undefined, + networkClientId, ); const pendingNonce = nonceDetails.params.highestSuggested; @@ -6535,13 +6968,13 @@ export default class MetamaskController extends EventEmitter { * Returns the next nonce according to the nonce-tracker * * @param {string} address - The hex string address for the transaction - * @param networkClientId - The optional networkClientId to get the nonce lock with + * @param networkClientId - The networkClientId to get the nonce lock with * @returns {Promise} */ async getNextNonce(address, networkClientId) { const nonceLock = await this.txController.getNonceLock( address, - process.env.TRANSACTION_MULTICHAIN ? networkClientId : undefined, + networkClientId, ); nonceLock.releaseLock(); return nonceLock.nextNonce; @@ -6679,10 +7112,6 @@ export default class MetamaskController extends EventEmitter { txHash, ); }, - getRedesignedConfirmationsEnabled: - this.isConfirmationRedesignEnabled.bind(this), - getRedesignedTransactionsEnabled: - this.isTransactionsRedesignEnabled.bind(this), getMethodData: (data) => { if (!data) { return null; @@ -6740,16 +7169,15 @@ export default class MetamaskController extends EventEmitter { /** * Sets the Ledger Live preference to use for Ledger hardware wallet support * - * @param _keyring + * @param keyring * @deprecated This method is deprecated and will be removed in the future. * Only webhid connections are supported in chrome and u2f in firefox. */ - async setLedgerTransportPreference(_keyring) { + async setLedgerTransportPreference(keyring) { const transportType = window.navigator.hid ? LedgerTransportTypes.webhid : LedgerTransportTypes.u2f; - const keyring = - _keyring || (await this.getKeyringForDevice(HardwareDeviceNames.ledger)); + if (keyring?.updateTransportMethod) { return keyring.updateTransportMethod(transportType).catch((e) => { throw e; @@ -6808,7 +7236,7 @@ export default class MetamaskController extends EventEmitter { /** * A method that is called by the background when a particular environment type is closed (fullscreen, popup, notification). - * Currently used to stop polling in the gasFeeController for only that environement type + * Currently used to stop polling controllers for only that environement type * * @param environmentType */ @@ -6816,10 +7244,17 @@ export default class MetamaskController extends EventEmitter { const appStatePollingTokenType = POLLING_TOKEN_ENVIRONMENT_TYPES[environmentType]; const pollingTokensToDisconnect = - this.appStateController.store.getState()[appStatePollingTokenType]; + this.appStateController.state[appStatePollingTokenType]; pollingTokensToDisconnect.forEach((pollingToken) => { + // We don't know which controller the token is associated with, so try them all. + // Consider storing the tokens per controller in state instead. this.gasFeeController.stopPollingByPollingToken(pollingToken); this.currencyRateController.stopPollingByPollingToken(pollingToken); + this.tokenRatesController.stopPollingByPollingToken(pollingToken); + this.tokenDetectionController.stopPollingByPollingToken(pollingToken); + this.tokenListController.stopPollingByPollingToken(pollingToken); + this.tokenBalancesController.stopPollingByPollingToken(pollingToken); + this.accountTrackerController.stopPollingByPollingToken(pollingToken); this.appStateController.removePollingToken( pollingToken, appStatePollingTokenType, @@ -6966,6 +7401,19 @@ export default class MetamaskController extends EventEmitter { } }; + rejectAllPendingApprovals() { + const deleteInterface = (id) => + this.controllerMessenger.call( + 'SnapInterfaceController:deleteInterface', + id, + ); + + rejectAllApprovals({ + approvalController: this.approvalController, + deleteInterface, + }); + } + async _onAccountChange(newAddress) { const permittedAccountsMap = getPermittedAccountsByOrigin( this.permissionController.state, @@ -6977,10 +7425,12 @@ export default class MetamaskController extends EventEmitter { } } - await this.txController.updateIncomingTransactions(); + await this.txController.updateIncomingTransactions([ + this.#getGlobalChainId(), + ]); } - async _notifyAccountsChange(origin, newAccounts) { + _notifyAccountsChange(origin, newAccounts) { if (this.isUnlocked()) { this.notifyConnections(origin, { method: NOTIFICATION_NAMES.accountsChanged, @@ -6993,7 +7443,7 @@ export default class MetamaskController extends EventEmitter { newAccounts : // If the length is 2 or greater, we have to execute // `eth_accounts` vi this method. - await this.getPermittedAccounts(origin), + this.getPermittedAccounts(origin), }); } @@ -7001,31 +7451,17 @@ export default class MetamaskController extends EventEmitter { } async _notifyChainChange() { - if (this.preferencesController.getUseRequestQueue()) { - this.notifyAllConnections(async (origin) => ({ - method: NOTIFICATION_NAMES.chainChanged, - params: await this.getProviderNetworkState(origin), - })); - } else { - this.notifyAllConnections({ - method: NOTIFICATION_NAMES.chainChanged, - params: await this.getProviderNetworkState(), - }); - } + this.notifyAllConnections(async (origin) => ({ + method: NOTIFICATION_NAMES.chainChanged, + params: await this.getProviderNetworkState(origin), + })); } async _notifyChainChangeForConnection(connection, origin) { - if (this.preferencesController.getUseRequestQueue()) { - this.notifyConnection(connection, { - method: NOTIFICATION_NAMES.chainChanged, - params: await this.getProviderNetworkState(origin), - }); - } else { - this.notifyConnection(connection, { - method: NOTIFICATION_NAMES.chainChanged, - params: await this.getProviderNetworkState(), - }); - } + this.notifyConnection(connection, { + method: NOTIFICATION_NAMES.chainChanged, + params: await this.getProviderNetworkState(origin), + }); } async _onFinishedTransaction(transactionMeta) { @@ -7329,6 +7765,82 @@ export default class MetamaskController extends EventEmitter { }; } + _getConfigForRemoteFeatureFlagRequest() { + const distribution = + buildTypeMappingForRemoteFeatureFlag[process.env.METAMASK_BUILD_TYPE] || + DistributionType.Main; + const environment = + environmentMappingForRemoteFeatureFlag[ + process.env.METAMASK_ENVIRONMENT + ] || EnvironmentType.Development; + return { distribution, environment }; + } + + /** + * Select a hardware wallet device and execute a + * callback with the keyring for that device. + * + * Note that KeyringController state is not updated before + * the end of the callback execution, and calls to KeyringController + * methods within the callback can lead to deadlocks. + * + * @param {object} options - The options for the device + * @param {string} options.name - The device name to select + * @param {string} options.hdPath - An optional hd path to be set on the device + * keyring + * @param {*} callback - The callback to execute with the keyring + * @returns {*} The result of the callback + */ + async #withKeyringForDevice(options, callback) { + const keyringOverrides = this.opts.overrides?.keyrings; + let keyringType = null; + switch (options.name) { + case HardwareDeviceNames.trezor: + keyringType = keyringOverrides?.trezor?.type || TrezorKeyring.type; + break; + case HardwareDeviceNames.ledger: + keyringType = keyringOverrides?.ledger?.type || LedgerKeyring.type; + break; + case HardwareDeviceNames.qr: + keyringType = QRHardwareKeyring.type; + break; + case HardwareDeviceNames.lattice: + keyringType = keyringOverrides?.lattice?.type || LatticeKeyring.type; + break; + default: + throw new Error( + 'MetamaskController:#withKeyringForDevice - Unknown device', + ); + } + + return this.keyringController.withKeyring( + { type: keyringType }, + async (keyring) => { + if (options.hdPath && keyring.setHdPath) { + keyring.setHdPath(options.hdPath); + } + + if (options.name === HardwareDeviceNames.lattice) { + keyring.appName = 'MetaMask'; + } + + if (options.name === HardwareDeviceNames.trezor) { + const model = keyring.getModel(); + this.appStateController.setTrezorModel(model); + } + + keyring.network = getProviderConfig({ + metamask: this.networkController.state, + }).type; + + return await callback(keyring); + }, + { + createIfMissing: true, + }, + ); + } + #checkTokenListPolling(currentState, previousState) { const previousEnabled = this.#isTokenListPollingRequired(previousState); const newEnabled = this.#isTokenListPollingRequired(currentState); @@ -7348,4 +7860,28 @@ export default class MetamaskController extends EventEmitter { return useTokenDetection || petnamesEnabled || useTransactionSimulations; } + + /** + * @deprecated Avoid new references to the global network. + * Will be removed once multi-chain support is fully implemented. + * @returns {string} The chain ID of the currently selected network. + */ + #getGlobalChainId() { + const globalNetworkClientId = this.#getGlobalNetworkClientId(); + + const globalNetworkClient = this.networkController.getNetworkClientById( + globalNetworkClientId, + ); + + return globalNetworkClient.configuration.chainId; + } + + /** + * @deprecated Avoid new references to the global network. + * Will be removed once multi-chain support is fully implemented. + * @returns {string} The network client ID of the currently selected network client. + */ + #getGlobalNetworkClientId() { + return this.networkController.state.selectedNetworkClientId; + } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 0e08a0ac27c0..fc2309b2646c 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -4,7 +4,6 @@ import { cloneDeep } from 'lodash'; import nock from 'nock'; import { obj as createThroughStream } from 'through2'; -import EthQuery from '@metamask/eth-query'; import { wordlist as englishWordlist } from '@metamask/scure-bip39/dist/wordlists/english'; import { ListNames, @@ -21,7 +20,10 @@ import { } from '@metamask/keyring-api'; import { ControllerMessenger } from '@metamask/base-controller'; import { LoggingController, LogType } from '@metamask/logging-controller'; -import { TransactionController } from '@metamask/transaction-controller'; +import { + CHAIN_IDS, + TransactionController, +} from '@metamask/transaction-controller'; import { RatesController, TokenListController, @@ -29,6 +31,11 @@ import { import ObjectMultiplex from '@metamask/object-multiplex'; import { TrezorKeyring } from '@metamask/eth-trezor-keyring'; import { LedgerKeyring } from '@metamask/eth-ledger-bridge-keyring'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; +import { PermissionDoesNotExistError } from '@metamask/permission-controller'; import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; import { KeyringType } from '../../shared/constants/keyring'; @@ -39,6 +46,12 @@ import { flushPromises } from '../../test/lib/timer-helpers'; import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods'; import { createMockInternalAccount } from '../../test/jest/mocks'; import { mockNetworkState } from '../../test/stub/networks'; +import { ENVIRONMENT } from '../../development/build/constants'; +import { SECOND } from '../../shared/constants/time'; +import { + CaveatTypes, + RestrictedMethods, +} from '../../shared/constants/permissions'; import { BalancesController as MultichainBalancesController, BTC_BALANCES_UPDATE_TIME as MULTICHAIN_BALANCES_UPDATE_TIME, @@ -49,6 +62,7 @@ import { METAMASK_COOKIE_HANDLER } from './constants/stream'; import MetaMaskController, { ONE_KEY_VIA_TREZOR_MINOR_VERSION, } from './metamask-controller'; +import { PermissionNames } from './controllers/permissions'; const { Ganache } = require('../../test/e2e/seeder/ganache'); @@ -105,10 +119,13 @@ const createLoggerMiddlewareMock = () => (req, res, next) => { jest.mock('./lib/createLoggerMiddleware', () => createLoggerMiddlewareMock); const rpcMethodMiddlewareMock = { - createMethodMiddleware: () => (_req, _res, next, _end) => { + createEip1193MethodMiddleware: () => (_req, _res, next, _end) => { + next(); + }, + createEthAccountsMethodMiddleware: () => (_req, _res, next, _end) => { next(); }, - createLegacyMethodMiddleware: () => (_req, _res, next, _end) => { + createMultichainMethodMiddleware: () => (_req, _res, next, _end) => { next(); }, createUnsupportedMethodMiddleware: () => (_req, _res, next, _end) => { @@ -206,8 +223,6 @@ const TEST_INTERNAL_ACCOUNT = { type: EthAccountType.Eoa, }; -const NOTIFICATION_ID = 'NHL8f2eSSTn9TKBamRLiU'; - const ALT_MAINNET_RPC_URL = 'http://localhost:8545'; const POLYGON_RPC_URL = 'https://polygon.llamarpc.com'; @@ -248,17 +263,6 @@ const firstTimeState = { }, ), }, - NotificationController: { - notifications: { - [NOTIFICATION_ID]: { - id: NOTIFICATION_ID, - origin: 'local:http://localhost:8086/', - createdDate: 1652967897732, - readDate: null, - message: 'Hello, http://localhost:8086!', - }, - }, - }, PhishingController: { phishingLists: [ { @@ -788,23 +792,1276 @@ describe('MetaMaskController', () => { it('should ask the network for a balance when not known by accountTrackerController', async () => { const accounts = {}; const balance = '0x14ced5122ce0a000'; - const ethQuery = new EthQuery(); - jest.spyOn(ethQuery, 'getBalance').mockImplementation((_, callback) => { - callback(undefined, balance); + const { provider } = createTestProviderTools({ + scaffold: { + eth_getBalance: balance, + }, + }); + + jest + .spyOn(metamaskController.accountTrackerController, 'state', 'get') + .mockReturnValue({ + accounts, + }); + + const gotten = await metamaskController.getBalance( + TEST_ADDRESS, + provider, + ); + + expect(balance).toStrictEqual(gotten); + }); + }); + + describe('#getPermittedAccounts', () => { + it('gets the CAIP-25 caveat value for the origin', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue(); + + metamaskController.getPermittedAccounts('test.com'); + + expect( + metamaskController.permissionController.getCaveat, + ).toHaveBeenCalledWith( + 'test.com', + Caip25EndowmentPermissionName, + Caip25CaveatType, + ); + }); + + it('returns empty array if there is no CAIP-25 permission for the origin', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockImplementation(() => { + throw new PermissionDoesNotExistError(); + }); + + expect( + metamaskController.getPermittedAccounts('test.com'), + ).toStrictEqual([]); + }); + + it('throws an error if getCaveat fails unexpectedly', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockImplementation(() => { + throw new Error('unexpected getCaveat error'); + }); + + expect(() => { + metamaskController.getPermittedAccounts('test.com'); + }).toThrow(new Error(`unexpected getCaveat error`)); + }); + + describe('the wallet is locked', () => { + beforeEach(() => { + jest.spyOn(metamaskController, 'isUnlocked').mockReturnValue(false); + }); + + it('returns empty array if there is a CAIP-25 permission for the origin and ignoreLock is false', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdead', 'eip155:1:0xbeef'], + }, + }, + }, + }); + + expect( + metamaskController.getPermittedAccounts('test.com', { + ignoreLock: false, + }), + ).toStrictEqual([]); + }); + + it('returns accounts if there is a CAIP-25 permission for the origin and ignoreLock is true', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdead', 'eip155:1:0xbeef'], + }, + }, + }, + }); + jest + .spyOn(metamaskController, 'sortAccountsByLastSelected') + .mockReturnValue(['not_empty']); + + expect( + metamaskController.getPermittedAccounts('test.com', { + ignoreLock: true, + }), + ).toStrictEqual(['not_empty']); + }); + }); + + describe('the wallet is unlocked', () => { + beforeEach(() => { + jest.spyOn(metamaskController, 'isUnlocked').mockReturnValue(true); + }); + + it('sorts the eth accounts from the CAIP-25 permission', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdead', 'eip155:1:0xbeef'], + }, + }, + }, + }); + jest + .spyOn(metamaskController, 'sortAccountsByLastSelected') + .mockReturnValue([]); + + metamaskController.getPermittedAccounts('test.com'); + expect( + metamaskController.sortAccountsByLastSelected, + ).toHaveBeenCalledWith(['0xdead', '0xbeef']); + }); + + it('returns the sorted eth accounts from the CAIP-25 permission', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdead', 'eip155:1:0xbeef'], + }, + }, + }, + }); + jest + .spyOn(metamaskController, 'sortAccountsByLastSelected') + .mockReturnValue(['0xbeef', '0xdead']); + + expect( + metamaskController.getPermittedAccounts('test.com'), + ).toStrictEqual(['0xbeef', '0xdead']); + }); + }); + }); + + describe('#requestCaip25Approval', () => { + it('requests approval with well formed id and origin', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('test.com', {}); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + requestData: expect.objectContaining({ + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + }, + }), + type: 'wallet_requestPermissions', + }), + ); + + const [params] = + metamaskController.approvalController.addAndShowApprovalRequest.mock + .calls[0]; + expect(params.id).toStrictEqual(params.requestData.metadata.id); + }); + + it('requests approval from the ApprovalController for eth_accounts and permittedChains when only eth_accounts is specified in params and origin is not snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('test.com', { + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + }, + permissions: { + [RestrictedMethods.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + [PermissionNames.permittedChains]: {}, + }, + }, + type: 'wallet_requestPermissions', + }), + ); + }); + + it('requests approval from the ApprovalController for eth_accounts and permittedChains when only permittedChains is specified in params and origin is not snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('test.com', { + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + }, + permissions: { + [RestrictedMethods.eth_accounts]: {}, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }, + }, + type: 'wallet_requestPermissions', + }), + ); + }); + + it('requests approval from the ApprovalController for eth_accounts and permittedChains when both are specified in params and origin is not snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('test.com', { + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + }, + permissions: { + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }, + }, + type: 'wallet_requestPermissions', + }), + ); + }); + + it('requests approval from the ApprovalController for only eth_accounts when only eth_accounts is specified in params and origin is snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('npm:snap', { + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'npm:snap', + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'npm:snap', + }, + permissions: { + [RestrictedMethods.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + }, + }, + type: 'wallet_requestPermissions', + }), + ); + }); + + it('requests approval from the ApprovalController for only eth_accounts when only permittedChains is specified in params and origin is snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('npm:snap', { + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, + }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'npm:snap', + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'npm:snap', + }, + permissions: { + [PermissionNames.eth_accounts]: {}, + }, + }, + type: 'wallet_requestPermissions', + }), + ); + }); + + it('requests approval from the ApprovalController for only eth_accounts when both eth_accounts and permittedChains are specified in params and origin is snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedAccounts: [], + approvedChainIds: [], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + await metamaskController.requestCaip25Approval('npm:snap', { + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x64'], + }, + ], + }, }); + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'npm:snap', + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'npm:snap', + }, + permissions: { + [PermissionNames.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['foo'], + }, + ], + }, + }, + }, + type: 'wallet_requestPermissions', + }), + ); + }); + + it('throws an error if the eth_accounts and permittedChains approval is rejected', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockRejectedValue(new Error('approval rejected')); + + await expect(() => + metamaskController.requestCaip25Approval('test.com', { + eth_accounts: {}, + }), + ).rejects.toThrow(new Error('approval rejected')); + }); + + it('returns the CAIP-25 approval with eth accounts, chainIds, and isMultichainOrigin: false if origin is not snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedChainIds: ['0x1', '0x5'], + approvedAccounts: ['0xdeadbeef'], + }); + + const result = await metamaskController.requestCaip25Approval( + 'test.com', + {}, + ); + + expect(result).toStrictEqual({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:0xdeadbeef'], + }, + 'eip155:1': { + accounts: ['eip155:1:0xdeadbeef'], + }, + 'eip155:5': { + accounts: ['eip155:5:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }); + }); + + it('returns the CAIP-25 approval with approved accounts for the `wallet:eip155` scope (and no approved chainIds) with isMultichainOrigin: false if origin is snapId', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + approvedChainIds: ['0x1', '0x5'], + approvedAccounts: ['0xdeadbeef'], + }); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue({ + [Caip25EndowmentPermissionName]: { + foo: 'bar', + }, + }); + + const result = await metamaskController.requestCaip25Approval( + 'npm:snap', + {}, + ); + + expect(result).toStrictEqual({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }); + }); + }); + + describe('requestApprovalPermittedChainsPermission', () => { + it('requests approval with well formed id and origin', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue(); + + await metamaskController.requestApprovalPermittedChainsPermission( + 'test.com', + '0x1', + ); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + requestData: expect.objectContaining({ + metadata: { + id: expect.stringMatching(/.{21}/u), + origin: 'test.com', + }, + permissions: { + [PermissionNames.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x1'], + }, + ], + }, + }, + }), + type: 'wallet_requestPermissions', + }), + ); + + const [params] = + metamaskController.approvalController.addAndShowApprovalRequest.mock + .calls[0]; + expect(params.id).toStrictEqual(params.requestData.metadata.id); + }); + + it('throws if the approval is rejected', async () => { + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockRejectedValue(new Error('approval rejected')); + + await expect(() => + metamaskController.requestApprovalPermittedChainsPermission( + 'test.com', + '0x1', + ), + ).rejects.toThrow(new Error('approval rejected')); + }); + }); + + describe('requestPermittedChainsPermission', () => { + it('throws if the origin is snapId', async () => { + await expect(() => + metamaskController.requestPermittedChainsPermission({ + origin: 'npm:snap', + chainId: '0x1', + }), + ).rejects.toThrow( + new Error( + 'Cannot request permittedChains permission for Snaps with origin "npm:snap"', + ), + ); + }); + + it('requests approval for permittedChains permissions from the ApprovalController if autoApprove: false', async () => { + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermission({ + origin: 'test.com', + chainId: '0x1', + autoApprove: false, + }); + + expect( + metamaskController.requestApprovalPermittedChainsPermission, + ).toHaveBeenCalledWith('test.com', '0x1'); + }); + + it('throws if permittedChains approval is rejected', async () => { + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockRejectedValue(new Error('approval rejected')); + + await expect(() => + metamaskController.requestPermittedChainsPermission({ + origin: 'test.com', + chainId: '0x1', + autoApprove: false, + }), + ).rejects.toThrow(new Error('approval rejected')); + }); + + it('does not request approval for permittedChains permissions from the ApprovalController if autoApprove: true', async () => { + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermission({ + origin: 'test.com', + chainId: '0x1', + autoApprove: true, + }); + + expect( + metamaskController.requestApprovalPermittedChainsPermission, + ).not.toHaveBeenCalled(); + }); + + it('grants the CAIP-25 permission', async () => { + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermission({ + origin: 'test.com', + chainId: '0x1', + }); + + expect( + metamaskController.permissionController.grantPermissions, + ).toHaveBeenCalledWith({ + subject: { origin: 'test.com' }, + approvedPermissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }); + }); + + it('throws if CAIP-25 permission grant fails', async () => { + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'grantPermissions') + .mockImplementation(() => { + throw new Error('grant failed'); + }); + + await expect(() => + metamaskController.requestPermittedChainsPermission({ + origin: 'test.com', + chainId: '0x1', + }), + ).rejects.toThrow(new Error('grant failed')); + }); + }); + + describe('requestPermittedChainsPermissionIncremental', () => { + it('throws if the origin is snapId', async () => { + await expect(() => + metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'npm:snap', + chainId: '0x1', + }), + ).rejects.toThrow( + new Error( + 'Cannot request permittedChains permission for Snaps with origin "npm:snap"', + ), + ); + }); + + it('gets the CAIP-25 caveat', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'updateCaveat') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + }); + + expect( + metamaskController.permissionController.getCaveat, + ).toHaveBeenCalledWith( + 'test.com', + Caip25EndowmentPermissionName, + Caip25CaveatType, + ); + }); + + it('throws if getting the caveat fails', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockImplementation(() => { + throw new Error('no caveat found'); + }); + + await expect(() => + metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + autoApprove: false, + }), + ).rejects.toThrow(new Error('no caveat found')); + }); + + it('requests permittedChains approval if autoApprove: false', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'updateCaveat') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + autoApprove: false, + }); + + expect( + metamaskController.requestApprovalPermittedChainsPermission, + ).toHaveBeenCalledWith('test.com', '0x1'); + }); + + it('throws if permittedChains approval is rejected', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockRejectedValue(new Error('approval rejected')); + + await expect(() => + metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + autoApprove: false, + }), + ).rejects.toThrow(new Error('approval rejected')); + }); + + it('does not request permittedChains approval if autoApprove: true', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'updateCaveat') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + autoApprove: true, + }); + + expect( + metamaskController.requestApprovalPermittedChainsPermission, + ).not.toHaveBeenCalled(); + }); + + it('updates the CAIP-25 permission', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:5': { + accounts: ['eip155:5:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'updateCaveat') + .mockReturnValue(); + + await metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + }); + + expect( + metamaskController.permissionController.updateCaveat, + ).toHaveBeenCalledWith( + 'test.com', + Caip25EndowmentPermissionName, + Caip25CaveatType, + { + requiredScopes: {}, + optionalScopes: { + 'eip155:5': { + accounts: ['eip155:5:0xdeadbeef'], + }, + 'eip155:1': { + accounts: ['eip155:1:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + ); + }); + + it('throws if CAIP-25 permission update fails', async () => { + jest + .spyOn(metamaskController.permissionController, 'getCaveat') + .mockReturnValue({ + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }); + jest + .spyOn(metamaskController, 'requestApprovalPermittedChainsPermission') + .mockResolvedValue(); + jest + .spyOn(metamaskController.permissionController, 'updateCaveat') + .mockImplementation(() => { + throw new Error('grant failed'); + }); + + await expect(() => + metamaskController.requestPermittedChainsPermissionIncremental({ + origin: 'test.com', + chainId: '0x1', + }), + ).rejects.toThrow(new Error('grant failed')); + }); + }); + + describe('#sortAccountsByLastSelected', () => { + it('returns the keyring accounts in lastSelected order', () => { + jest + .spyOn(metamaskController.accountsController, 'listAccounts') + .mockReturnValueOnce([ + { + address: '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + id: '21066553-d8c8-4cdc-af33-efc921cd3ca9', + metadata: { + name: 'Test Account', + lastSelected: 1, + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + { + address: '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + id: '0bd7348e-bdfe-4f67-875c-de831a583857', + metadata: { + name: 'Test Account', + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + { + address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', + metadata: { + name: 'Test Account', + keyring: { + type: 'HD Key Tree', + }, + lastSelected: 3, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + { + address: '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', + id: '0bd7348e-bdfe-4f67-875c-de831a583857', + metadata: { + name: 'Test Account', + lastSelected: 3, + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + ]); + jest + .spyOn(metamaskController, 'captureKeyringTypesWithMissingIdentities') + .mockImplementation(() => { + // noop + }); + + expect( + metamaskController.sortAccountsByLastSelected([ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', + ]), + ).toStrictEqual([ + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + ]); + }); + + it('throws if a keyring account is missing an address (case 1)', () => { + const internalAccounts = [ + { + address: '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + id: '0bd7348e-bdfe-4f67-875c-de831a583857', + metadata: { + name: 'Test Account', + lastSelected: 2, + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + { + address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', + metadata: { + name: 'Test Account', + lastSelected: 3, + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + ]; + jest + .spyOn(metamaskController.accountsController, 'listAccounts') + .mockReturnValueOnce(internalAccounts); + jest + .spyOn(metamaskController, 'captureKeyringTypesWithMissingIdentities') + .mockImplementation(() => { + // noop + }); + + expect(() => + metamaskController.sortAccountsByLastSelected([ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + ]), + ).toThrow( + 'Missing identity for address: "0x7A2Bd22810088523516737b4Dc238A4bC37c23F2".', + ); + expect( + metamaskController.captureKeyringTypesWithMissingIdentities, + ).toHaveBeenCalledWith(internalAccounts, [ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + ]); + }); + + it('throws if a keyring account is missing an address (case 2)', () => { + const internalAccounts = [ + { + address: '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', + metadata: { + name: 'Test Account', + lastSelected: 1, + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + { + address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', + metadata: { + name: 'Test Account', + lastSelected: 3, + keyring: { + type: 'HD Key Tree', + }, + }, + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + }, + ]; jest - .spyOn(metamaskController.accountTrackerController, 'state', 'get') - .mockReturnValue({ - accounts, + .spyOn(metamaskController.accountsController, 'listAccounts') + .mockReturnValueOnce(internalAccounts); + jest + .spyOn(metamaskController, 'captureKeyringTypesWithMissingIdentities') + .mockImplementation(() => { + // noop }); - const gotten = await metamaskController.getBalance( - TEST_ADDRESS, - ethQuery, + expect(() => + metamaskController.sortAccountsByLastSelected([ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + ]), + ).toThrow( + 'Missing identity for address: "0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3".', ); - - expect(balance).toStrictEqual(gotten); + expect( + metamaskController.captureKeyringTypesWithMissingIdentities, + ).toHaveBeenCalledWith(internalAccounts, [ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + ]); }); }); @@ -830,21 +2087,16 @@ describe('MetaMaskController', () => { ); await expect(result).rejects.toThrow( - 'MetamaskController:getKeyringForDevice - Unknown device', + 'MetamaskController:#withKeyringForDevice - Unknown device', ); }); it('should add the Trezor Hardware keyring and return the first page of accounts', async () => { - jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); - const firstPage = await metamaskController.connectHardware( HardwareDeviceNames.trezor, 0, ); - expect( - metamaskController.keyringController.addNewKeyring, - ).toHaveBeenCalledWith(KeyringType.trezor); expect( metamaskController.keyringController.state.keyrings[1].type, ).toBe(TrezorKeyring.type); @@ -852,16 +2104,11 @@ describe('MetaMaskController', () => { }); it('should add the Ledger Hardware keyring and return the first page of accounts', async () => { - jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); - const firstPage = await metamaskController.connectHardware( HardwareDeviceNames.ledger, 0, ); - expect( - metamaskController.keyringController.addNewKeyring, - ).toHaveBeenCalledWith(KeyringType.ledger); expect( metamaskController.keyringController.state.keyrings[1].type, ).toBe(LedgerKeyring.type); @@ -876,7 +2123,7 @@ describe('MetaMaskController', () => { `m/44/0'/0'`, ); await expect(result).rejects.toThrow( - 'MetamaskController:getKeyringForDevice - Unknown device', + 'MetamaskController:#withKeyringForDevice - Unknown device', ); }); @@ -897,7 +2144,7 @@ describe('MetaMaskController', () => { ); }); - describe('getHardwareDeviceName', () => { + describe('getDeviceNameForMetric', () => { const hdPath = "m/44'/60'/0'/0/0"; it('should return the correct device name for Ledger', async () => { @@ -907,6 +2154,7 @@ describe('MetaMaskController', () => { deviceName, hdPath, ); + expect(result).toBe('ledger'); }); @@ -917,49 +2165,62 @@ describe('MetaMaskController', () => { deviceName, hdPath, ); + expect(result).toBe('lattice'); }); it('should return the correct device name for Trezor', async () => { const deviceName = 'trezor'; jest - .spyOn(metamaskController, 'getKeyringForDevice') - .mockResolvedValue({ - bridge: { - minorVersion: 1, - model: 'T', - }, - }); + .spyOn(metamaskController.keyringController, 'withKeyring') + .mockImplementation((_, operation) => + operation({ + getModel: jest.fn().mockReturnValue('T'), + bridge: { + minorVersion: 1, + model: 'T', + }, + }), + ); + const result = await metamaskController.getDeviceNameForMetric( deviceName, hdPath, ); + expect(result).toBe('trezor'); }); it('should return undefined for unknown device name', async () => { const deviceName = 'unknown'; + const result = await metamaskController.getDeviceNameForMetric( deviceName, hdPath, ); + expect(result).toBe(deviceName); }); it('should handle special case for OneKeyDevice via Trezor', async () => { const deviceName = 'trezor'; jest - .spyOn(metamaskController, 'getKeyringForDevice') - .mockResolvedValue({ - bridge: { - model: 'T', - minorVersion: ONE_KEY_VIA_TREZOR_MINOR_VERSION, - }, - }); + .spyOn(metamaskController.keyringController, 'withKeyring') + .mockImplementation((_, operation) => + operation({ + getModel: jest.fn().mockReturnValue('T'), + bridge: { + model: 'T', + minorVersion: ONE_KEY_VIA_TREZOR_MINOR_VERSION, + }, + }), + ); + const result = await metamaskController.getDeviceNameForMetric( deviceName, hdPath, ); + expect(result).toBe('OneKey via Trezor'); }); }); @@ -970,7 +2231,7 @@ describe('MetaMaskController', () => { 'Some random device name', ); await expect(result).rejects.toThrow( - 'MetamaskController:getKeyringForDevice - Unknown device', + 'MetamaskController:#withKeyringForDevice - Unknown device', ); }); @@ -1058,22 +2319,6 @@ describe('MetaMaskController', () => { ]); }); - it('should call keyringController.addNewAccountForKeyring', async () => { - jest.spyOn( - metamaskController.keyringController, - 'addNewAccountForKeyring', - ); - - await metamaskController.unlockHardwareWalletAccount( - accountToUnlock, - device, - ); - - expect( - metamaskController.keyringController.addNewAccountForKeyring, - ).toHaveBeenCalledTimes(1); - }); - it('should call preferencesController.setSelectedAddress', async () => { jest.spyOn( metamaskController.preferencesController, @@ -1218,7 +2463,10 @@ describe('MetaMaskController', () => { ).toHaveBeenCalledTimes(1); expect( metamaskController.txController.wipeTransactions, - ).toHaveBeenCalledWith(false, selectedAddressMock); + ).toHaveBeenCalledWith({ + address: selectedAddressMock, + chainId: CHAIN_IDS.MAINNET, + }); expect( metamaskController.smartTransactionsController.wipeSmartTransactions, ).toHaveBeenCalledWith({ @@ -1265,14 +2513,6 @@ describe('MetaMaskController', () => { it('should return address', async () => { expect(ret).toStrictEqual('0x1'); }); - it('should call keyringController.getKeyringForAccount', async () => { - expect( - metamaskController.keyringController.getKeyringForAccount, - ).toHaveBeenCalledWith(addressToRemove); - }); - it('should call keyring.destroy', async () => { - expect(mockKeyring.destroy).toHaveBeenCalledTimes(1); - }); }); describe('#setupPhishingCommunication', () => { beforeEach(() => { @@ -2005,23 +3245,6 @@ describe('MetaMaskController', () => { }); }); - describe('markNotificationsAsRead', () => { - it('marks the notification as read', () => { - metamaskController.markNotificationsAsRead([NOTIFICATION_ID]); - const readNotification = - metamaskController.getState().notifications[NOTIFICATION_ID]; - expect(readNotification.readDate).not.toBeNull(); - }); - }); - - describe('dismissNotifications', () => { - it('deletes the notification from state', () => { - metamaskController.dismissNotifications([NOTIFICATION_ID]); - const state = metamaskController.getState().notifications; - expect(Object.values(state)).not.toContain(NOTIFICATION_ID); - }); - }); - describe('getTokenStandardAndDetails', () => { it('gets token data from the token list if available, and with a balance retrieved by fetchTokenBalance', async () => { const providerResultStub = { @@ -2434,6 +3657,7 @@ describe('MetaMaskController', () => { methods: [BtcMethod.SendBitcoin], address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', }; + const mockCurrency = 'CAD'; beforeEach(() => { jest.spyOn(metamaskController.multichainRatesController, 'start'); @@ -2531,6 +3755,43 @@ describe('MetaMaskController', () => { localMetamaskController.multichainRatesController.start, ).toHaveBeenCalled(); }); + + it('calls setFiatCurrency when the `currentCurrency` has changed', async () => { + jest.spyOn(RatesController.prototype, 'setFiatCurrency'); + const localMetamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: { + ...cloneDeep(firstTimeState), + AccountsController: { + internalAccounts: { + accounts: { + [mockNonEvmAccount.id]: mockNonEvmAccount, + [mockEvmAccount.id]: mockEvmAccount, + }, + selectedAccount: mockNonEvmAccount.id, + }, + }, + }, + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + + metamaskController.controllerMessenger.publish( + 'CurrencyRateController:stateChange', + { currentCurrency: mockCurrency }, + ); + + expect( + localMetamaskController.multichainRatesController.setFiatCurrency, + ).toHaveBeenCalledWith(mockCurrency); + }); }); describe('MultichainBalancesController', () => { @@ -2630,6 +3891,181 @@ describe('MetaMaskController', () => { ); }); }); + + describe('RemoteFeatureFlagController', () => { + let localMetamaskController; + + beforeEach(() => { + localMetamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: { + ...cloneDeep(firstTimeState), + PreferencesController: { + useExternalServices: false, + }, + }, + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize RemoteFeatureFlagController in disabled state when useExternalServices is false', async () => { + const { remoteFeatureFlagController, preferencesController } = + localMetamaskController; + + expect(preferencesController.state.useExternalServices).toBe(false); + expect(remoteFeatureFlagController.state).toStrictEqual({ + remoteFeatureFlags: {}, + cacheTimestamp: 0, + }); + }); + + it('should disable feature flag fetching when useExternalServices is disabled', async () => { + const { remoteFeatureFlagController } = localMetamaskController; + + // First enable external services + await simulatePreferencesChange({ + useExternalServices: true, + }); + + // Then disable them + await simulatePreferencesChange({ + useExternalServices: false, + }); + + expect(remoteFeatureFlagController.state).toStrictEqual({ + remoteFeatureFlags: {}, + cacheTimestamp: 0, + }); + }); + + it('should handle errors during feature flag updates', async () => { + const { remoteFeatureFlagController } = localMetamaskController; + const mockError = new Error('Failed to fetch'); + + jest + .spyOn(remoteFeatureFlagController, 'updateRemoteFeatureFlags') + .mockRejectedValue(mockError); + + await simulatePreferencesChange({ + useExternalServices: true, + }); + + expect(remoteFeatureFlagController.state).toStrictEqual({ + remoteFeatureFlags: {}, + cacheTimestamp: 0, + }); + }); + + it('should maintain feature flag state across preference toggles', async () => { + const { remoteFeatureFlagController } = localMetamaskController; + const mockFlags = { testFlag: true }; + + jest + .spyOn(remoteFeatureFlagController, 'updateRemoteFeatureFlags') + .mockResolvedValue(mockFlags); + + // Enable external services + await simulatePreferencesChange({ + useExternalServices: true, + }); + + // Disable external services + await simulatePreferencesChange({ + useExternalServices: false, + }); + + // Verify state is cleared + expect(remoteFeatureFlagController.state).toStrictEqual({ + remoteFeatureFlags: {}, + cacheTimestamp: 0, + }); + }); + }); + + describe('_getConfigForRemoteFeatureFlagRequest', () => { + it('returns config in mapping', async () => { + const result = + await metamaskController._getConfigForRemoteFeatureFlagRequest(); + expect(result).toStrictEqual({ + distribution: 'main', + environment: 'dev', + }); + }); + + it('returna config when not matching default mapping', async () => { + process.env.METAMASK_BUILD_TYPE = 'beta'; + process.env.METAMASK_ENVIRONMENT = ENVIRONMENT.RELEASE_CANDIDATE; + + const result = + await metamaskController._getConfigForRemoteFeatureFlagRequest(); + expect(result).toStrictEqual({ + distribution: 'main', + environment: 'rc', + }); + }); + }); + }); + + describe('onFeatureFlagResponseReceived', () => { + const metamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: cloneDeep(firstTimeState), + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + + beforeEach(() => { + jest.spyOn( + metamaskController.tokenBalancesController, + 'setIntervalLength', + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should not set the interval length if the pollInterval is 0', () => { + metamaskController.onFeatureFlagResponseReceived({ + multiChainAssets: { + pollInterval: 0, + }, + }); + expect( + metamaskController.tokenBalancesController.setIntervalLength, + ).not.toHaveBeenCalled(); + }); + + it('should set the interval length if the pollInterval is greater than 0', () => { + const pollInterval = 10; + metamaskController.onFeatureFlagResponseReceived({ + multiChainAssets: { + pollInterval, + }, + }); + expect( + metamaskController.tokenBalancesController.setIntervalLength, + ).toHaveBeenCalledWith(pollInterval * SECOND); + }); }); describe('MV3 Specific behaviour', () => { @@ -2687,4 +4123,144 @@ describe('MetaMaskController', () => { expect(browserPolyfillMock.storage.session.set).not.toHaveBeenCalled(); }); }); + + describe('MetaMetrics & authentication dependencies', () => { + let metamaskController; + let mockPerformSignIn; + let mockPerformSignOut; + + const arrangeControllerState = (stateOverrides) => { + metamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: { ...cloneDeep(firstTimeState), ...stateOverrides }, + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + + mockPerformSignIn = jest + .spyOn(metamaskController.authenticationController, 'performSignIn') + .mockResolvedValue(); + mockPerformSignOut = jest + .spyOn(metamaskController.authenticationController, 'performSignOut') + .mockResolvedValue(); + }; + + it('should sign in the user if MetaMetrics is enabled and the user is not signed in', async () => { + arrangeControllerState({ + MetaMetricsController: { + participateInMetaMetrics: false, + }, + AuthenticationController: { + isSignedIn: false, + }, + }); + + metamaskController.controllerMessenger.publish( + 'MetaMetricsController:stateChange', + { + participateInMetaMetrics: true, + }, + ); + + expect(mockPerformSignIn).toHaveBeenCalledTimes(1); + expect(mockPerformSignOut).not.toHaveBeenCalled(); + }); + + it('should sign out the user if MetaMetrics is disabled and the user is signed in but profile syncing is disabled', async () => { + arrangeControllerState({ + MetaMetricsController: { + participateInMetaMetrics: true, + }, + AuthenticationController: { + isSignedIn: true, + }, + UserStorageController: { + isProfileSyncingEnabled: false, + }, + }); + + metamaskController.controllerMessenger.publish( + 'MetaMetricsController:stateChange', + { + participateInMetaMetrics: false, + }, + ); + + expect(mockPerformSignIn).not.toHaveBeenCalled(); + expect(mockPerformSignOut).toHaveBeenCalledTimes(1); + }); + + it('should not sign in the user if MetaMetrics is enabled and the user is already signed in', async () => { + arrangeControllerState({ + MetaMetricsController: { + participateInMetaMetrics: false, + }, + AuthenticationController: { + isSignedIn: true, + }, + }); + + metamaskController.controllerMessenger.publish( + 'MetaMetricsController:stateChange', + { + participateInMetaMetrics: true, + }, + ); + + expect(mockPerformSignIn).not.toHaveBeenCalled(); + expect(mockPerformSignOut).not.toHaveBeenCalled(); + }); + + it('should not sign out the user if MetaMetrics is disabled and the user is not signed in', async () => { + arrangeControllerState({ + MetaMetricsController: { + participateInMetaMetrics: true, + }, + AuthenticationController: { + isSignedIn: false, + }, + }); + + metamaskController.controllerMessenger.publish( + 'MetaMetricsController:stateChange', + { + participateInMetaMetrics: false, + }, + ); + + expect(mockPerformSignIn).not.toHaveBeenCalled(); + expect(mockPerformSignOut).not.toHaveBeenCalled(); + }); + + it('should not sign out the user if MetaMetrics is disabled and profile syncing is enabled', async () => { + arrangeControllerState({ + MetaMetricsController: { + participateInMetaMetrics: true, + }, + AuthenticationController: { + isSignedIn: true, + }, + UserStorageController: { + isProfileSyncingEnabled: true, + }, + }); + + metamaskController.controllerMessenger.publish( + 'MetaMetricsController:stateChange', + { + participateInMetaMetrics: false, + }, + ); + + expect(mockPerformSignIn).not.toHaveBeenCalled(); + expect(mockPerformSignOut).not.toHaveBeenCalled(); + }); + }); }); diff --git a/app/scripts/migrations/105.test.ts b/app/scripts/migrations/105.test.ts index 168fe8dd0916..ffa808e33291 100644 --- a/app/scripts/migrations/105.test.ts +++ b/app/scripts/migrations/105.test.ts @@ -1,8 +1,8 @@ import { v4 as uuid } from 'uuid'; import { sha256FromString } from 'ethereumjs-util'; -import { InternalAccount } from '@metamask/keyring-api'; import { ETH_EOA_METHODS } from '../../../shared/constants/eth-methods'; import { migrate } from './105'; +import type { Identity, InternalAccountV1 } from './105'; const MOCK_ADDRESS = '0x0'; const MOCK_ADDRESS_2 = '0x1'; @@ -22,12 +22,6 @@ function addressToUUID(address: string): string { }); } -type Identity = { - name: string; - address: string; - lastSelected?: number; -}; - type Identities = { [key: string]: Identity; }; @@ -62,7 +56,7 @@ function expectedInternalAccount( address: string, nickname: string, lastSelected?: number, -): InternalAccount { +): InternalAccountV1 { return { address, id: addressToUUID(address), diff --git a/app/scripts/migrations/105.ts b/app/scripts/migrations/105.ts index a54b3e6457a7..e3a6986af484 100644 --- a/app/scripts/migrations/105.ts +++ b/app/scripts/migrations/105.ts @@ -1,4 +1,5 @@ -import { EthAccountType, InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType } from '@metamask/keyring-api'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; import { sha256FromString } from 'ethereumjs-util'; import { v4 as uuid } from 'uuid'; import { cloneDeep } from 'lodash'; @@ -9,12 +10,16 @@ type VersionedData = { data: Record; }; -type Identity = { +export type Identity = { name: string; address: string; lastSelected?: number; }; +// The `InternalAccount` has been updated with `@metamask/keyring-api@13.0.0`, so we +// omit the new field to re-use the original type for that migration. +export type InternalAccountV1 = Omit; + export const version = 105; /** @@ -50,11 +55,11 @@ function findInternalAccountByAddress( // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record, address: string, -): InternalAccount | undefined { - return Object.values( +): InternalAccountV1 | undefined { + return Object.values( state.AccountsController.internalAccounts.accounts, ).find( - (account: InternalAccount) => + (account: InternalAccountV1) => account.address.toLowerCase() === address.toLowerCase(), ); } @@ -83,7 +88,7 @@ function createInternalAccountsForAccountsController( return; } - const accounts: Record = {}; + const accounts: Record = {}; Object.values(identities).forEach((identity) => { const expectedId = uuid({ diff --git a/app/scripts/migrations/119.ts b/app/scripts/migrations/119.ts index 8cb0d2c04b97..606a9e38a31d 100644 --- a/app/scripts/migrations/119.ts +++ b/app/scripts/migrations/119.ts @@ -1,7 +1,7 @@ import { cloneDeep, isObject } from 'lodash'; import { hasProperty } from '@metamask/utils'; import { AccountsControllerState } from '@metamask/accounts-controller'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; type VersionedData = { meta: { version: number }; diff --git a/app/scripts/migrations/131.1.test.ts b/app/scripts/migrations/131.1.test.ts new file mode 100644 index 000000000000..3d1d8446a983 --- /dev/null +++ b/app/scripts/migrations/131.1.test.ts @@ -0,0 +1,254 @@ +import { cloneDeep } from 'lodash'; +import { NetworkState } from '@metamask/network-controller'; +import { infuraProjectId } from '../../../shared/constants/network'; +import { migrate, version } from './131.1'; + +const oldVersion = 131; +const BASE_CHAIN_ID = '0x2105'; + +describe('migration #131.1', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + describe('Base Network Migration', () => { + it('does nothing if networkConfigurationsByChainId is not in state', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('does nothing if no Infura RPC endpoints are used', async () => { + const oldState = { + NetworkController: { + networkConfigurationsByChainId: { + '0x1': { + rpcEndpoints: [ + { + url: 'https://custom.rpc', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('does nothing if Base network configuration is missing', async () => { + const oldState = { + NetworkController: { + networkConfigurationsByChainId: { + '0x1': { + rpcEndpoints: [ + { + url: `https://mainnet.infura.io/v3/${infuraProjectId}`, + type: 'infura', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('replaces "https://mainnet.base.org" if the default RPC endpoint is Infura', async () => { + const oldState = { + NetworkController: { + networkConfigurationsByChainId: { + [BASE_CHAIN_ID]: { + rpcEndpoints: [ + { + url: 'https://mainnet.base.org', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x1': { + rpcEndpoints: [ + { + url: `https://mainnet.infura.io/v3/${infuraProjectId}`, + type: 'infura', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + const updatedNetworkController = transformedState.data + .NetworkController as NetworkState; + + expect( + updatedNetworkController.networkConfigurationsByChainId[BASE_CHAIN_ID] + .rpcEndpoints[0].url, + ).toEqual(`https://base-mainnet.infura.io/v3/${infuraProjectId}`); + }); + + it('does not modify RPC endpoints if the default RPC endpoint is not Infura', async () => { + const oldState = { + NetworkController: { + networkConfigurationsByChainId: { + [BASE_CHAIN_ID]: { + rpcEndpoints: [ + { + url: 'https://other.rpc', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x1': { + rpcEndpoints: [ + { + url: 'https://custom.rpc', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + const updatedNetworkController = transformedState.data + .NetworkController as NetworkState; + + expect( + updatedNetworkController.networkConfigurationsByChainId[BASE_CHAIN_ID] + .rpcEndpoints[0].url, + ).toEqual('https://other.rpc'); + }); + + it('keeps defaultRpcEndpointIndex unchanged when replacing "https://mainnet.base.org"', async () => { + const oldState = { + NetworkController: { + networkConfigurationsByChainId: { + [BASE_CHAIN_ID]: { + rpcEndpoints: [ + { + url: 'https://mainnet.base.org', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x1': { + rpcEndpoints: [ + { + url: `https://mainnet.infura.io/v3/${infuraProjectId}`, + type: 'infura', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + const updatedNetworkController = transformedState.data + .NetworkController as NetworkState; + + expect( + updatedNetworkController.networkConfigurationsByChainId[BASE_CHAIN_ID] + .defaultRpcEndpointIndex, + ).toEqual(0); + }); + + it('does nothing if Linea mainnet is excluded', async () => { + const oldState = { + NetworkController: { + networkConfigurationsByChainId: { + [BASE_CHAIN_ID]: { + rpcEndpoints: [ + { + url: 'https://mainnet.base.org', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x1': { + rpcEndpoints: [ + { + url: `https://mainnet.infura.io/v3/${infuraProjectId}`, + type: 'infura', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x13881': { + rpcEndpoints: [ + { + url: 'https://rpc.goerli.linea.io', + type: 'custom', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + const updatedNetworkController = transformedState.data + .NetworkController as NetworkState; + + expect( + updatedNetworkController.networkConfigurationsByChainId['0x13881'] + .rpcEndpoints[0].url, + ).toEqual('https://rpc.goerli.linea.io'); + }); + }); +}); diff --git a/app/scripts/migrations/131.1.ts b/app/scripts/migrations/131.1.ts new file mode 100644 index 000000000000..4b9478afd2ab --- /dev/null +++ b/app/scripts/migrations/131.1.ts @@ -0,0 +1,128 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { RpcEndpointType } from '@metamask/network-controller'; +import { cloneDeep } from 'lodash'; +import { + allowedInfuraHosts, + CHAIN_IDS, + infuraChainIdsTestNets, + infuraProjectId, +} from '../../../shared/constants/network'; + +export const version = 131.1; +const BASE_CHAIN_ID = '0x2105'; + +/** + * Replace all occurrences of "https://mainnet.base.org" with + * "https://base-mainnet.infura.io/v3/${infuraProjectId}" in the Base network configuration, + * if the user already relies on at least one Infura RPC endpoint. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate(originalVersionedData: { + meta: { version: number }; + data: Record; +}) { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + versionedData.data = transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if ( + hasProperty(state, 'NetworkController') && + isObject(state.NetworkController) && + hasProperty(state.NetworkController, 'networkConfigurationsByChainId') && + isObject(state.NetworkController.networkConfigurationsByChainId) + ) { + const { networkConfigurationsByChainId } = state.NetworkController; + + // Check if at least one network uses an Infura RPC endpoint, excluding testnets + const usesInfura = Object.entries(networkConfigurationsByChainId) + .filter( + ([chainId]) => + ![...infuraChainIdsTestNets, CHAIN_IDS.LINEA_MAINNET].includes( + chainId, + ), + ) + .some(([, networkConfig]) => { + if ( + !isObject(networkConfig) || + !Array.isArray(networkConfig.rpcEndpoints) || + typeof networkConfig.defaultRpcEndpointIndex !== 'number' + ) { + return false; + } + + // Get the default RPC endpoint used by the network + const defaultRpcEndpoint = + networkConfig?.rpcEndpoints?.[networkConfig?.defaultRpcEndpointIndex]; + + if ( + !isObject(defaultRpcEndpoint) || + typeof defaultRpcEndpoint.url !== 'string' + ) { + return false; + } + + try { + const urlHost = new URL(defaultRpcEndpoint.url).host; + return ( + defaultRpcEndpoint.type === RpcEndpointType.Infura || + allowedInfuraHosts.includes(urlHost) + ); + } catch { + return false; + } + }); + + if (!usesInfura) { + // If no Infura endpoints are used, return the state unchanged + return state; + } + + // Check for Base network configuration (chainId 8453 / 0x2105) + const baseNetworkConfig = networkConfigurationsByChainId[BASE_CHAIN_ID]; + if (isObject(baseNetworkConfig)) { + const { rpcEndpoints } = baseNetworkConfig; + + if (Array.isArray(rpcEndpoints)) { + // Find the first occurrence of "https://mainnet.base.org" + const index = rpcEndpoints.findIndex( + (endpoint) => + isObject(endpoint) && endpoint.url === 'https://mainnet.base.org', + ); + + if (index !== -1) { + // Replace the URL with the new Infura URL + rpcEndpoints[index] = { + ...rpcEndpoints[index], + url: `https://base-mainnet.infura.io/v3/${infuraProjectId}`, + }; + + // Update the configuration + networkConfigurationsByChainId[BASE_CHAIN_ID] = { + ...baseNetworkConfig, + rpcEndpoints, + }; + + return { + ...state, + NetworkController: { + ...state.NetworkController, + networkConfigurationsByChainId, + }, + }; + } + } + } + } + + return state; +} diff --git a/app/scripts/migrations/133.1.test.ts b/app/scripts/migrations/133.1.test.ts new file mode 100644 index 000000000000..4f1979f31a97 --- /dev/null +++ b/app/scripts/migrations/133.1.test.ts @@ -0,0 +1,224 @@ +import { cloneDeep } from 'lodash'; +import { TokensControllerState } from '@metamask/assets-controllers'; +import { migrate, version } from './133.1'; + +const sentryCaptureExceptionMock = jest.fn(); +const sentryCaptureMessageMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, + captureMessage: sentryCaptureMessageMock, +}; + +const oldVersion = 133; + +const mockStateWithNullDecimals = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { address: '0x1', symbol: 'TOKEN1', decimals: null }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + }, + }, + allDetectedTokens: { + '0x1': { + '0x123': [ + { address: '0x5', symbol: 'TOKEN5', decimals: null }, + { address: '0x6', symbol: 'TOKEN6', decimals: 6 }, + ], + }, + }, + tokens: [ + { address: '0x7', symbol: 'TOKEN7', decimals: null }, + { address: '0x8', symbol: 'TOKEN8', decimals: 18 }, + ], + detectedTokens: [ + { address: '0x9', symbol: 'TOKEN9', decimals: null }, + { address: '0xA', symbol: 'TOKEN10', decimals: 6 }, + ], + }, + }, +}; + +describe(`migration #${version}`, () => { + afterEach(() => jest.resetAllMocks()); + + it('updates the version metadata', async () => { + const oldStorage = cloneDeep(mockStateWithNullDecimals); + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('removes tokens with null decimals from allTokens', async () => { + const oldStorage = cloneDeep(mockStateWithNullDecimals); + + const newStorage = await migrate(oldStorage); + + const tokensControllerState = newStorage.data + .TokensController as TokensControllerState; + const { allTokens } = tokensControllerState; + + expect(allTokens).toEqual({ + '0x1': { + '0x123': [{ address: '0x2', symbol: 'TOKEN2', decimals: 18 }], + }, + }); + }); + + it('removes tokens with null decimals from allDetectedTokens', async () => { + const oldStorage = cloneDeep(mockStateWithNullDecimals); + + const newStorage = await migrate(oldStorage); + + const tokensControllerState = newStorage.data + .TokensController as TokensControllerState; + const { allDetectedTokens } = tokensControllerState; + + expect(allDetectedTokens).toEqual({ + '0x1': { + '0x123': [{ address: '0x6', symbol: 'TOKEN6', decimals: 6 }], + }, + }); + }); + + it('removes tokens with null decimals from tokens array', async () => { + const oldStorage = cloneDeep(mockStateWithNullDecimals); + + const newStorage = await migrate(oldStorage); + + const tokensControllerState = newStorage.data + .TokensController as TokensControllerState; + const { tokens } = tokensControllerState; + + expect(tokens).toEqual([ + { address: '0x8', symbol: 'TOKEN8', decimals: 18 }, + ]); + }); + + it('removes tokens with null decimals from detectedTokens array', async () => { + const oldStorage = cloneDeep(mockStateWithNullDecimals); + + const newStorage = await migrate(oldStorage); + + const tokensControllerState = newStorage.data + .TokensController as TokensControllerState; + const { detectedTokens } = tokensControllerState; + + expect(detectedTokens).toEqual([ + { address: '0xA', symbol: 'TOKEN10', decimals: 6 }, + ]); + }); + + it('logs tokens with null decimals before removing them', async () => { + const oldStorage = cloneDeep(mockStateWithNullDecimals); + + await migrate(oldStorage); + + expect(sentryCaptureMessageMock).toHaveBeenCalledTimes(4); + expect(sentryCaptureMessageMock).toHaveBeenCalledWith( + `Migration ${version}: Removed token with decimals === null in allTokens. Address: 0x1`, + ); + expect(sentryCaptureMessageMock).toHaveBeenCalledWith( + `Migration ${version}: Removed token with decimals === null in allDetectedTokens. Address: 0x5`, + ); + expect(sentryCaptureMessageMock).toHaveBeenCalledWith( + `Migration ${version}: Removed token with decimals === null in tokens. Address: 0x7`, + ); + expect(sentryCaptureMessageMock).toHaveBeenCalledWith( + `Migration ${version}: Removed token with decimals === null in detectedTokens. Address: 0x9`, + ); + }); + + it('does nothing if all tokens have valid decimals', async () => { + const validState = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [{ address: '0x2', symbol: 'TOKEN2', decimals: 18 }], + }, + }, + allDetectedTokens: { + '0x1': { + '0x123': [{ address: '0x6', symbol: 'TOKEN6', decimals: 6 }], + }, + }, + tokens: [{ address: '0x8', symbol: 'TOKEN8', decimals: 18 }], + detectedTokens: [{ address: '0xA', symbol: 'TOKEN10', decimals: 6 }], + }, + }, + }; + + const oldStorage = cloneDeep(validState); + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + expect(sentryCaptureMessageMock).not.toHaveBeenCalled(); + }); + + it('does nothing if TokensController is missing', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + expect(sentryCaptureMessageMock).not.toHaveBeenCalled(); + }); + + const invalidState = [ + { + errorMessage: `Migration ${version}: Invalid allTokens state of type 'string'`, + label: 'Invalid allTokens', + state: { TokensController: { allTokens: 'invalid' } }, + }, + { + errorMessage: `Migration ${version}: Invalid allDetectedTokens state of type 'string'`, + label: 'Invalid allDetectedTokens', + state: { TokensController: { allDetectedTokens: 'invalid' } }, + }, + { + errorMessage: `Migration ${version}: Invalid tokens state of type 'string'`, + label: 'Invalid tokens', + state: { TokensController: { tokens: 'invalid' } }, + }, + { + errorMessage: `Migration ${version}: Invalid detectedTokens state of type 'string'`, + label: 'Invalid detectedTokens', + state: { TokensController: { detectedTokens: 'invalid' } }, + }, + ]; + + // @ts-expect-error 'each' function is not recognized by TypeScript types + it.each(invalidState)( + 'captures error when state is invalid due to: $label', + async ({ + errorMessage, + state, + }: { + errorMessage: string; + state: Record; + }) => { + const oldStorage = { + meta: { version: oldVersion }, + data: state, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(errorMessage), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }, + ); +}); diff --git a/app/scripts/migrations/133.1.ts b/app/scripts/migrations/133.1.ts new file mode 100644 index 000000000000..c59b5c0cfed5 --- /dev/null +++ b/app/scripts/migrations/133.1.ts @@ -0,0 +1,187 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 133.1; + +/** + * Removes tokens with `decimals === null` from `allTokens`, `allDetectedTokens`, `tokens`, and `detectedTokens`. + * Captures exceptions for invalid states using Sentry and logs tokens with `decimals === null`. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to disk. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +/** + * Transforms the TokensController state to remove tokens with `decimals === null`. + * + * @param state - The persisted MetaMask state. + */ +function transformState(state: Record): void { + if (!hasProperty(state, 'TokensController')) { + return; + } + + const tokensControllerState = state.TokensController; + + if (!isObject(tokensControllerState)) { + global.sentry?.captureException( + new Error( + `Migration ${version}: Invalid TokensController state of type '${typeof tokensControllerState}'`, + ), + ); + return; + } + + // Validate and transform `allTokens` + if (hasProperty(tokensControllerState, 'allTokens')) { + if (isObject(tokensControllerState.allTokens)) { + tokensControllerState.allTokens = transformTokenCollection( + tokensControllerState.allTokens, + 'allTokens', + ); + } else { + global.sentry?.captureException( + new Error( + `Migration ${version}: Invalid allTokens state of type '${typeof tokensControllerState.allTokens}'`, + ), + ); + } + } + + // Validate and transform `allDetectedTokens` + if (hasProperty(tokensControllerState, 'allDetectedTokens')) { + if (isObject(tokensControllerState.allDetectedTokens)) { + tokensControllerState.allDetectedTokens = transformTokenCollection( + tokensControllerState.allDetectedTokens, + 'allDetectedTokens', + ); + } else { + global.sentry?.captureException( + new Error( + `Migration ${version}: Invalid allDetectedTokens state of type '${typeof tokensControllerState.allDetectedTokens}'`, + ), + ); + } + } + + // Transform `tokens` array + if ( + hasProperty(tokensControllerState, 'tokens') && + Array.isArray(tokensControllerState.tokens) + ) { + tokensControllerState.tokens = tokensControllerState.tokens.filter( + (token) => { + if ( + isObject(token) && + hasProperty(token, 'decimals') && + token.decimals === null && + hasProperty(token, 'address') + ) { + global.sentry?.captureMessage( + `Migration ${version}: Removed token with decimals === null in tokens. Address: ${token.address}`, + ); + return false; + } + return true; + }, + ); + } else if (hasProperty(tokensControllerState, 'tokens')) { + global.sentry?.captureException( + new Error( + `Migration ${version}: Invalid tokens state of type '${typeof tokensControllerState.tokens}'`, + ), + ); + } + + // Transform `detectedTokens` array + if ( + hasProperty(tokensControllerState, 'detectedTokens') && + Array.isArray(tokensControllerState.detectedTokens) + ) { + tokensControllerState.detectedTokens = + tokensControllerState.detectedTokens.filter((token) => { + if ( + isObject(token) && + hasProperty(token, 'decimals') && + token.decimals === null && + hasProperty(token, 'address') + ) { + global.sentry?.captureMessage( + `Migration ${version}: Removed token with decimals === null in detectedTokens. Address: ${token.address}`, + ); + return false; + } + return true; + }); + } else if (hasProperty(tokensControllerState, 'detectedTokens')) { + global.sentry?.captureException( + new Error( + `Migration ${version}: Invalid detectedTokens state of type '${typeof tokensControllerState.detectedTokens}'`, + ), + ); + } +} + +/** + * Removes tokens with `decimals === null` from a token collection and logs their addresses. + * + * @param tokenCollection - The token collection to transform. + * @param propertyName - The name of the property being transformed (for logging purposes). + * @returns The updated token collection. + */ +function transformTokenCollection( + tokenCollection: Record, + propertyName: string, +) { + const updatedState: Record = {}; + + for (const [chainId, accounts] of Object.entries(tokenCollection)) { + if (isObject(accounts)) { + const updatedTokensAccounts: Record = {}; + + for (const [account, tokens] of Object.entries(accounts)) { + if (Array.isArray(tokens)) { + // Filter tokens and log those with `decimals === null` + const filteredTokens = tokens.filter((token) => { + if ( + isObject(token) && + hasProperty(token, 'decimals') && + token.decimals === null && + hasProperty(token, 'address') + ) { + global.sentry?.captureMessage( + `Migration ${version}: Removed token with decimals === null in ${propertyName}. Address: ${token.address}`, + ); + return false; // Exclude token + } + return ( + isObject(token) && + hasProperty(token, 'decimals') && + token.decimals !== null + ); + }); + + updatedTokensAccounts[account] = filteredTokens; + } + } + + updatedState[chainId] = updatedTokensAccounts; + } + } + + return updatedState; +} diff --git a/app/scripts/migrations/133.2.test.ts b/app/scripts/migrations/133.2.test.ts new file mode 100644 index 000000000000..18251d8f4b2b --- /dev/null +++ b/app/scripts/migrations/133.2.test.ts @@ -0,0 +1,185 @@ +import { migrate, version } from './133.2'; + +const oldVersion = 133.1; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if theres no tokens controller state defined', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if theres empty tokens controller state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: {}, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if theres empty tokens controller state for allTokens', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: {}, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if theres empty tokens controller state for mainnet', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': {}, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('Does nothing if theres no tokens with empty address', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { address: '0x1', symbol: 'TOKEN1', decimals: 18 }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + '0x123456': [ + { address: '0x3', symbol: 'TOKEN3', decimals: 18 }, + { address: '0x4', symbol: 'TOKEN4', decimals: 18 }, + ], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('Removes tokens with empty address', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + TokensController: { + allTokens: { + '0x1': { + '0x123': [{ address: '0x2', symbol: 'TOKEN2', decimals: 18 }], + }, + }, + }, + }); + }); + + it('Removes tokens with empty address across multiple accounts', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + '0x456': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x3', symbol: 'TOKEN3', decimals: 18 }, + ], + '0x789': [{ address: '0x4', symbol: 'TOKEN4', decimals: 18 }], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + TokensController: { + allTokens: { + '0x1': { + '0x123': [{ address: '0x2', symbol: 'TOKEN2', decimals: 18 }], + '0x456': [{ address: '0x3', symbol: 'TOKEN3', decimals: 18 }], + '0x789': [{ address: '0x4', symbol: 'TOKEN4', decimals: 18 }], + }, + }, + }, + }); + }); + + it('Does not change state on chains other than mainnet', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x999': { + '0x123': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); +}); diff --git a/app/scripts/migrations/133.2.ts b/app/scripts/migrations/133.2.ts new file mode 100644 index 000000000000..6ad8ff888cfd --- /dev/null +++ b/app/scripts/migrations/133.2.ts @@ -0,0 +1,53 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 133.2; + +/** + * This migration removes tokens on mainnet with the + * zero address, since this is not a valid erc20 token. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to disk. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record): void { + if ( + !hasProperty(state, 'TokensController') || + !isObject(state.TokensController) || + !isObject(state.TokensController.allTokens) + ) { + return; + } + + const chainIds = ['0x1']; + + for (const chainId of chainIds) { + const allTokensOnChain = state.TokensController.allTokens[chainId]; + + if (isObject(allTokensOnChain)) { + for (const [account, tokens] of Object.entries(allTokensOnChain)) { + if (Array.isArray(tokens)) { + allTokensOnChain[account] = tokens.filter( + (token) => + token?.address !== '0x0000000000000000000000000000000000000000', + ); + } + } + } + } +} diff --git a/app/scripts/migrations/133.test.ts b/app/scripts/migrations/133.test.ts new file mode 100644 index 000000000000..76dc66a0d611 --- /dev/null +++ b/app/scripts/migrations/133.test.ts @@ -0,0 +1,46 @@ +import { cloneDeep } from 'lodash'; +import { migrate, version } from './133'; + +const oldVersion = 132; + +describe('migration #133', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + describe('NotificationController', () => { + it('does nothing if NotificationController is not in state', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('deletes the NotificationController from state', async () => { + const oldState = { + NotificationController: {}, + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ OtherController: {} }); + }); + }); +}); diff --git a/app/scripts/migrations/133.ts b/app/scripts/migrations/133.ts new file mode 100644 index 000000000000..864f9d1b9637 --- /dev/null +++ b/app/scripts/migrations/133.ts @@ -0,0 +1,43 @@ +import { hasProperty } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 133; + +/** + * This migration removes the notification controller from state. Previously used for + * snap notifications, it is no longer needed now that snap notifications will live in the + * notification services controller. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +/** + * Remove the notification controller from state (and any persisted notifications). + * + * @param state - The persisted MetaMask state, keyed by controller. + */ +function transformState(state: Record): void { + // we're removing the NotificationController in favor of the NotificationServicesController + if (hasProperty(state, 'NotificationController')) { + delete state.NotificationController; + } +} diff --git a/app/scripts/migrations/134.test.ts b/app/scripts/migrations/134.test.ts new file mode 100644 index 000000000000..9b3d31db017f --- /dev/null +++ b/app/scripts/migrations/134.test.ts @@ -0,0 +1,62 @@ +import { cloneDeep } from 'lodash'; +import { migrate, version } from './134'; + +const oldVersion = 133; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('Does nothing if `usedNetworks` is not in the `AppStateController` state', async () => { + const oldState = { + AppStateController: { + timeoutMinutes: 0, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toStrictEqual(oldState); + }); + + it('Removes `usedNetworks` from the `AppStateController` state', async () => { + const oldState: { + AppStateController: { + timeoutMinutes: number; + usedNetworks?: Record; + }; + } = { + AppStateController: { + timeoutMinutes: 0, + usedNetworks: { + '0x1': true, + '0x5': true, + '0x539': true, + }, + }, + }; + const expectedState = { + AppStateController: { + timeoutMinutes: 0, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toStrictEqual(expectedState); + }); +}); diff --git a/app/scripts/migrations/134.ts b/app/scripts/migrations/134.ts new file mode 100644 index 000000000000..e11b2abd9625 --- /dev/null +++ b/app/scripts/migrations/134.ts @@ -0,0 +1,41 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 134; + +/** + * This migration removes `usedNetworks` from `AppStateController` state. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState( + state: Record, +): Record { + if ( + hasProperty(state, 'AppStateController') && + isObject(state.AppStateController) && + hasProperty(state.AppStateController, 'usedNetworks') + ) { + console.log('Removing usedNetworks from AppStateController'); + delete state.AppStateController.usedNetworks; + } + return state; +} diff --git a/app/scripts/migrations/135.test.ts b/app/scripts/migrations/135.test.ts new file mode 100644 index 000000000000..b2cca43b7733 --- /dev/null +++ b/app/scripts/migrations/135.test.ts @@ -0,0 +1,187 @@ +import { SmartTransaction } from '@metamask/smart-transactions-controller/dist/types'; +import { migrate, VersionedData } from './135'; + +const prevVersion = 134; + +describe('migration #135', () => { + const mockSmartTransaction: SmartTransaction = { + uuid: 'test-uuid', + }; + + it('should update the version metadata', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version: 135 }); + }); + + it('should set stx opt-in to true and migration flag when stx opt-in status is null', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: { + PreferencesController: { + preferences: { + smartTransactionsOptInStatus: null, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsOptInStatus, + ).toBe(true); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsMigrationApplied, + ).toBe(true); + }); + + it('should set stx opt-in to true and migration flag when stx opt-in status is undefined', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: { + PreferencesController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsOptInStatus, + ).toBe(true); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsMigrationApplied, + ).toBe(true); + }); + + it('should set stx opt-in to true and migration flag when stx opt-in is false and no existing mainnet smart transactions', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: { + PreferencesController: { + preferences: { + smartTransactionsOptInStatus: false, + }, + }, + SmartTransactionsController: { + smartTransactionsState: { + smartTransactions: { + '0x1': [], // Empty mainnet transactions + '0xAA36A7': [mockSmartTransaction], // Sepolia has transactions + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsOptInStatus, + ).toBe(true); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsMigrationApplied, + ).toBe(true); + }); + + it('should not change stx opt-in when stx opt-in is false but has existing smart transactions, but should set migration flag', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: { + PreferencesController: { + preferences: { + smartTransactionsOptInStatus: false, + }, + }, + SmartTransactionsController: { + smartTransactionsState: { + smartTransactions: { + '0x1': [mockSmartTransaction], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsOptInStatus, + ).toBe(false); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsMigrationApplied, + ).toBe(true); + }); + + it('should not change stx opt-in when stx opt-in is already true, but should set migration flag', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: { + PreferencesController: { + preferences: { + smartTransactionsOptInStatus: true, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsOptInStatus, + ).toBe(true); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsMigrationApplied, + ).toBe(true); + }); + + it('should initialize preferences object if it does not exist', async () => { + const oldStorage: VersionedData = { + meta: { version: prevVersion }, + data: { + PreferencesController: { + preferences: { + smartTransactionsOptInStatus: true, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data.PreferencesController?.preferences).toBeDefined(); + expect( + newStorage.data.PreferencesController?.preferences + ?.smartTransactionsMigrationApplied, + ).toBe(true); + }); + + it('should capture exception if PreferencesController state is invalid', async () => { + const sentryCaptureExceptionMock = jest.fn(); + global.sentry = { + captureException: sentryCaptureExceptionMock, + }; + + const oldStorage = { + meta: { version: prevVersion }, + data: { + PreferencesController: 'invalid', + }, + } as unknown as VersionedData; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error('Invalid PreferencesController state: string'), + ); + }); +}); diff --git a/app/scripts/migrations/135.ts b/app/scripts/migrations/135.ts new file mode 100644 index 000000000000..277aafa66227 --- /dev/null +++ b/app/scripts/migrations/135.ts @@ -0,0 +1,84 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; +import type { SmartTransaction } from '@metamask/smart-transactions-controller/dist/types'; +import { CHAIN_IDS } from '@metamask/transaction-controller'; + +export type VersionedData = { + meta: { + version: number; + }; + data: { + PreferencesController?: { + preferences?: { + smartTransactionsOptInStatus?: boolean | null; + smartTransactionsMigrationApplied?: boolean; + }; + }; + SmartTransactionsController?: { + smartTransactionsState: { + smartTransactions: Record; + }; + }; + }; +}; + +export const version = 135; + +function transformState(state: VersionedData['data']) { + if ( + !hasProperty(state, 'PreferencesController') || + !isObject(state.PreferencesController) + ) { + global.sentry?.captureException?.( + new Error( + `Invalid PreferencesController state: ${typeof state.PreferencesController}`, + ), + ); + return state; + } + + const { PreferencesController } = state; + + const currentOptInStatus = + PreferencesController.preferences?.smartTransactionsOptInStatus; + + if ( + currentOptInStatus === undefined || + currentOptInStatus === null || + (currentOptInStatus === false && !hasExistingSmartTransactions(state)) + ) { + state.PreferencesController.preferences = { + ...state.PreferencesController.preferences, + smartTransactionsOptInStatus: true, + smartTransactionsMigrationApplied: true, + }; + } else { + state.PreferencesController.preferences = { + ...state.PreferencesController.preferences, + smartTransactionsMigrationApplied: true, + }; + } + + return state; +} + +function hasExistingSmartTransactions(state: VersionedData['data']): boolean { + const smartTransactions = + state?.SmartTransactionsController?.smartTransactionsState + ?.smartTransactions; + + if (!isObject(smartTransactions)) { + return false; + } + + return (smartTransactions[CHAIN_IDS.MAINNET] || []).length > 0; +} + +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} diff --git a/app/scripts/migrations/136.test.ts b/app/scripts/migrations/136.test.ts new file mode 100644 index 000000000000..01fc9d49d69f --- /dev/null +++ b/app/scripts/migrations/136.test.ts @@ -0,0 +1,56 @@ +import { migrate, version } from './136'; + +const oldVersion = 135; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + describe(`migration #${version}`, () => { + it('removes the useRequestQueue preference', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useRequestQueue: true, + otherPreference: true, + }, + }, + }; + const expectedData = { + PreferencesController: { + otherPreference: true, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + + it('does nothing to other PreferencesController state if there is a useRequestQueue preference', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + existingPreference: true, + }, + }, + }; + + const expectedData = { + PreferencesController: { + existingPreference: true, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + }); +}); diff --git a/app/scripts/migrations/136.ts b/app/scripts/migrations/136.ts new file mode 100644 index 000000000000..3f6bfc661292 --- /dev/null +++ b/app/scripts/migrations/136.ts @@ -0,0 +1,37 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; +export const version = 136; +/** + * This migration removes the useRequestQueue preference from the user's preferences + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} +function transformState( + state: Record, +): Record { + if ( + hasProperty(state, 'PreferencesController') && + isObject(state.PreferencesController) && + hasProperty(state.PreferencesController, 'useRequestQueue') + ) { + delete state.PreferencesController.useRequestQueue; + } + return state; +} diff --git a/app/scripts/migrations/137.test.ts b/app/scripts/migrations/137.test.ts new file mode 100644 index 000000000000..20e7fec205cb --- /dev/null +++ b/app/scripts/migrations/137.test.ts @@ -0,0 +1,66 @@ +import { migrate, version, VersionedData } from './137'; + +const oldVersion = 136; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage: VersionedData = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + describe(`migration #${version}`, () => { + it('sets isAccountSyncingReadyToBeDispatched to true if completedOnboarding is true', async () => { + const oldStorage: VersionedData = { + meta: { version: oldVersion }, + data: { + OnboardingController: { + completedOnboarding: true, + }, + UserStorageController: { + isAccountSyncingReadyToBeDispatched: false, + }, + }, + }; + const expectedData = { + OnboardingController: { + completedOnboarding: true, + }, + UserStorageController: { + isAccountSyncingReadyToBeDispatched: true, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + + it('sets isAccountSyncingReadyToBeDispatched to false if completedOnboarding is false', async () => { + const oldStorage: VersionedData = { + meta: { version: oldVersion }, + data: { + OnboardingController: { + completedOnboarding: false, + }, + UserStorageController: { + isAccountSyncingReadyToBeDispatched: true, + }, + }, + }; + const expectedData = { + OnboardingController: { + completedOnboarding: false, + }, + UserStorageController: { + isAccountSyncingReadyToBeDispatched: false, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + }); +}); diff --git a/app/scripts/migrations/137.ts b/app/scripts/migrations/137.ts new file mode 100644 index 000000000000..2e45bd6db502 --- /dev/null +++ b/app/scripts/migrations/137.ts @@ -0,0 +1,75 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +export type VersionedData = { + meta: { + version: number; + }; + data: { + OnboardingController?: { + completedOnboarding?: boolean; + }; + UserStorageController?: { + isAccountSyncingReadyToBeDispatched?: boolean; + }; + }; +}; + +export const version = 137; + +function transformState(state: VersionedData['data']) { + if ( + !hasProperty(state, 'OnboardingController') || + !isObject(state.OnboardingController) + ) { + global.sentry?.captureException?.( + new Error( + `Invalid OnboardingController state: ${typeof state.OnboardingController}`, + ), + ); + return state; + } + + if ( + !hasProperty(state, 'UserStorageController') || + !isObject(state.UserStorageController) + ) { + global.sentry?.captureException?.( + new Error( + `Invalid UserStorageController state: ${typeof state.UserStorageController}`, + ), + ); + return state; + } + + const { OnboardingController } = state; + + const currentCompletedOnboardingStatus = + OnboardingController.completedOnboarding; + + if (currentCompletedOnboardingStatus) { + state.UserStorageController.isAccountSyncingReadyToBeDispatched = true; + } else { + state.UserStorageController.isAccountSyncingReadyToBeDispatched = false; + } + + return state; +} + +/** + * This migration sets isAccountSyncingReadyToBeDispatched to true if completedOnboarding is true + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} diff --git a/app/scripts/migrations/138.test.ts b/app/scripts/migrations/138.test.ts new file mode 100644 index 000000000000..1084d6b08136 --- /dev/null +++ b/app/scripts/migrations/138.test.ts @@ -0,0 +1,79 @@ +import { migrate, version } from './138'; + +const oldVersion = 137; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + describe(`migration #${version}`, () => { + it('removes the redesignedTransactionsEnabled preference if it is set to true', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + preferences: { + redesignedTransactionsEnabled: true, + }, + }, + }, + }; + const expectedData = { + PreferencesController: { + preferences: {}, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + + it('removes the redesignedTransactionsEnabled preference if it is set to false', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + preferences: { + redesignedTransactionsEnabled: false, + }, + }, + }, + }; + const expectedData = { + PreferencesController: { + preferences: {}, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + + it('does nothing to other PreferencesController state if there is not a redesignedTransactionsEnabled preference', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + existingPreference: true, + }, + }, + }; + + const expectedData = { + PreferencesController: { + existingPreference: true, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + }); +}); diff --git a/app/scripts/migrations/138.ts b/app/scripts/migrations/138.ts new file mode 100644 index 000000000000..a4a51348bffc --- /dev/null +++ b/app/scripts/migrations/138.ts @@ -0,0 +1,47 @@ +import { hasProperty } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 138; + +/** + * This migration deletes the preference `redesignedTransactionsEnabled` if the + * user has existing data. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const preferencesControllerState = state?.PreferencesController as + | Record + | undefined; + + const preferences = preferencesControllerState?.preferences as + | Record + | undefined; + + if ( + preferences && + hasProperty(preferences, 'redesignedTransactionsEnabled') + ) { + delete preferences.redesignedTransactionsEnabled; + } +} diff --git a/app/scripts/migrations/139.test.ts b/app/scripts/migrations/139.test.ts new file mode 100644 index 000000000000..6291e72de241 --- /dev/null +++ b/app/scripts/migrations/139.test.ts @@ -0,0 +1,1253 @@ +import { migrate, version } from './139'; + +const PermissionNames = { + eth_accounts: 'eth_accounts', + permittedChains: 'endowment:permitted-chains', +}; + +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + +const oldVersion = 138; + +describe('migration #139', () => { + afterEach(() => jest.resetAllMocks()); + + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if PermissionController state is missing', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + NetworkController: {}, + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if PermissionController state is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: 'foo', + NetworkController: {}, + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.PermissionController is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController state is missing', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: {}, + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController is undefined`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController state is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: {}, + NetworkController: 'foo', + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if SelectedNetworkController state is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: {}, + NetworkController: {}, + SelectedNetworkController: 'foo', + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.SelectedNetworkController is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if PermissionController.subjects is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: 'foo', + }, + NetworkController: {}, + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.PermissionController.subjects is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController.selectedNetworkClientId is not a non-empty string', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: {}, + }, + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController.selectedNetworkClientId is object`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController.networkConfigurationsByChainId is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: 'foo', + }, + SelectedNetworkController: {}, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if SelectedNetworkController.domains is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: {}, + }, + SelectedNetworkController: { + domains: 'foo', + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.SelectedNetworkController.domains is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController.networkConfigurationsByChainId[] is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: 'nonExistentNetworkClientId', + networkConfigurationsByChainId: { + '0x1': 'foo', + }, + }, + SelectedNetworkController: { + domains: {}, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId["0x1"] is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController.networkConfigurationsByChainId[].rpcEndpoints is not an array', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: 'nonExistentNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + rpcEndpoints: 'foo', + }, + }, + }, + SelectedNetworkController: { + domains: {}, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId["0x1"].rpcEndpoints is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if NetworkController.networkConfigurationsByChainId[].rpcEndpoints[] is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: 'nonExistentNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + rpcEndpoints: ['foo'], + }, + }, + }, + SelectedNetworkController: { + domains: {}, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId["0x1"].rpcEndpoints[] is string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if the currently selected network client is neither built in nor exists in NetworkController.networkConfigurationsByChainId', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PermissionController: { + subjects: {}, + }, + NetworkController: { + selectedNetworkClientId: 'nonExistentNetworkClientId', + networkConfigurationsByChainId: {}, + }, + SelectedNetworkController: { + domains: {}, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: No chainId found for selectedNetworkClientId "nonExistentNetworkClientId"`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if a subject is not an object', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: {}, + }, + SelectedNetworkController: { + domains: {}, + }, + PermissionController: { + subjects: { + 'test.com': 'foo', + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: Invalid subject for origin "test.com" of type string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it("does nothing if a subject's permissions is not an object", async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: {}, + }, + SelectedNetworkController: { + domains: {}, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: 'foo', + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: Invalid permissions for origin "test.com" of type string`, + ), + ); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if neither eth_accounts nor permittedChains permissions have been granted', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: {}, + }, + SelectedNetworkController: { + domains: {}, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: {}, + }, + SelectedNetworkController: { + domains: {}, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + }, + }, + }, + }, + }); + }); + + // @ts-expect-error This function is missing from the Mocha type definitions + describe.each([ + [ + 'built-in', + { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: {}, + }, + '1', + ], + [ + 'custom', + { + selectedNetworkClientId: 'customId', + networkConfigurationsByChainId: { + '0xf': { + rpcEndpoints: [ + { + networkClientId: 'customId', + }, + ], + }, + }, + }, + '15', + ], + ])( + 'the currently selected network client is %s', + ( + _type: string, + NetworkController: { + networkConfigurationsByChainId: Record< + string, + { + rpcEndpoints: { networkClientId: string }[]; + } + >; + } & Record, + chainId: string, + ) => { + const baseData = () => ({ + PermissionController: { + subjects: {}, + }, + NetworkController, + SelectedNetworkController: { + domains: {}, + }, + }); + const baseEthAccountsPermissionMetadata = { + id: '1', + date: 2, + invoker: 'test.com', + parentCapability: PermissionNames.eth_accounts, + }; + const currentScope = `eip155:${chainId}`; + + it('does nothing when eth_accounts and permittedChains permissions are missing metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + invoker: 'test.com', + parentCapability: PermissionNames.eth_accounts, + date: 2, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + [PermissionNames.permittedChains]: { + invoker: 'test.com', + parentCapability: PermissionNames.permittedChains, + date: 2, + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0xa', '0x64'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing when there are malformed network configurations (even if there is a valid networkConfiguration that matches the selected network client)', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurationsByChainId: { + '0x1': { + rpcEndpoints: [ + { + networkClientId: 'mainnet', + }, + ], + }, + '0xInvalid': 'invalid-network-configuration', + '0xa': { + rpcEndpoints: [ + { + networkClientId: 'bar', + }, + ], + }, + }, + }, + SelectedNetworkController: { + domains: { + 'test.com': 'bar', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('replaces the eth_accounts permission with a CAIP-25 permission using the eth_accounts value for the currently selected chain id when the origin does not have its own network client', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + [currentScope]: { + accounts: [ + `${currentScope}:0xdeadbeef`, + `${currentScope}:0x999`, + ], + }, + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0xdeadbeef', + 'wallet:eip155:0x999', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + + it('replaces the eth_accounts permission with a CAIP-25 permission using the globally selected chain id value for the currently selected chain id when the origin does have its own network client that cannot be resolved', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + SelectedNetworkController: { + domains: { + 'test.com': 'doesNotExist', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: No chainId found for networkClientIdForOrigin "doesNotExist"`, + ), + ); + + expect(newStorage.data).toStrictEqual({ + ...baseData(), + SelectedNetworkController: { + domains: { + 'test.com': 'doesNotExist', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + [currentScope]: { + accounts: [ + `${currentScope}:0xdeadbeef`, + `${currentScope}:0x999`, + ], + }, + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0xdeadbeef', + 'wallet:eip155:0x999', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + + it('replaces the eth_accounts permission with a CAIP-25 permission using the eth_accounts value for the origin chain id when the origin does have its own network client and it exists in the built-in networks', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + SelectedNetworkController: { + domains: { + 'test.com': 'sepolia', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + SelectedNetworkController: { + domains: { + 'test.com': 'sepolia', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:11155111': { + accounts: [ + 'eip155:11155111:0xdeadbeef', + 'eip155:11155111:0x999', + ], + }, + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0xdeadbeef', + 'wallet:eip155:0x999', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + + it('replaces the eth_accounts permission with a CAIP-25 permission using the eth_accounts value without permitted chains when the origin is snapId', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + PermissionController: { + subjects: { + 'npm:snap': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + PermissionController: { + subjects: { + 'npm:snap': { + permissions: { + unrelated: { + foo: 'bar', + }, + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0xdeadbeef', + 'wallet:eip155:0x999', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + + it('replaces the eth_accounts permission with a CAIP-25 permission using the eth_accounts value for the origin chain id when the origin does have its own network client and it exists in the custom configurations', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + NetworkController: { + ...baseData().NetworkController, + networkConfigurationsByChainId: { + ...baseData().NetworkController.networkConfigurationsByChainId, + '0xa': { + rpcEndpoints: [ + { + networkClientId: 'customNetworkClientId', + }, + ], + }, + }, + }, + SelectedNetworkController: { + domains: { + 'test.com': 'customNetworkClientId', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + NetworkController: { + ...baseData().NetworkController, + networkConfigurationsByChainId: { + ...baseData().NetworkController.networkConfigurationsByChainId, + '0xa': { + rpcEndpoints: [ + { + networkClientId: 'customNetworkClientId', + }, + ], + }, + }, + }, + SelectedNetworkController: { + domains: { + 'test.com': 'customNetworkClientId', + }, + }, + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:10': { + accounts: [ + 'eip155:10:0xdeadbeef', + 'eip155:10:0x999', + ], + }, + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0xdeadbeef', + 'wallet:eip155:0x999', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + + it('does not create a CAIP-25 permission when eth_accounts permission is missing', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.permittedChains]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0xa', '0x64'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + }, + }, + }, + }, + }); + }); + + it('replaces both eth_accounts and permittedChains permission with a CAIP-25 permission using the values from both permissions', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef', '0x999'], + }, + ], + }, + [PermissionNames.permittedChains]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0xa', '0x64'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + unrelated: { + foo: 'bar', + }, + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:10': { + accounts: [ + 'eip155:10:0xdeadbeef', + 'eip155:10:0x999', + ], + }, + 'eip155:100': { + accounts: [ + 'eip155:100:0xdeadbeef', + 'eip155:100:0x999', + ], + }, + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0xdeadbeef', + 'wallet:eip155:0x999', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + + it('replaces permissions for each subject', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef'], + }, + ], + }, + }, + }, + 'test2.com': { + permissions: { + [PermissionNames.eth_accounts]: { + ...baseEthAccountsPermissionMetadata, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef'], + }, + ], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + ...baseData(), + PermissionController: { + subjects: { + 'test.com': { + permissions: { + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + [currentScope]: { + accounts: [`${currentScope}:0xdeadbeef`], + }, + 'wallet:eip155': { + accounts: ['wallet:eip155:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + 'test2.com': { + permissions: { + 'endowment:caip25': { + ...baseEthAccountsPermissionMetadata, + parentCapability: 'endowment:caip25', + caveats: [ + { + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + [currentScope]: { + accounts: [`${currentScope}:0xdeadbeef`], + }, + 'wallet:eip155': { + accounts: ['wallet:eip155:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + }); + }, + ); +}); diff --git a/app/scripts/migrations/139.ts b/app/scripts/migrations/139.ts new file mode 100644 index 000000000000..4a9be59a58ce --- /dev/null +++ b/app/scripts/migrations/139.ts @@ -0,0 +1,430 @@ +import { hasProperty, hexToBigInt, isObject } from '@metamask/utils'; +import type { + CaipChainId, + CaipAccountId, + Json, + Hex, + NonEmptyArray, +} from '@metamask/utils'; +import { cloneDeep } from 'lodash'; +import type { + Caveat, + PermissionConstraint, + ValidPermission, +} from '@metamask/permission-controller'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 139; + +// In-lined from @metamask/multichain +const Caip25CaveatType = 'authorizedScopes'; +const Caip25EndowmentPermissionName = 'endowment:caip25'; + +type InternalScopeObject = { + accounts: CaipAccountId[]; +}; + +type InternalScopesObject = Record; + +type Caip25CaveatValue = { + requiredScopes: InternalScopesObject; + optionalScopes: InternalScopesObject; + sessionProperties?: Record; + isMultichainOrigin: boolean; +}; + +// Locally defined types +type Caip25Caveat = Caveat; +type Caip25Permission = ValidPermission< + typeof Caip25EndowmentPermissionName, + Caip25Caveat +>; + +const PermissionNames = { + eth_accounts: 'eth_accounts', + permittedChains: 'endowment:permitted-chains', +} as const; + +// a map of the networks built into the extension at the time of this migration to their chain IDs +// copied from shared/constants/network.ts (https://github.com/MetaMask/metamask-extension/blob/5b5c04a16fb7937a6e9d59b1debe4713978ef39d/shared/constants/network.ts#L535) +const BUILT_IN_NETWORKS: ReadonlyMap = new Map([ + ['sepolia', '0xaa36a7'], + ['mainnet', '0x1'], + ['linea-sepolia', '0xe705'], + ['linea-mainnet', '0xe708'], +]); + +const snapsPrefixes = ['npm:', 'local:'] as const; + +function isPermissionConstraint(obj: unknown): obj is PermissionConstraint { + return ( + isObject(obj) && + obj !== null && + hasProperty(obj, 'caveats') && + Array.isArray(obj.caveats) && + obj.caveats.length > 0 && + hasProperty(obj, 'date') && + typeof obj.date === 'number' && + hasProperty(obj, 'id') && + typeof obj.id === 'string' && + hasProperty(obj, 'invoker') && + typeof obj.invoker === 'string' && + hasProperty(obj, 'parentCapability') && + typeof obj.parentCapability === 'string' + ); +} + +function isNonEmptyArrayOfStrings(obj: unknown): obj is NonEmptyArray { + return ( + Array.isArray(obj) && + obj.length > 0 && + obj.every((item) => typeof item === 'string') + ); +} + +/** + * This migration transforms `eth_accounts` and `permittedChains` permissions into + * an equivalent CAIP-25 permission. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + + const newState = transformState(versionedData.data); + versionedData.data = newState as Record; + return versionedData; +} + +function transformState(oldState: Record) { + const newState = cloneDeep(oldState); + if (!hasProperty(newState, 'PermissionController')) { + return oldState; + } + + if (!isObject(newState.PermissionController)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.PermissionController is ${typeof newState.PermissionController}`, + ), + ); + return oldState; + } + + if ( + !hasProperty(newState, 'NetworkController') || + !isObject(newState.NetworkController) + ) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.NetworkController is ${typeof newState.NetworkController}`, + ), + ); + return oldState; + } + + if (!hasProperty(newState, 'SelectedNetworkController')) { + console.warn( + `Migration ${version}: typeof state.SelectedNetworkController is ${typeof newState.SelectedNetworkController}`, + ); + // This matches how the `SelectedNetworkController` is initialized + // See https://github.com/MetaMask/core/blob/e692641040be470f7f4ad2d58692b0668e6443b3/packages/selected-network-controller/src/SelectedNetworkController.ts#L27 + newState.SelectedNetworkController = { + domains: {}, + }; + } + + if (!isObject(newState.SelectedNetworkController)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.SelectedNetworkController is ${typeof newState.SelectedNetworkController}`, + ), + ); + return oldState; + } + + const { + NetworkController: { + selectedNetworkClientId, + networkConfigurationsByChainId, + }, + PermissionController: { subjects }, + } = newState; + + if (!isObject(subjects)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.PermissionController.subjects is ${typeof subjects}`, + ), + ); + return oldState; + } + + if (!selectedNetworkClientId || typeof selectedNetworkClientId !== 'string') { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.NetworkController.selectedNetworkClientId is ${typeof selectedNetworkClientId}`, + ), + ); + return oldState; + } + + if (!isObject(networkConfigurationsByChainId)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId is ${typeof newState + .NetworkController.networkConfigurationsByChainId}`, + ), + ); + return oldState; + } + + if ( + !hasProperty(newState.SelectedNetworkController, 'domains') || + !isObject(newState.SelectedNetworkController.domains) + ) { + const { domains } = newState.SelectedNetworkController; + global.sentry?.captureException?.( + new Error( + `Migration ${version}: typeof state.SelectedNetworkController.domains is ${typeof domains}`, + ), + ); + return oldState; + } + + const { domains } = newState.SelectedNetworkController; + + const getChainIdForNetworkClientId = ( + networkClientId: string, + propertyName: string, + ): string | undefined => { + let malformedDataErrorFound = false; + let matchingChainId: string | undefined; + for (const [chainId, networkConfiguration] of Object.entries( + networkConfigurationsByChainId, + )) { + if (!isObject(networkConfiguration)) { + global.sentry?.captureException( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId["${chainId}"] is ${typeof networkConfiguration}`, + ), + ); + malformedDataErrorFound = true; + continue; + } + if (!Array.isArray(networkConfiguration.rpcEndpoints)) { + global.sentry?.captureException( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId["${chainId}"].rpcEndpoints is ${typeof networkConfiguration.rpcEndpoints}`, + ), + ); + malformedDataErrorFound = true; + continue; + } + + for (const rpcEndpoint of networkConfiguration.rpcEndpoints) { + if (!isObject(rpcEndpoint)) { + global.sentry?.captureException( + new Error( + `Migration ${version}: typeof state.NetworkController.networkConfigurationsByChainId["${chainId}"].rpcEndpoints[] is ${typeof rpcEndpoint}`, + ), + ); + malformedDataErrorFound = true; + continue; + } + + if (rpcEndpoint.networkClientId === networkClientId) { + matchingChainId = chainId; + } + } + } + if (malformedDataErrorFound) { + return undefined; + } + + if (matchingChainId) { + return matchingChainId; + } + + const builtInChainId = BUILT_IN_NETWORKS.get(networkClientId); + if (!builtInChainId) { + global.sentry?.captureException( + new Error( + `Migration ${version}: No chainId found for ${propertyName} "${networkClientId}"`, + ), + ); + } + return builtInChainId; + }; + + const currentChainId = getChainIdForNetworkClientId( + selectedNetworkClientId, + 'selectedNetworkClientId', + ); + if (!currentChainId) { + return oldState; + } + + // perform mutations on the cloned state + for (const [origin, subject] of Object.entries(subjects)) { + if (!isObject(subject)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid subject for origin "${origin}" of type ${typeof subject}`, + ), + ); + return oldState; + } + + if ( + !hasProperty(subject, 'permissions') || + !isObject(subject.permissions) + ) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid permissions for origin "${origin}" of type ${typeof subject.permissions}`, + ), + ); + return oldState; + } + + const { permissions } = subject; + + let basePermission: PermissionConstraint | undefined; + + let ethAccounts: string[] = []; + const ethAccountsPermission = permissions[PermissionNames.eth_accounts]; + const permittedChainsPermission = + permissions[PermissionNames.permittedChains]; + + // if there is no eth_accounts permission we can't create a valid CAIP-25 permission so we remove the permission + if (permittedChainsPermission && !ethAccountsPermission) { + delete permissions[PermissionNames.permittedChains]; + continue; + } + if (!isPermissionConstraint(ethAccountsPermission)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid state.PermissionController.subjects[${origin}].permissions[${ + PermissionNames.eth_accounts + }: ${JSON.stringify(ethAccountsPermission)}`, + ), + ); + return oldState; + } + const accountsCaveatValue = ethAccountsPermission.caveats?.[0]?.value; + if (!isNonEmptyArrayOfStrings(accountsCaveatValue)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid state.PermissionController.subjects[${origin}].permissions[${ + PermissionNames.eth_accounts + }].caveats[0].value of type ${typeof ethAccountsPermission + .caveats?.[0]?.value}`, + ), + ); + return oldState; + } + ethAccounts = accountsCaveatValue; + basePermission = ethAccountsPermission; + + delete permissions[PermissionNames.eth_accounts]; + + let chainIds: string[] = []; + // this permission is new so it may not exist + if (permittedChainsPermission) { + if (!isPermissionConstraint(permittedChainsPermission)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid state.PermissionController.subjects[${origin}].permissions[${ + PermissionNames.permittedChains + }]: ${JSON.stringify(permittedChainsPermission)}`, + ), + ); + return oldState; + } + const chainsCaveatValue = permittedChainsPermission.caveats?.[0]?.value; + if (!isNonEmptyArrayOfStrings(chainsCaveatValue)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid state.PermissionController.subjects[${origin}].permissions[${ + PermissionNames.permittedChains + }].caveats[0].value of type ${typeof permittedChainsPermission + .caveats?.[0]?.value}`, + ), + ); + return oldState; + } + chainIds = chainsCaveatValue; + basePermission ??= permittedChainsPermission; + delete permissions[PermissionNames.permittedChains]; + } + + if (chainIds.length === 0) { + chainIds = [currentChainId]; + + const networkClientIdForOrigin = domains[origin]; + if ( + networkClientIdForOrigin && + typeof networkClientIdForOrigin === 'string' + ) { + const chainIdForOrigin = getChainIdForNetworkClientId( + networkClientIdForOrigin, + 'networkClientIdForOrigin', + ); + if (chainIdForOrigin) { + chainIds = [chainIdForOrigin]; + } + } + } + + const isSnap = snapsPrefixes.some((prefix) => origin.startsWith(prefix)); + const scopes: InternalScopesObject = {}; + const scopeStrings: CaipChainId[] = isSnap + ? [] + : chainIds.map( + (chainId) => `eip155:${hexToBigInt(chainId).toString(10)}`, + ); + scopeStrings.push('wallet:eip155'); + + scopeStrings.forEach((scopeString) => { + const caipAccounts = ethAccounts.map( + (account) => `${scopeString}:${account}`, + ); + scopes[scopeString] = { + accounts: caipAccounts, + }; + }); + + const caip25Permission: Caip25Permission = { + ...basePermission, + parentCapability: Caip25EndowmentPermissionName, + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: scopes, + isMultichainOrigin: false, + }, + }, + ], + }; + + permissions[Caip25EndowmentPermissionName] = caip25Permission; + } + + return newState; +} diff --git a/app/scripts/migrations/140.test.ts b/app/scripts/migrations/140.test.ts new file mode 100644 index 000000000000..5127652098cd --- /dev/null +++ b/app/scripts/migrations/140.test.ts @@ -0,0 +1,305 @@ +import { omit } from 'lodash'; +import { migrate, version } from './140'; + +const oldVersion = 139; + +const dataWithAllControllerProperties = { + AppStateController: {}, + NetworkController: {}, + PreferencesController: {}, +}; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if state has no AppStateController property, even if it has other relevant properties', async () => { + const data = omit(dataWithAllControllerProperties, 'AppStateController'); + const oldStorage = { + meta: { version: oldVersion }, + data, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(data); + }); + + it('deletes AppStateController.collectiblesDropdownState from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppStateController: { + collectiblesDropdownState: 'test', + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + AppStateController: {}, + }); + }); + + it('deletes AppStateController.serviceWorkerLastActiveTime from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppStateController: { + serviceWorkerLastActiveTime: 5, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + AppStateController: {}, + }); + }); + + it('deletes AppStateController.showPortfolioTooltip from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppStateController: { + showPortfolioTooltip: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + AppStateController: {}, + }); + }); + + it('does nothing if state has no NetworkController property, even if it has other relevant properties', async () => { + const data = omit(dataWithAllControllerProperties, 'NetworkController'); + const oldStorage = { + meta: { version: oldVersion }, + data, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(data); + }); + + it('deletes NetworkController.networkConfigurations from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + NetworkController: { + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + doesnt: 'matter', + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + NetworkController: {}, + }); + }); + + it('deletes NetworkController.providerConfig from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + NetworkController: { + providerConfig: { + doesnt: 'matter', + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + NetworkController: {}, + }); + }); + + it('does nothing if state has no PreferencesController property, even if it has other relevant properties', async () => { + const data = omit(dataWithAllControllerProperties, 'PreferencesController'); + const oldStorage = { + meta: { version: oldVersion }, + data, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(data); + }); + + it('deletes PreferencesController.customNetworkListEnabled from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + customNetworkListEnabled: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.disabledRpcMethodPreferences from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + disabledRpcMethodPreferences: { + doesnt: 'matter', + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.eip1559V2Enabled from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + eip1559V2Enabled: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.hasDismissedOpenSeaToBlockaidBanner from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + hasDismissedOpenSeaToBlockaidBanner: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.hasMigratedFromOpenSeaToBlockaid from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + hasMigratedFromOpenSeaToBlockaid: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.improvedTokenAllowanceEnabled from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + improvedTokenAllowanceEnabled: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.infuraBlocked from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + infuraBlocked: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.useCollectibleDetection from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useCollectibleDetection: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); + + it('deletes PreferencesController.useStaticTokenList from state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useStaticTokenList: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + PreferencesController: {}, + }); + }); +}); diff --git a/app/scripts/migrations/140.ts b/app/scripts/migrations/140.ts new file mode 100644 index 000000000000..101bc83dd08f --- /dev/null +++ b/app/scripts/migrations/140.ts @@ -0,0 +1,89 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 140; + +/** + * This migration deletes properties from state which have been removed in + * previous commits. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if ( + hasProperty(state, 'AppStateController') && + isObject(state.AppStateController) + ) { + // Removed in 33cc8d587aad05c0b41871ba3676676a3ce5680e with a migration, but + // still persists for some people for some reason + // See https://metamask.sentry.io/issues/6223008336/events/723c5195130e4c5584b53a6656a85595/ + delete state.AppStateController.collectiblesDropdownState; + // Removed in 4ea52511eb7934bf0ce6b9b7d570a525120229ce + delete state.AppStateController.serviceWorkerLastActiveTime; + // Removed in 24e0a9030b1a715a008e0c5dfaf9c552bcdb304e with a migration, but + // still persists for some people for some reason + // See https://metamask.sentry.io/issues/6223008336/events/a2cc42d6ed79485a8b2e9072d8033720/ + delete state.AppStateController.showPortfolioTooltip; + } + + if ( + hasProperty(state, 'NetworkController') && + isObject(state.NetworkController) + ) { + // Removed in 555d42b9ead0f4919356ff16e11c663c5e38639e + delete state.NetworkController.networkConfigurations; + // Removed in 800a9d3a177446ff2d05e3e95ec06b3658474207 with a migration, but + // still persists for some people for some reason + // See: https://metamask.sentry.io/issues/6011869130/events/039861ddb07f4b39b947edba3bbd710e/ + delete state.NetworkController.providerConfig; + } + + if ( + hasProperty(state, 'PreferencesController') && + isObject(state.PreferencesController) + ) { + // Removed in 6c84e9604c7160dd91c685f301f3c8bd128ad3e3 + delete state.PreferencesController.customNetworkListEnabled; + // Removed in e6ecd956b054a29481071e4eded2f8cd17d137d2 + delete state.PreferencesController.disabledRpcMethodPreferences; + // Removed in 8125473dc53476b6685c5e85918f89bce87e3006 + delete state.PreferencesController.eip1559V2Enabled; + // Removed in 699ddccc76302df6130835dc6655077806bf6335 + delete state.PreferencesController.hasDismissedOpenSeaToBlockaidBanner; + // I could find references to this in the commit history, but don't know + // where it was removed + delete state.PreferencesController.hasMigratedFromOpenSeaToBlockaid; + // Removed in f988dc1c5ef98ec72212d1f58e736556273b68f7 + delete state.PreferencesController.improvedTokenAllowanceEnabled; + // Removed in 315c043785cd5d7a4b0f7e974097ccac18a6b241 + delete state.PreferencesController.infuraBlocked; + // Removed in 4f66dc948fee54b8491227414342ab0d373475f1 with a migration, but + // still persists for some people for some reason + // See: https://metamask.sentry.io/issues/6042074159/events/5711f95785d741739e5d0fa5ad19e7c0/ + delete state.PreferencesController.useCollectibleDetection; + // Removed in eb987a47b51ce410de0047ec883bb4549ce80c85 + delete state.PreferencesController.useStaticTokenList; + } + + return state; +} diff --git a/app/scripts/migrations/141.test.ts b/app/scripts/migrations/141.test.ts new file mode 100644 index 000000000000..ce7793269b1b --- /dev/null +++ b/app/scripts/migrations/141.test.ts @@ -0,0 +1,79 @@ +import { migrate, version } from './141'; + +const oldVersion = 140; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + describe(`migration #${version}`, () => { + it('removes the redesignedConfirmationsEnabled preference if it is set to true', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + preferences: { + redesignedConfirmationsEnabled: true, + }, + }, + }, + }; + const expectedData = { + PreferencesController: { + preferences: {}, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + + it('removes the redesignedConfirmationsEnabled preference if it is set to false', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + preferences: { + redesignedConfirmationsEnabled: false, + }, + }, + }, + }; + const expectedData = { + PreferencesController: { + preferences: {}, + }, + }; + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + + it('does nothing to other PreferencesController state if there is not a redesignedConfirmationsEnabled preference', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + existingPreference: true, + }, + }, + }; + + const expectedData = { + PreferencesController: { + existingPreference: true, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(expectedData); + }); + }); +}); diff --git a/app/scripts/migrations/141.ts b/app/scripts/migrations/141.ts new file mode 100644 index 000000000000..7243a853b831 --- /dev/null +++ b/app/scripts/migrations/141.ts @@ -0,0 +1,47 @@ +import { hasProperty } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 141; + +/** + * This migration deletes the preference `redesignedConfirmationsEnabled` if the + * user has existing data. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const preferencesControllerState = state?.PreferencesController as + | Record + | undefined; + + const preferences = preferencesControllerState?.preferences as + | Record + | undefined; + + if ( + preferences && + hasProperty(preferences, 'redesignedConfirmationsEnabled') + ) { + delete preferences.redesignedConfirmationsEnabled; + } +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 6cde292ba55d..08fcb604a87b 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -152,7 +152,19 @@ const migrations = [ require('./129'), require('./130'), require('./131'), + require('./131.1'), require('./132'), + require('./133'), + require('./133.1'), + require('./133.2'), + require('./134'), + require('./135'), + require('./136'), + require('./137'), + require('./138'), + require('./139'), + require('./140'), + require('./141'), ]; export default migrations; diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 794037e12761..5f59709f920f 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -1,7 +1,11 @@ // Disabled to allow setting up initial state hooks first +// This import sets up safe intrinsics required for LavaDome to function securely. +// It must be run before any less trusted code so that no such code can undermine it. +import '@lavamoat/lavadome-react'; + // This import sets up global functions required for Sentry to function. -// It must be run first in case an error is thrown later during initialization. +// It must be run as soon as possible in case an error is thrown later during initialization. import './lib/setup-initial-state-hooks'; import '../../development/wdyr'; @@ -11,8 +15,6 @@ import 'react-devtools'; import PortStream from 'extension-port-stream'; import browser from 'webextension-polyfill'; -import Eth from '@metamask/ethjs'; -import EthQuery from '@metamask/eth-query'; import StreamProvider from 'web3-stream-provider'; import log from 'loglevel'; // TODO: Remove restricted import @@ -362,8 +364,6 @@ function setupWeb3Connection(connectionStream) { connectionStream.on('error', console.error.bind(console)); providerStream.on('error', console.error.bind(console)); global.ethereumProvider = providerStream; - global.ethQuery = new EthQuery(providerStream); - global.eth = new Eth(providerStream); } /** diff --git a/attribution.txt b/attribution.txt index 364128f620f3..bcf9b961de51 100644 --- a/attribution.txt +++ b/attribution.txt @@ -5044,33 +5044,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -****************************** - -cross-spawn -7.0.3 -The MIT License (MIT) - -Copyright (c) 2018 Made With MOXY Lda - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ****************************** crypto diff --git a/builds.yml b/builds.yml index 75f6f4b462c6..efecdbb52466 100644 --- a/builds.yml +++ b/builds.yml @@ -27,7 +27,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.12.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -48,7 +48,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.12.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Modifies how the version is displayed. # eg. instead of 10.25.0 -> 10.25.0-beta.2 @@ -64,13 +64,15 @@ buildTypes: features: - build-flask - keyring-snaps + - solana + - solana-swaps env: - INFURA_FLASK_PROJECT_ID - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - REJECT_INVALID_SNAPS_PLATFORM_VERSION: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.12.0/index.html - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -94,7 +96,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.12.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://support.metamask-institutional.io - SUPPORT_REQUEST_LINK: https://support.metamask-institutional.io @@ -138,6 +140,7 @@ features: solana: assets: - ./{app,shared,ui}/**/solana/** + solana-swaps: # Env variables that are required for all types of builds # @@ -213,13 +216,9 @@ env: # API keys to 3rd party services ### - - PUBNUB_PUB_KEY: null - - PUBNUB_SUB_KEY: null - SEGMENT_HOST: null - SENTRY_DSN: null - SENTRY_DSN_DEV: null - - OPENSEA_KEY: null - - ETHERSCAN_KEY: null # also INFURA_PROJECT_ID below ### @@ -235,10 +234,8 @@ env: # Used to enable confirmation redesigned pages - ENABLE_CONFIRMATION_REDESIGN: '' - # Used to enable signature decoding - - ENABLE_SIGNATURE_DECODING: '' # URL of the decoding API used to provide additional data from signature requests - - DECODING_API_URL: null + - DECODING_API_URL: 'https://signature-insights.api.cx.metamask.io/v1' # Determines if feature flagged Settings Page - Developer Options should be used - ENABLE_SETTINGS_PAGE_DEV_OPTIONS: false # Used for debugging changes to the phishing warning page. @@ -278,14 +275,12 @@ env: - NODE_DEBUG: '' # Used by react-devtools-core - EDITOR_URL: '' - # Determines if feature flagged Multichain Transactions should be used - - TRANSACTION_MULTICHAIN: '' # Determines if Barad Dur features should be used - BARAD_DUR: '' # Determines if feature flagged Chain permissions - CHAIN_PERMISSIONS: '' # Determines if Portfolio View UI should be shown - - PORTFOLIO_VIEW: '' + - PORTFOLIO_VIEW: 'true' # Enables use of test gas fee flow to debug gas fee estimation - TEST_GAS_FEE_FLOWS: false # Temporary mechanism to enable security alerts API prior to release @@ -321,3 +316,10 @@ env: # This should NEVER be enabled in production since it slows down react ### - ENABLE_WHY_DID_YOU_RENDER: false + + ### + # Unused environment variables referenced in dependencies + # Unset environment variables cause a build error. These are set to `null` to tell our build + # system that they are intentionally unset. + ### + - ETHERSCAN_KEY: null # Used by `gridplus-sdk/dist/util.js` diff --git a/coverage.json b/coverage.json index 9887e06e2db6..6035036ba23b 100644 --- a/coverage.json +++ b/coverage.json @@ -1 +1 @@ -{ "coverage": 71 } +{ "coverage": 72 } diff --git a/development/build/README.md b/development/build/README.md index 9d5df1e149fc..3aecead0cb25 100644 --- a/development/build/README.md +++ b/development/build/README.md @@ -4,7 +4,7 @@ > Add `--build-type flask` to build Flask, our canary distribution with more experimental features. This directory contains the MetaMask build system, which is used to build the MetaMask Extension such that it can be used in a supported browser. -From the repository root, the build system entry file is located at [`./development/build/index.js`](https://github.com/MetaMask/metamask-extension/blob/develop/development/build/index.js). +From the repository root, the build system entry file is located at [`./development/build/index.js`](https://github.com/MetaMask/metamask-extension/blob/main/development/build/index.js). Several package scripts invoke the build system. For example, `yarn start` creates a watched development build, and `yarn dist` creates a production build. @@ -20,7 +20,7 @@ are written to the `./dist` directory. Our JavaScript source files are transformed using [Babel](https://babeljs.io/), specifically using the [`babelify`](https://npmjs.com/package/babelify) Browserify transform. -Source file bundling tasks are implemented in the [`./development/build/scripts.js`](https://github.com/MetaMask/metamask-extension/blob/develop/development/build/scripts.js). +Source file bundling tasks are implemented in the [`./development/build/scripts.js`](https://github.com/MetaMask/metamask-extension/blob/main/development/build/scripts.js). > Locally implemented Browserify transforms, _some of which affect how we write JavaScript_, are listed and documented [here](./transforms/README.md). diff --git a/development/build/task.js b/development/build/task.js index cbbc1ea22578..edb1b6761cd8 100644 --- a/development/build/task.js +++ b/development/build/task.js @@ -1,5 +1,5 @@ const EventEmitter = require('events'); -const spawn = require('cross-spawn'); +const { spawn } = require('node:child_process'); const tasks = {}; const taskEvents = new EventEmitter(); @@ -84,6 +84,7 @@ function runInChildProcess( ], { env: process.env, + shell: true, }, ); diff --git a/development/build/utils.js b/development/build/utils.js index 626aacd588c7..301c998534ad 100644 --- a/development/build/utils.js +++ b/development/build/utils.js @@ -130,7 +130,7 @@ function getEnvironment({ buildTarget }) { /^Version-v(\d+)[.](\d+)[.](\d+)/u.test(process.env.CIRCLE_BRANCH) ) { return ENVIRONMENT.RELEASE_CANDIDATE; - } else if (process.env.CIRCLE_BRANCH === 'develop') { + } else if (process.env.CIRCLE_BRANCH === 'main') { return ENVIRONMENT.STAGING; } else if (process.env.CIRCLE_PULL_REQUEST) { return ENVIRONMENT.PULL_REQUEST; diff --git a/development/charts/flamegraph/chart/index.html b/development/charts/flamegraph/chart/index.html deleted file mode 100644 index ce53076ad9e4..000000000000 --- a/development/charts/flamegraph/chart/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - Performance Measurements - - - - - -
-
- -

d3-flame-graph

-
-
-
-
-
- - - - - - - - - - - diff --git a/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js b/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js deleted file mode 100644 index cc042a0f281b..000000000000 --- a/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js +++ /dev/null @@ -1,3117 +0,0 @@ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if(typeof define === 'function' && define.amd) - define([], factory); - else if(typeof exports === 'object') - exports["flamegraph"] = factory(); - else - root["flamegraph"] = root["flamegraph"] || {}, root["flamegraph"]["tooltip"] = factory(); -})(self, function() { -return /******/ (() => { // webpackBootstrap -/******/ "use strict"; -/******/ // The require scope -/******/ var __webpack_require__ = {}; -/******/ -/************************************************************************/ -/******/ /* webpack/runtime/define property getters */ -/******/ (() => { -/******/ // define getter functions for harmony exports -/******/ __webpack_require__.d = (exports, definition) => { -/******/ for(var key in definition) { -/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { -/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); -/******/ } -/******/ } -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/hasOwnProperty shorthand */ -/******/ (() => { -/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) -/******/ })(); -/******/ -/******/ /* webpack/runtime/make namespace object */ -/******/ (() => { -/******/ // define __esModule on exports -/******/ __webpack_require__.r = (exports) => { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ })(); -/******/ -/************************************************************************/ -var __webpack_exports__ = {}; -// ESM COMPAT FLAG -__webpack_require__.r(__webpack_exports__); - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "defaultFlamegraphTooltip": () => (/* binding */ defaultFlamegraphTooltip) -}); - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selector.js -function none() {} - -/* harmony default export */ function selector(selector) { - return selector == null ? none : function() { - return this.querySelector(selector); - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/select.js - - - -/* harmony default export */ function selection_select(select) { - if (typeof select !== "function") select = selector(select); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { - if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { - if ("__data__" in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - } - } - } - - return new Selection(subgroups, this._parents); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/array.js -// Given something array like (or null), returns something that is strictly an -// array. This is used to ensure that array-like objects passed to d3.selectAll -// or selection.selectAll are converted into proper arrays when creating a -// selection; we don’t ever want to create a selection backed by a live -// HTMLCollection or NodeList. However, note that selection.selectAll will use a -// static NodeList as a group, since it safely derived from querySelectorAll. -function array(x) { - return x == null ? [] : Array.isArray(x) ? x : Array.from(x); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selectorAll.js -function empty() { - return []; -} - -/* harmony default export */ function selectorAll(selector) { - return selector == null ? empty : function() { - return this.querySelectorAll(selector); - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectAll.js - - - - -function arrayAll(select) { - return function() { - return array(select.apply(this, arguments)); - }; -} - -/* harmony default export */ function selectAll(select) { - if (typeof select === "function") select = arrayAll(select); - else select = selectorAll(select); - - for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - subgroups.push(select.call(node, node.__data__, i, group)); - parents.push(node); - } - } - } - - return new Selection(subgroups, parents); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/matcher.js -/* harmony default export */ function matcher(selector) { - return function() { - return this.matches(selector); - }; -} - -function childMatcher(selector) { - return function(node) { - return node.matches(selector); - }; -} - - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChild.js - - -var find = Array.prototype.find; - -function childFind(match) { - return function() { - return find.call(this.children, match); - }; -} - -function childFirst() { - return this.firstElementChild; -} - -/* harmony default export */ function selectChild(match) { - return this.select(match == null ? childFirst - : childFind(typeof match === "function" ? match : childMatcher(match))); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChildren.js - - -var filter = Array.prototype.filter; - -function children() { - return Array.from(this.children); -} - -function childrenFilter(match) { - return function() { - return filter.call(this.children, match); - }; -} - -/* harmony default export */ function selectChildren(match) { - return this.selectAll(match == null ? children - : childrenFilter(typeof match === "function" ? match : childMatcher(match))); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/filter.js - - - -/* harmony default export */ function selection_filter(match) { - if (typeof match !== "function") match = matcher(match); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Selection(subgroups, this._parents); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sparse.js -/* harmony default export */ function sparse(update) { - return new Array(update.length); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/enter.js - - - -/* harmony default export */ function enter() { - return new Selection(this._enter || this._groups.map(sparse), this._parents); -} - -function EnterNode(parent, datum) { - this.ownerDocument = parent.ownerDocument; - this.namespaceURI = parent.namespaceURI; - this._next = null; - this._parent = parent; - this.__data__ = datum; -} - -EnterNode.prototype = { - constructor: EnterNode, - appendChild: function(child) { return this._parent.insertBefore(child, this._next); }, - insertBefore: function(child, next) { return this._parent.insertBefore(child, next); }, - querySelector: function(selector) { return this._parent.querySelector(selector); }, - querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); } -}; - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/constant.js -/* harmony default export */ function src_constant(x) { - return function() { - return x; - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/data.js - - - - -function bindIndex(parent, group, enter, update, exit, data) { - var i = 0, - node, - groupLength = group.length, - dataLength = data.length; - - // Put any non-null nodes that fit into update. - // Put any null nodes into enter. - // Put any remaining data into enter. - for (; i < dataLength; ++i) { - if (node = group[i]) { - node.__data__ = data[i]; - update[i] = node; - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Put any non-null nodes that don’t fit into exit. - for (; i < groupLength; ++i) { - if (node = group[i]) { - exit[i] = node; - } - } -} - -function bindKey(parent, group, enter, update, exit, data, key) { - var i, - node, - nodeByKeyValue = new Map, - groupLength = group.length, - dataLength = data.length, - keyValues = new Array(groupLength), - keyValue; - - // Compute the key for each node. - // If multiple nodes have the same key, the duplicates are added to exit. - for (i = 0; i < groupLength; ++i) { - if (node = group[i]) { - keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + ""; - if (nodeByKeyValue.has(keyValue)) { - exit[i] = node; - } else { - nodeByKeyValue.set(keyValue, node); - } - } - } - - // Compute the key for each datum. - // If there a node associated with this key, join and add it to update. - // If there is not (or the key is a duplicate), add it to enter. - for (i = 0; i < dataLength; ++i) { - keyValue = key.call(parent, data[i], i, data) + ""; - if (node = nodeByKeyValue.get(keyValue)) { - update[i] = node; - node.__data__ = data[i]; - nodeByKeyValue.delete(keyValue); - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Add any remaining nodes that were not bound to data to exit. - for (i = 0; i < groupLength; ++i) { - if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) { - exit[i] = node; - } - } -} - -function datum(node) { - return node.__data__; -} - -/* harmony default export */ function data(value, key) { - if (!arguments.length) return Array.from(this, datum); - - var bind = key ? bindKey : bindIndex, - parents = this._parents, - groups = this._groups; - - if (typeof value !== "function") value = src_constant(value); - - for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) { - var parent = parents[j], - group = groups[j], - groupLength = group.length, - data = arraylike(value.call(parent, parent && parent.__data__, j, parents)), - dataLength = data.length, - enterGroup = enter[j] = new Array(dataLength), - updateGroup = update[j] = new Array(dataLength), - exitGroup = exit[j] = new Array(groupLength); - - bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); - - // Now connect the enter nodes to their following update node, such that - // appendChild can insert the materialized enter node before this node, - // rather than at the end of the parent node. - for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { - if (previous = enterGroup[i0]) { - if (i0 >= i1) i1 = i0 + 1; - while (!(next = updateGroup[i1]) && ++i1 < dataLength); - previous._next = next || null; - } - } - } - - update = new Selection(update, parents); - update._enter = enter; - update._exit = exit; - return update; -} - -// Given some data, this returns an array-like view of it: an object that -// exposes a length property and allows numeric indexing. Note that unlike -// selectAll, this isn’t worried about “live” collections because the resulting -// array will only be used briefly while data is being bound. (It is possible to -// cause the data to change while iterating by using a key function, but please -// don’t; we’d rather avoid a gratuitous copy.) -function arraylike(data) { - return typeof data === "object" && "length" in data - ? data // Array, TypedArray, NodeList, array-like - : Array.from(data); // Map, Set, iterable, string, or anything else -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/exit.js - - - -/* harmony default export */ function exit() { - return new Selection(this._exit || this._groups.map(sparse), this._parents); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/join.js -/* harmony default export */ function join(onenter, onupdate, onexit) { - var enter = this.enter(), update = this, exit = this.exit(); - if (typeof onenter === "function") { - enter = onenter(enter); - if (enter) enter = enter.selection(); - } else { - enter = enter.append(onenter + ""); - } - if (onupdate != null) { - update = onupdate(update); - if (update) update = update.selection(); - } - if (onexit == null) exit.remove(); else onexit(exit); - return enter && update ? enter.merge(update).order() : update; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/merge.js - - -/* harmony default export */ function merge(context) { - var selection = context.selection ? context.selection() : context; - - for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { - for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group0[i] || group1[i]) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Selection(merges, this._parents); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/order.js -/* harmony default export */ function order() { - - for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) { - for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) { - if (node = group[i]) { - if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next); - next = node; - } - } - } - - return this; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sort.js - - -/* harmony default export */ function sort(compare) { - if (!compare) compare = ascending; - - function compareNode(a, b) { - return a && b ? compare(a.__data__, b.__data__) : !a - !b; - } - - for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group[i]) { - sortgroup[i] = node; - } - } - sortgroup.sort(compareNode); - } - - return new Selection(sortgroups, this._parents).order(); -} - -function ascending(a, b) { - return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/call.js -/* harmony default export */ function call() { - var callback = arguments[0]; - arguments[0] = this; - callback.apply(null, arguments); - return this; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/nodes.js -/* harmony default export */ function nodes() { - return Array.from(this); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/node.js -/* harmony default export */ function node() { - - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { - var node = group[i]; - if (node) return node; - } - } - - return null; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/size.js -/* harmony default export */ function size() { - let size = 0; - for (const node of this) ++size; // eslint-disable-line no-unused-vars - return size; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/empty.js -/* harmony default export */ function selection_empty() { - return !this.node(); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/each.js -/* harmony default export */ function each(callback) { - - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if (node = group[i]) callback.call(node, node.__data__, i, group); - } - } - - return this; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/namespaces.js -var xhtml = "http://www.w3.org/1999/xhtml"; - -/* harmony default export */ const namespaces = ({ - svg: "http://www.w3.org/2000/svg", - xhtml: xhtml, - xlink: "http://www.w3.org/1999/xlink", - xml: "http://www.w3.org/XML/1998/namespace", - xmlns: "http://www.w3.org/2000/xmlns/" -}); - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/namespace.js - - -/* harmony default export */ function namespace(name) { - var prefix = name += "", i = prefix.indexOf(":"); - if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); - return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/attr.js - - -function attrRemove(name) { - return function() { - this.removeAttribute(name); - }; -} - -function attrRemoveNS(fullname) { - return function() { - this.removeAttributeNS(fullname.space, fullname.local); - }; -} - -function attrConstant(name, value) { - return function() { - this.setAttribute(name, value); - }; -} - -function attrConstantNS(fullname, value) { - return function() { - this.setAttributeNS(fullname.space, fullname.local, value); - }; -} - -function attrFunction(name, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.removeAttribute(name); - else this.setAttribute(name, v); - }; -} - -function attrFunctionNS(fullname, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.removeAttributeNS(fullname.space, fullname.local); - else this.setAttributeNS(fullname.space, fullname.local, v); - }; -} - -/* harmony default export */ function attr(name, value) { - var fullname = namespace(name); - - if (arguments.length < 2) { - var node = this.node(); - return fullname.local - ? node.getAttributeNS(fullname.space, fullname.local) - : node.getAttribute(fullname); - } - - return this.each((value == null - ? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === "function" - ? (fullname.local ? attrFunctionNS : attrFunction) - : (fullname.local ? attrConstantNS : attrConstant)))(fullname, value)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/window.js -/* harmony default export */ function src_window(node) { - return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node - || (node.document && node) // node is a Window - || node.defaultView; // node is a Document -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/style.js - - -function styleRemove(name) { - return function() { - this.style.removeProperty(name); - }; -} - -function styleConstant(name, value, priority) { - return function() { - this.style.setProperty(name, value, priority); - }; -} - -function styleFunction(name, value, priority) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.style.removeProperty(name); - else this.style.setProperty(name, v, priority); - }; -} - -/* harmony default export */ function style(name, value, priority) { - return arguments.length > 1 - ? this.each((value == null - ? styleRemove : typeof value === "function" - ? styleFunction - : styleConstant)(name, value, priority == null ? "" : priority)) - : styleValue(this.node(), name); -} - -function styleValue(node, name) { - return node.style.getPropertyValue(name) - || src_window(node).getComputedStyle(node, null).getPropertyValue(name); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/property.js -function propertyRemove(name) { - return function() { - delete this[name]; - }; -} - -function propertyConstant(name, value) { - return function() { - this[name] = value; - }; -} - -function propertyFunction(name, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) delete this[name]; - else this[name] = v; - }; -} - -/* harmony default export */ function property(name, value) { - return arguments.length > 1 - ? this.each((value == null - ? propertyRemove : typeof value === "function" - ? propertyFunction - : propertyConstant)(name, value)) - : this.node()[name]; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/classed.js -function classArray(string) { - return string.trim().split(/^|\s+/); -} - -function classList(node) { - return node.classList || new ClassList(node); -} - -function ClassList(node) { - this._node = node; - this._names = classArray(node.getAttribute("class") || ""); -} - -ClassList.prototype = { - add: function(name) { - var i = this._names.indexOf(name); - if (i < 0) { - this._names.push(name); - this._node.setAttribute("class", this._names.join(" ")); - } - }, - remove: function(name) { - var i = this._names.indexOf(name); - if (i >= 0) { - this._names.splice(i, 1); - this._node.setAttribute("class", this._names.join(" ")); - } - }, - contains: function(name) { - return this._names.indexOf(name) >= 0; - } -}; - -function classedAdd(node, names) { - var list = classList(node), i = -1, n = names.length; - while (++i < n) list.add(names[i]); -} - -function classedRemove(node, names) { - var list = classList(node), i = -1, n = names.length; - while (++i < n) list.remove(names[i]); -} - -function classedTrue(names) { - return function() { - classedAdd(this, names); - }; -} - -function classedFalse(names) { - return function() { - classedRemove(this, names); - }; -} - -function classedFunction(names, value) { - return function() { - (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names); - }; -} - -/* harmony default export */ function classed(name, value) { - var names = classArray(name + ""); - - if (arguments.length < 2) { - var list = classList(this.node()), i = -1, n = names.length; - while (++i < n) if (!list.contains(names[i])) return false; - return true; - } - - return this.each((typeof value === "function" - ? classedFunction : value - ? classedTrue - : classedFalse)(names, value)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/text.js -function textRemove() { - this.textContent = ""; -} - -function textConstant(value) { - return function() { - this.textContent = value; - }; -} - -function textFunction(value) { - return function() { - var v = value.apply(this, arguments); - this.textContent = v == null ? "" : v; - }; -} - -/* harmony default export */ function selection_text(value) { - return arguments.length - ? this.each(value == null - ? textRemove : (typeof value === "function" - ? textFunction - : textConstant)(value)) - : this.node().textContent; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/html.js -function htmlRemove() { - this.innerHTML = ""; -} - -function htmlConstant(value) { - return function() { - this.innerHTML = value; - }; -} - -function htmlFunction(value) { - return function() { - var v = value.apply(this, arguments); - this.innerHTML = v == null ? "" : v; - }; -} - -/* harmony default export */ function html(value) { - return arguments.length - ? this.each(value == null - ? htmlRemove : (typeof value === "function" - ? htmlFunction - : htmlConstant)(value)) - : this.node().innerHTML; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/raise.js -function raise() { - if (this.nextSibling) this.parentNode.appendChild(this); -} - -/* harmony default export */ function selection_raise() { - return this.each(raise); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/lower.js -function lower() { - if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild); -} - -/* harmony default export */ function selection_lower() { - return this.each(lower); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/creator.js - - - -function creatorInherit(name) { - return function() { - var document = this.ownerDocument, - uri = this.namespaceURI; - return uri === xhtml && document.documentElement.namespaceURI === xhtml - ? document.createElement(name) - : document.createElementNS(uri, name); - }; -} - -function creatorFixed(fullname) { - return function() { - return this.ownerDocument.createElementNS(fullname.space, fullname.local); - }; -} - -/* harmony default export */ function creator(name) { - var fullname = namespace(name); - return (fullname.local - ? creatorFixed - : creatorInherit)(fullname); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/append.js - - -/* harmony default export */ function append(name) { - var create = typeof name === "function" ? name : creator(name); - return this.select(function() { - return this.appendChild(create.apply(this, arguments)); - }); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/insert.js - - - -function constantNull() { - return null; -} - -/* harmony default export */ function insert(name, before) { - var create = typeof name === "function" ? name : creator(name), - select = before == null ? constantNull : typeof before === "function" ? before : selector(before); - return this.select(function() { - return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null); - }); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/remove.js -function remove() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); -} - -/* harmony default export */ function selection_remove() { - return this.each(remove); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/clone.js -function selection_cloneShallow() { - var clone = this.cloneNode(false), parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; -} - -function selection_cloneDeep() { - var clone = this.cloneNode(true), parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; -} - -/* harmony default export */ function clone(deep) { - return this.select(deep ? selection_cloneDeep : selection_cloneShallow); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/datum.js -/* harmony default export */ function selection_datum(value) { - return arguments.length - ? this.property("__data__", value) - : this.node().__data__; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/on.js -function contextListener(listener) { - return function(event) { - listener.call(this, event, this.__data__); - }; -} - -function parseTypenames(typenames) { - return typenames.trim().split(/^|\s+/).map(function(t) { - var name = "", i = t.indexOf("."); - if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); - return {type: t, name: name}; - }); -} - -function onRemove(typename) { - return function() { - var on = this.__on; - if (!on) return; - for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { - if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) { - this.removeEventListener(o.type, o.listener, o.options); - } else { - on[++i] = o; - } - } - if (++i) on.length = i; - else delete this.__on; - }; -} - -function onAdd(typename, value, options) { - return function() { - var on = this.__on, o, listener = contextListener(value); - if (on) for (var j = 0, m = on.length; j < m; ++j) { - if ((o = on[j]).type === typename.type && o.name === typename.name) { - this.removeEventListener(o.type, o.listener, o.options); - this.addEventListener(o.type, o.listener = listener, o.options = options); - o.value = value; - return; - } - } - this.addEventListener(typename.type, listener, options); - o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options}; - if (!on) this.__on = [o]; - else on.push(o); - }; -} - -/* harmony default export */ function on(typename, value, options) { - var typenames = parseTypenames(typename + ""), i, n = typenames.length, t; - - if (arguments.length < 2) { - var on = this.node().__on; - if (on) for (var j = 0, m = on.length, o; j < m; ++j) { - for (i = 0, o = on[j]; i < n; ++i) { - if ((t = typenames[i]).type === o.type && t.name === o.name) { - return o.value; - } - } - } - return; - } - - on = value ? onAdd : onRemove; - for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options)); - return this; -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/dispatch.js - - -function dispatchEvent(node, type, params) { - var window = src_window(node), - event = window.CustomEvent; - - if (typeof event === "function") { - event = new event(type, params); - } else { - event = window.document.createEvent("Event"); - if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail; - else event.initEvent(type, false, false); - } - - node.dispatchEvent(event); -} - -function dispatchConstant(type, params) { - return function() { - return dispatchEvent(this, type, params); - }; -} - -function dispatchFunction(type, params) { - return function() { - return dispatchEvent(this, type, params.apply(this, arguments)); - }; -} - -/* harmony default export */ function dispatch(type, params) { - return this.each((typeof params === "function" - ? dispatchFunction - : dispatchConstant)(type, params)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/iterator.js -/* harmony default export */ function* iterator() { - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if (node = group[i]) yield node; - } - } -} - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/index.js - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -var root = [null]; - -function Selection(groups, parents) { - this._groups = groups; - this._parents = parents; -} - -function selection() { - return new Selection([[document.documentElement]], root); -} - -function selection_selection() { - return this; -} - -Selection.prototype = selection.prototype = { - constructor: Selection, - select: selection_select, - selectAll: selectAll, - selectChild: selectChild, - selectChildren: selectChildren, - filter: selection_filter, - data: data, - enter: enter, - exit: exit, - join: join, - merge: merge, - selection: selection_selection, - order: order, - sort: sort, - call: call, - nodes: nodes, - node: node, - size: size, - empty: selection_empty, - each: each, - attr: attr, - style: style, - property: property, - classed: classed, - text: selection_text, - html: html, - raise: selection_raise, - lower: selection_lower, - append: append, - insert: insert, - remove: selection_remove, - clone: clone, - datum: selection_datum, - on: on, - dispatch: dispatch, - [Symbol.iterator]: iterator -}; - -/* harmony default export */ const src_selection = (selection); - -;// CONCATENATED MODULE: ../node_modules/d3-selection/src/select.js - - -/* harmony default export */ function src_select(selector) { - return typeof selector === "string" - ? new Selection([[document.querySelector(selector)]], [document.documentElement]) - : new Selection([[selector]], root); -} - -;// CONCATENATED MODULE: ../node_modules/d3-dispatch/src/dispatch.js -var noop = {value: () => {}}; - -function dispatch_dispatch() { - for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { - if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t); - _[t] = []; - } - return new Dispatch(_); -} - -function Dispatch(_) { - this._ = _; -} - -function dispatch_parseTypenames(typenames, types) { - return typenames.trim().split(/^|\s+/).map(function(t) { - var name = "", i = t.indexOf("."); - if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); - if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t); - return {type: t, name: name}; - }); -} - -Dispatch.prototype = dispatch_dispatch.prototype = { - constructor: Dispatch, - on: function(typename, callback) { - var _ = this._, - T = dispatch_parseTypenames(typename + "", _), - t, - i = -1, - n = T.length; - - // If no callback was specified, return the callback of the given type and name. - if (arguments.length < 2) { - while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t; - return; - } - - // If a type was specified, set the callback for the given type and name. - // Otherwise, if a null callback was specified, remove callbacks of the given name. - if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback); - while (++i < n) { - if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback); - else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null); - } - - return this; - }, - copy: function() { - var copy = {}, _ = this._; - for (var t in _) copy[t] = _[t].slice(); - return new Dispatch(copy); - }, - call: function(type, that) { - if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2]; - if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); - for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); - }, - apply: function(type, that, args) { - if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); - for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); - } -}; - -function get(type, name) { - for (var i = 0, n = type.length, c; i < n; ++i) { - if ((c = type[i]).name === name) { - return c.value; - } - } -} - -function set(type, name, callback) { - for (var i = 0, n = type.length; i < n; ++i) { - if (type[i].name === name) { - type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1)); - break; - } - } - if (callback != null) type.push({name: name, value: callback}); - return type; -} - -/* harmony default export */ const src_dispatch = (dispatch_dispatch); - -;// CONCATENATED MODULE: ../node_modules/d3-timer/src/timer.js -var timer_frame = 0, // is an animation frame pending? - timeout = 0, // is a timeout pending? - interval = 0, // are any timers active? - pokeDelay = 1000, // how frequently we check for clock skew - taskHead, - taskTail, - clockLast = 0, - clockNow = 0, - clockSkew = 0, - clock = typeof performance === "object" && performance.now ? performance : Date, - setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); }; - -function now() { - return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); -} - -function clearNow() { - clockNow = 0; -} - -function Timer() { - this._call = - this._time = - this._next = null; -} - -Timer.prototype = timer.prototype = { - constructor: Timer, - restart: function(callback, delay, time) { - if (typeof callback !== "function") throw new TypeError("callback is not a function"); - time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); - if (!this._next && taskTail !== this) { - if (taskTail) taskTail._next = this; - else taskHead = this; - taskTail = this; - } - this._call = callback; - this._time = time; - sleep(); - }, - stop: function() { - if (this._call) { - this._call = null; - this._time = Infinity; - sleep(); - } - } -}; - -function timer(callback, delay, time) { - var t = new Timer; - t.restart(callback, delay, time); - return t; -} - -function timerFlush() { - now(); // Get the current time, if not already set. - ++timer_frame; // Pretend we’ve set an alarm, if we haven’t already. - var t = taskHead, e; - while (t) { - if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e); - t = t._next; - } - --timer_frame; -} - -function wake() { - clockNow = (clockLast = clock.now()) + clockSkew; - timer_frame = timeout = 0; - try { - timerFlush(); - } finally { - timer_frame = 0; - nap(); - clockNow = 0; - } -} - -function poke() { - var now = clock.now(), delay = now - clockLast; - if (delay > pokeDelay) clockSkew -= delay, clockLast = now; -} - -function nap() { - var t0, t1 = taskHead, t2, time = Infinity; - while (t1) { - if (t1._call) { - if (time > t1._time) time = t1._time; - t0 = t1, t1 = t1._next; - } else { - t2 = t1._next, t1._next = null; - t1 = t0 ? t0._next = t2 : taskHead = t2; - } - } - taskTail = t0; - sleep(time); -} - -function sleep(time) { - if (timer_frame) return; // Soonest alarm already set, or will be. - if (timeout) timeout = clearTimeout(timeout); - var delay = time - clockNow; // Strictly less than if we recomputed clockNow. - if (delay > 24) { - if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew); - if (interval) interval = clearInterval(interval); - } else { - if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay); - timer_frame = 1, setFrame(wake); - } -} - -;// CONCATENATED MODULE: ../node_modules/d3-timer/src/timeout.js - - -/* harmony default export */ function src_timeout(callback, delay, time) { - var t = new Timer; - delay = delay == null ? 0 : +delay; - t.restart(elapsed => { - t.stop(); - callback(elapsed + delay); - }, delay, time); - return t; -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/schedule.js - - - -var emptyOn = src_dispatch("start", "end", "cancel", "interrupt"); -var emptyTween = []; - -var CREATED = 0; -var SCHEDULED = 1; -var STARTING = 2; -var STARTED = 3; -var RUNNING = 4; -var ENDING = 5; -var ENDED = 6; - -/* harmony default export */ function schedule(node, name, id, index, group, timing) { - var schedules = node.__transition; - if (!schedules) node.__transition = {}; - else if (id in schedules) return; - create(node, id, { - name: name, - index: index, // For context during callback. - group: group, // For context during callback. - on: emptyOn, - tween: emptyTween, - time: timing.time, - delay: timing.delay, - duration: timing.duration, - ease: timing.ease, - timer: null, - state: CREATED - }); -} - -function init(node, id) { - var schedule = schedule_get(node, id); - if (schedule.state > CREATED) throw new Error("too late; already scheduled"); - return schedule; -} - -function schedule_set(node, id) { - var schedule = schedule_get(node, id); - if (schedule.state > STARTED) throw new Error("too late; already running"); - return schedule; -} - -function schedule_get(node, id) { - var schedule = node.__transition; - if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found"); - return schedule; -} - -function create(node, id, self) { - var schedules = node.__transition, - tween; - - // Initialize the self timer when the transition is created. - // Note the actual delay is not known until the first callback! - schedules[id] = self; - self.timer = timer(schedule, 0, self.time); - - function schedule(elapsed) { - self.state = SCHEDULED; - self.timer.restart(start, self.delay, self.time); - - // If the elapsed delay is less than our first sleep, start immediately. - if (self.delay <= elapsed) start(elapsed - self.delay); - } - - function start(elapsed) { - var i, j, n, o; - - // If the state is not SCHEDULED, then we previously errored on start. - if (self.state !== SCHEDULED) return stop(); - - for (i in schedules) { - o = schedules[i]; - if (o.name !== self.name) continue; - - // While this element already has a starting transition during this frame, - // defer starting an interrupting transition until that transition has a - // chance to tick (and possibly end); see d3/d3-transition#54! - if (o.state === STARTED) return src_timeout(start); - - // Interrupt the active transition, if any. - if (o.state === RUNNING) { - o.state = ENDED; - o.timer.stop(); - o.on.call("interrupt", node, node.__data__, o.index, o.group); - delete schedules[i]; - } - - // Cancel any pre-empted transitions. - else if (+i < id) { - o.state = ENDED; - o.timer.stop(); - o.on.call("cancel", node, node.__data__, o.index, o.group); - delete schedules[i]; - } - } - - // Defer the first tick to end of the current frame; see d3/d3#1576. - // Note the transition may be canceled after start and before the first tick! - // Note this must be scheduled before the start event; see d3/d3-transition#16! - // Assuming this is successful, subsequent callbacks go straight to tick. - src_timeout(function() { - if (self.state === STARTED) { - self.state = RUNNING; - self.timer.restart(tick, self.delay, self.time); - tick(elapsed); - } - }); - - // Dispatch the start event. - // Note this must be done before the tween are initialized. - self.state = STARTING; - self.on.call("start", node, node.__data__, self.index, self.group); - if (self.state !== STARTING) return; // interrupted - self.state = STARTED; - - // Initialize the tween, deleting null tween. - tween = new Array(n = self.tween.length); - for (i = 0, j = -1; i < n; ++i) { - if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) { - tween[++j] = o; - } - } - tween.length = j + 1; - } - - function tick(elapsed) { - var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1), - i = -1, - n = tween.length; - - while (++i < n) { - tween[i].call(node, t); - } - - // Dispatch the end event. - if (self.state === ENDING) { - self.on.call("end", node, node.__data__, self.index, self.group); - stop(); - } - } - - function stop() { - self.state = ENDED; - self.timer.stop(); - delete schedules[id]; - for (var i in schedules) return; // eslint-disable-line no-unused-vars - delete node.__transition; - } -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/interrupt.js - - -/* harmony default export */ function interrupt(node, name) { - var schedules = node.__transition, - schedule, - active, - empty = true, - i; - - if (!schedules) return; - - name = name == null ? null : name + ""; - - for (i in schedules) { - if ((schedule = schedules[i]).name !== name) { empty = false; continue; } - active = schedule.state > STARTING && schedule.state < ENDING; - schedule.state = ENDED; - schedule.timer.stop(); - schedule.on.call(active ? "interrupt" : "cancel", node, node.__data__, schedule.index, schedule.group); - delete schedules[i]; - } - - if (empty) delete node.__transition; -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/interrupt.js - - -/* harmony default export */ function selection_interrupt(name) { - return this.each(function() { - interrupt(this, name); - }); -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/number.js -/* harmony default export */ function number(a, b) { - return a = +a, b = +b, function(t) { - return a * (1 - t) + b * t; - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/decompose.js -var degrees = 180 / Math.PI; - -var identity = { - translateX: 0, - translateY: 0, - rotate: 0, - skewX: 0, - scaleX: 1, - scaleY: 1 -}; - -/* harmony default export */ function decompose(a, b, c, d, e, f) { - var scaleX, scaleY, skewX; - if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; - if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; - if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; - if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; - return { - translateX: e, - translateY: f, - rotate: Math.atan2(b, a) * degrees, - skewX: Math.atan(skewX) * degrees, - scaleX: scaleX, - scaleY: scaleY - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/parse.js - - -var svgNode; - -/* eslint-disable no-undef */ -function parseCss(value) { - const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + ""); - return m.isIdentity ? identity : decompose(m.a, m.b, m.c, m.d, m.e, m.f); -} - -function parseSvg(value) { - if (value == null) return identity; - if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); - svgNode.setAttribute("transform", value); - if (!(value = svgNode.transform.baseVal.consolidate())) return identity; - value = value.matrix; - return decompose(value.a, value.b, value.c, value.d, value.e, value.f); -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/index.js - - - -function interpolateTransform(parse, pxComma, pxParen, degParen) { - - function pop(s) { - return s.length ? s.pop() + " " : ""; - } - - function translate(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push("translate(", null, pxComma, null, pxParen); - q.push({i: i - 4, x: number(xa, xb)}, {i: i - 2, x: number(ya, yb)}); - } else if (xb || yb) { - s.push("translate(" + xb + pxComma + yb + pxParen); - } - } - - function rotate(a, b, s, q) { - if (a !== b) { - if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path - q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: number(a, b)}); - } else if (b) { - s.push(pop(s) + "rotate(" + b + degParen); - } - } - - function skewX(a, b, s, q) { - if (a !== b) { - q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: number(a, b)}); - } else if (b) { - s.push(pop(s) + "skewX(" + b + degParen); - } - } - - function scale(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push(pop(s) + "scale(", null, ",", null, ")"); - q.push({i: i - 4, x: number(xa, xb)}, {i: i - 2, x: number(ya, yb)}); - } else if (xb !== 1 || yb !== 1) { - s.push(pop(s) + "scale(" + xb + "," + yb + ")"); - } - } - - return function(a, b) { - var s = [], // string constants and placeholders - q = []; // number interpolators - a = parse(a), b = parse(b); - translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); - rotate(a.rotate, b.rotate, s, q); - skewX(a.skewX, b.skewX, s, q); - scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); - a = b = null; // gc - return function(t) { - var i = -1, n = q.length, o; - while (++i < n) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; - }; -} - -var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); -var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/tween.js - - -function tweenRemove(id, name) { - var tween0, tween1; - return function() { - var schedule = schedule_set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = tween0 = tween; - for (var i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1 = tween1.slice(); - tween1.splice(i, 1); - break; - } - } - } - - schedule.tween = tween1; - }; -} - -function tweenFunction(id, name, value) { - var tween0, tween1; - if (typeof value !== "function") throw new Error; - return function() { - var schedule = schedule_set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = (tween0 = tween).slice(); - for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1[i] = t; - break; - } - } - if (i === n) tween1.push(t); - } - - schedule.tween = tween1; - }; -} - -/* harmony default export */ function tween(name, value) { - var id = this._id; - - name += ""; - - if (arguments.length < 2) { - var tween = schedule_get(this.node(), id).tween; - for (var i = 0, n = tween.length, t; i < n; ++i) { - if ((t = tween[i]).name === name) { - return t.value; - } - } - return null; - } - - return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value)); -} - -function tweenValue(transition, name, value) { - var id = transition._id; - - transition.each(function() { - var schedule = schedule_set(this, id); - (schedule.value || (schedule.value = {}))[name] = value.apply(this, arguments); - }); - - return function(node) { - return schedule_get(node, id).value[name]; - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-color/src/define.js -/* harmony default export */ function src_define(constructor, factory, prototype) { - constructor.prototype = factory.prototype = prototype; - prototype.constructor = constructor; -} - -function extend(parent, definition) { - var prototype = Object.create(parent.prototype); - for (var key in definition) prototype[key] = definition[key]; - return prototype; -} - -;// CONCATENATED MODULE: ../node_modules/d3-color/src/color.js - - -function Color() {} - -var darker = 0.7; -var brighter = 1 / darker; - -var reI = "\\s*([+-]?\\d+)\\s*", - reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*", - reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*", - reHex = /^#([0-9a-f]{3,8})$/, - reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"), - reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"), - reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"), - reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"), - reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"), - reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$"); - -var named = { - aliceblue: 0xf0f8ff, - antiquewhite: 0xfaebd7, - aqua: 0x00ffff, - aquamarine: 0x7fffd4, - azure: 0xf0ffff, - beige: 0xf5f5dc, - bisque: 0xffe4c4, - black: 0x000000, - blanchedalmond: 0xffebcd, - blue: 0x0000ff, - blueviolet: 0x8a2be2, - brown: 0xa52a2a, - burlywood: 0xdeb887, - cadetblue: 0x5f9ea0, - chartreuse: 0x7fff00, - chocolate: 0xd2691e, - coral: 0xff7f50, - cornflowerblue: 0x6495ed, - cornsilk: 0xfff8dc, - crimson: 0xdc143c, - cyan: 0x00ffff, - darkblue: 0x00008b, - darkcyan: 0x008b8b, - darkgoldenrod: 0xb8860b, - darkgray: 0xa9a9a9, - darkgreen: 0x006400, - darkgrey: 0xa9a9a9, - darkkhaki: 0xbdb76b, - darkmagenta: 0x8b008b, - darkolivegreen: 0x556b2f, - darkorange: 0xff8c00, - darkorchid: 0x9932cc, - darkred: 0x8b0000, - darksalmon: 0xe9967a, - darkseagreen: 0x8fbc8f, - darkslateblue: 0x483d8b, - darkslategray: 0x2f4f4f, - darkslategrey: 0x2f4f4f, - darkturquoise: 0x00ced1, - darkviolet: 0x9400d3, - deeppink: 0xff1493, - deepskyblue: 0x00bfff, - dimgray: 0x696969, - dimgrey: 0x696969, - dodgerblue: 0x1e90ff, - firebrick: 0xb22222, - floralwhite: 0xfffaf0, - forestgreen: 0x228b22, - fuchsia: 0xff00ff, - gainsboro: 0xdcdcdc, - ghostwhite: 0xf8f8ff, - gold: 0xffd700, - goldenrod: 0xdaa520, - gray: 0x808080, - green: 0x008000, - greenyellow: 0xadff2f, - grey: 0x808080, - honeydew: 0xf0fff0, - hotpink: 0xff69b4, - indianred: 0xcd5c5c, - indigo: 0x4b0082, - ivory: 0xfffff0, - khaki: 0xf0e68c, - lavender: 0xe6e6fa, - lavenderblush: 0xfff0f5, - lawngreen: 0x7cfc00, - lemonchiffon: 0xfffacd, - lightblue: 0xadd8e6, - lightcoral: 0xf08080, - lightcyan: 0xe0ffff, - lightgoldenrodyellow: 0xfafad2, - lightgray: 0xd3d3d3, - lightgreen: 0x90ee90, - lightgrey: 0xd3d3d3, - lightpink: 0xffb6c1, - lightsalmon: 0xffa07a, - lightseagreen: 0x20b2aa, - lightskyblue: 0x87cefa, - lightslategray: 0x778899, - lightslategrey: 0x778899, - lightsteelblue: 0xb0c4de, - lightyellow: 0xffffe0, - lime: 0x00ff00, - limegreen: 0x32cd32, - linen: 0xfaf0e6, - magenta: 0xff00ff, - maroon: 0x800000, - mediumaquamarine: 0x66cdaa, - mediumblue: 0x0000cd, - mediumorchid: 0xba55d3, - mediumpurple: 0x9370db, - mediumseagreen: 0x3cb371, - mediumslateblue: 0x7b68ee, - mediumspringgreen: 0x00fa9a, - mediumturquoise: 0x48d1cc, - mediumvioletred: 0xc71585, - midnightblue: 0x191970, - mintcream: 0xf5fffa, - mistyrose: 0xffe4e1, - moccasin: 0xffe4b5, - navajowhite: 0xffdead, - navy: 0x000080, - oldlace: 0xfdf5e6, - olive: 0x808000, - olivedrab: 0x6b8e23, - orange: 0xffa500, - orangered: 0xff4500, - orchid: 0xda70d6, - palegoldenrod: 0xeee8aa, - palegreen: 0x98fb98, - paleturquoise: 0xafeeee, - palevioletred: 0xdb7093, - papayawhip: 0xffefd5, - peachpuff: 0xffdab9, - peru: 0xcd853f, - pink: 0xffc0cb, - plum: 0xdda0dd, - powderblue: 0xb0e0e6, - purple: 0x800080, - rebeccapurple: 0x663399, - red: 0xff0000, - rosybrown: 0xbc8f8f, - royalblue: 0x4169e1, - saddlebrown: 0x8b4513, - salmon: 0xfa8072, - sandybrown: 0xf4a460, - seagreen: 0x2e8b57, - seashell: 0xfff5ee, - sienna: 0xa0522d, - silver: 0xc0c0c0, - skyblue: 0x87ceeb, - slateblue: 0x6a5acd, - slategray: 0x708090, - slategrey: 0x708090, - snow: 0xfffafa, - springgreen: 0x00ff7f, - steelblue: 0x4682b4, - tan: 0xd2b48c, - teal: 0x008080, - thistle: 0xd8bfd8, - tomato: 0xff6347, - turquoise: 0x40e0d0, - violet: 0xee82ee, - wheat: 0xf5deb3, - white: 0xffffff, - whitesmoke: 0xf5f5f5, - yellow: 0xffff00, - yellowgreen: 0x9acd32 -}; - -src_define(Color, color, { - copy: function(channels) { - return Object.assign(new this.constructor, this, channels); - }, - displayable: function() { - return this.rgb().displayable(); - }, - hex: color_formatHex, // Deprecated! Use color.formatHex. - formatHex: color_formatHex, - formatHsl: color_formatHsl, - formatRgb: color_formatRgb, - toString: color_formatRgb -}); - -function color_formatHex() { - return this.rgb().formatHex(); -} - -function color_formatHsl() { - return hslConvert(this).formatHsl(); -} - -function color_formatRgb() { - return this.rgb().formatRgb(); -} - -function color(format) { - var m, l; - format = (format + "").trim().toLowerCase(); - return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000 - : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00 - : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000 - : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000 - : null) // invalid hex - : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) - : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%) - : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) - : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1) - : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) - : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) - : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins - : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) - : null; -} - -function rgbn(n) { - return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1); -} - -function rgba(r, g, b, a) { - if (a <= 0) r = g = b = NaN; - return new Rgb(r, g, b, a); -} - -function rgbConvert(o) { - if (!(o instanceof Color)) o = color(o); - if (!o) return new Rgb; - o = o.rgb(); - return new Rgb(o.r, o.g, o.b, o.opacity); -} - -function color_rgb(r, g, b, opacity) { - return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity); -} - -function Rgb(r, g, b, opacity) { - this.r = +r; - this.g = +g; - this.b = +b; - this.opacity = +opacity; -} - -src_define(Rgb, color_rgb, extend(Color, { - brighter: function(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - darker: function(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - rgb: function() { - return this; - }, - displayable: function() { - return (-0.5 <= this.r && this.r < 255.5) - && (-0.5 <= this.g && this.g < 255.5) - && (-0.5 <= this.b && this.b < 255.5) - && (0 <= this.opacity && this.opacity <= 1); - }, - hex: rgb_formatHex, // Deprecated! Use color.formatHex. - formatHex: rgb_formatHex, - formatRgb: rgb_formatRgb, - toString: rgb_formatRgb -})); - -function rgb_formatHex() { - return "#" + hex(this.r) + hex(this.g) + hex(this.b); -} - -function rgb_formatRgb() { - var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); - return (a === 1 ? "rgb(" : "rgba(") - + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", " - + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", " - + Math.max(0, Math.min(255, Math.round(this.b) || 0)) - + (a === 1 ? ")" : ", " + a + ")"); -} - -function hex(value) { - value = Math.max(0, Math.min(255, Math.round(value) || 0)); - return (value < 16 ? "0" : "") + value.toString(16); -} - -function hsla(h, s, l, a) { - if (a <= 0) h = s = l = NaN; - else if (l <= 0 || l >= 1) h = s = NaN; - else if (s <= 0) h = NaN; - return new Hsl(h, s, l, a); -} - -function hslConvert(o) { - if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); - if (!(o instanceof Color)) o = color(o); - if (!o) return new Hsl; - if (o instanceof Hsl) return o; - o = o.rgb(); - var r = o.r / 255, - g = o.g / 255, - b = o.b / 255, - min = Math.min(r, g, b), - max = Math.max(r, g, b), - h = NaN, - s = max - min, - l = (max + min) / 2; - if (s) { - if (r === max) h = (g - b) / s + (g < b) * 6; - else if (g === max) h = (b - r) / s + 2; - else h = (r - g) / s + 4; - s /= l < 0.5 ? max + min : 2 - max - min; - h *= 60; - } else { - s = l > 0 && l < 1 ? 0 : h; - } - return new Hsl(h, s, l, o.opacity); -} - -function hsl(h, s, l, opacity) { - return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity); -} - -function Hsl(h, s, l, opacity) { - this.h = +h; - this.s = +s; - this.l = +l; - this.opacity = +opacity; -} - -src_define(Hsl, hsl, extend(Color, { - brighter: function(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - darker: function(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - rgb: function() { - var h = this.h % 360 + (this.h < 0) * 360, - s = isNaN(h) || isNaN(this.s) ? 0 : this.s, - l = this.l, - m2 = l + (l < 0.5 ? l : 1 - l) * s, - m1 = 2 * l - m2; - return new Rgb( - hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), - hsl2rgb(h, m1, m2), - hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), - this.opacity - ); - }, - displayable: function() { - return (0 <= this.s && this.s <= 1 || isNaN(this.s)) - && (0 <= this.l && this.l <= 1) - && (0 <= this.opacity && this.opacity <= 1); - }, - formatHsl: function() { - var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); - return (a === 1 ? "hsl(" : "hsla(") - + (this.h || 0) + ", " - + (this.s || 0) * 100 + "%, " - + (this.l || 0) * 100 + "%" - + (a === 1 ? ")" : ", " + a + ")"); - } -})); - -/* From FvD 13.37, CSS Color Module Level 3 */ -function hsl2rgb(h, m1, m2) { - return (h < 60 ? m1 + (m2 - m1) * h / 60 - : h < 180 ? m2 - : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 - : m1) * 255; -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basis.js -function basis(t1, v0, v1, v2, v3) { - var t2 = t1 * t1, t3 = t2 * t1; - return ((1 - 3 * t1 + 3 * t2 - t3) * v0 - + (4 - 6 * t2 + 3 * t3) * v1 - + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 - + t3 * v3) / 6; -} - -/* harmony default export */ function src_basis(values) { - var n = values.length - 1; - return function(t) { - var i = t <= 0 ? (t = 0) : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n), - v1 = values[i], - v2 = values[i + 1], - v0 = i > 0 ? values[i - 1] : 2 * v1 - v2, - v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1; - return basis((t - i / n) * n, v0, v1, v2, v3); - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basisClosed.js - - -/* harmony default export */ function basisClosed(values) { - var n = values.length; - return function(t) { - var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n), - v0 = values[(i + n - 1) % n], - v1 = values[i % n], - v2 = values[(i + 1) % n], - v3 = values[(i + 2) % n]; - return basis((t - i / n) * n, v0, v1, v2, v3); - }; -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/constant.js -/* harmony default export */ const d3_interpolate_src_constant = (x => () => x); - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/color.js - - -function linear(a, d) { - return function(t) { - return a + t * d; - }; -} - -function exponential(a, b, y) { - return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) { - return Math.pow(a + t * b, y); - }; -} - -function hue(a, b) { - var d = b - a; - return d ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : constant(isNaN(a) ? b : a); -} - -function gamma(y) { - return (y = +y) === 1 ? nogamma : function(a, b) { - return b - a ? exponential(a, b, y) : d3_interpolate_src_constant(isNaN(a) ? b : a); - }; -} - -function nogamma(a, b) { - var d = b - a; - return d ? linear(a, d) : d3_interpolate_src_constant(isNaN(a) ? b : a); -} - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/rgb.js - - - - - -/* harmony default export */ const rgb = ((function rgbGamma(y) { - var color = gamma(y); - - function rgb(start, end) { - var r = color((start = color_rgb(start)).r, (end = color_rgb(end)).r), - g = color(start.g, end.g), - b = color(start.b, end.b), - opacity = nogamma(start.opacity, end.opacity); - return function(t) { - start.r = r(t); - start.g = g(t); - start.b = b(t); - start.opacity = opacity(t); - return start + ""; - }; - } - - rgb.gamma = rgbGamma; - - return rgb; -})(1)); - -function rgbSpline(spline) { - return function(colors) { - var n = colors.length, - r = new Array(n), - g = new Array(n), - b = new Array(n), - i, color; - for (i = 0; i < n; ++i) { - color = color_rgb(colors[i]); - r[i] = color.r || 0; - g[i] = color.g || 0; - b[i] = color.b || 0; - } - r = spline(r); - g = spline(g); - b = spline(b); - color.opacity = 1; - return function(t) { - color.r = r(t); - color.g = g(t); - color.b = b(t); - return color + ""; - }; - }; -} - -var rgbBasis = rgbSpline(src_basis); -var rgbBasisClosed = rgbSpline(basisClosed); - -;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/string.js - - -var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, - reB = new RegExp(reA.source, "g"); - -function zero(b) { - return function() { - return b; - }; -} - -function one(b) { - return function(t) { - return b(t) + ""; - }; -} - -/* harmony default export */ function string(a, b) { - var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b - am, // current match in a - bm, // current match in b - bs, // string preceding current number in b, if any - i = -1, // index in s - s = [], // string constants and placeholders - q = []; // number interpolators - - // Coerce inputs to strings. - a = a + "", b = b + ""; - - // Interpolate pairs of numbers in a & b. - while ((am = reA.exec(a)) - && (bm = reB.exec(b))) { - if ((bs = bm.index) > bi) { // a string precedes the next number in b - bs = b.slice(bi, bs); - if (s[i]) s[i] += bs; // coalesce with previous string - else s[++i] = bs; - } - if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match - if (s[i]) s[i] += bm; // coalesce with previous string - else s[++i] = bm; - } else { // interpolate non-matching numbers - s[++i] = null; - q.push({i: i, x: number(am, bm)}); - } - bi = reB.lastIndex; - } - - // Add remains of b. - if (bi < b.length) { - bs = b.slice(bi); - if (s[i]) s[i] += bs; // coalesce with previous string - else s[++i] = bs; - } - - // Special optimization for only a single match. - // Otherwise, interpolate each of the numbers and rejoin the string. - return s.length < 2 ? (q[0] - ? one(q[0].x) - : zero(b)) - : (b = q.length, function(t) { - for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/interpolate.js - - - -/* harmony default export */ function interpolate(a, b) { - var c; - return (typeof b === "number" ? number - : b instanceof color ? rgb - : (c = color(b)) ? (b = c, rgb) - : string)(a, b); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attr.js - - - - - -function attr_attrRemove(name) { - return function() { - this.removeAttribute(name); - }; -} - -function attr_attrRemoveNS(fullname) { - return function() { - this.removeAttributeNS(fullname.space, fullname.local); - }; -} - -function attr_attrConstant(name, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = this.getAttribute(name); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function attr_attrConstantNS(fullname, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = this.getAttributeNS(fullname.space, fullname.local); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function attr_attrFunction(name, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0, value1 = value(this), string1; - if (value1 == null) return void this.removeAttribute(name); - string0 = this.getAttribute(name); - string1 = value1 + ""; - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function attr_attrFunctionNS(fullname, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0, value1 = value(this), string1; - if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local); - string0 = this.getAttributeNS(fullname.space, fullname.local); - string1 = value1 + ""; - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -/* harmony default export */ function transition_attr(name, value) { - var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate; - return this.attrTween(name, typeof value === "function" - ? (fullname.local ? attr_attrFunctionNS : attr_attrFunction)(fullname, i, tweenValue(this, "attr." + name, value)) - : value == null ? (fullname.local ? attr_attrRemoveNS : attr_attrRemove)(fullname) - : (fullname.local ? attr_attrConstantNS : attr_attrConstant)(fullname, i, value)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attrTween.js - - -function attrInterpolate(name, i) { - return function(t) { - this.setAttribute(name, i.call(this, t)); - }; -} - -function attrInterpolateNS(fullname, i) { - return function(t) { - this.setAttributeNS(fullname.space, fullname.local, i.call(this, t)); - }; -} - -function attrTweenNS(fullname, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i); - return t0; - } - tween._value = value; - return tween; -} - -function attrTween(name, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i); - return t0; - } - tween._value = value; - return tween; -} - -/* harmony default export */ function transition_attrTween(name, value) { - var key = "attr." + name; - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - var fullname = namespace(name); - return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/delay.js - - -function delayFunction(id, value) { - return function() { - init(this, id).delay = +value.apply(this, arguments); - }; -} - -function delayConstant(id, value) { - return value = +value, function() { - init(this, id).delay = value; - }; -} - -/* harmony default export */ function delay(value) { - var id = this._id; - - return arguments.length - ? this.each((typeof value === "function" - ? delayFunction - : delayConstant)(id, value)) - : schedule_get(this.node(), id).delay; -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/duration.js - - -function durationFunction(id, value) { - return function() { - schedule_set(this, id).duration = +value.apply(this, arguments); - }; -} - -function durationConstant(id, value) { - return value = +value, function() { - schedule_set(this, id).duration = value; - }; -} - -/* harmony default export */ function duration(value) { - var id = this._id; - - return arguments.length - ? this.each((typeof value === "function" - ? durationFunction - : durationConstant)(id, value)) - : schedule_get(this.node(), id).duration; -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/ease.js - - -function easeConstant(id, value) { - if (typeof value !== "function") throw new Error; - return function() { - schedule_set(this, id).ease = value; - }; -} - -/* harmony default export */ function ease(value) { - var id = this._id; - - return arguments.length - ? this.each(easeConstant(id, value)) - : schedule_get(this.node(), id).ease; -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/easeVarying.js - - -function easeVarying(id, value) { - return function() { - var v = value.apply(this, arguments); - if (typeof v !== "function") throw new Error; - schedule_set(this, id).ease = v; - }; -} - -/* harmony default export */ function transition_easeVarying(value) { - if (typeof value !== "function") throw new Error; - return this.each(easeVarying(this._id, value)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/filter.js - - - -/* harmony default export */ function transition_filter(match) { - if (typeof match !== "function") match = matcher(match); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Transition(subgroups, this._parents, this._name, this._id); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/merge.js - - -/* harmony default export */ function transition_merge(transition) { - if (transition._id !== this._id) throw new Error; - - for (var groups0 = this._groups, groups1 = transition._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { - for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group0[i] || group1[i]) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Transition(merges, this._parents, this._name, this._id); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/on.js - - -function start(name) { - return (name + "").trim().split(/^|\s+/).every(function(t) { - var i = t.indexOf("."); - if (i >= 0) t = t.slice(0, i); - return !t || t === "start"; - }); -} - -function onFunction(id, name, listener) { - var on0, on1, sit = start(name) ? init : schedule_set; - return function() { - var schedule = sit(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener); - - schedule.on = on1; - }; -} - -/* harmony default export */ function transition_on(name, listener) { - var id = this._id; - - return arguments.length < 2 - ? schedule_get(this.node(), id).on.on(name) - : this.each(onFunction(id, name, listener)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/remove.js -function removeFunction(id) { - return function() { - var parent = this.parentNode; - for (var i in this.__transition) if (+i !== id) return; - if (parent) parent.removeChild(this); - }; -} - -/* harmony default export */ function transition_remove() { - return this.on("end.remove", removeFunction(this._id)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/select.js - - - - -/* harmony default export */ function transition_select(select) { - var name = this._name, - id = this._id; - - if (typeof select !== "function") select = selector(select); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { - if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { - if ("__data__" in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - schedule(subgroup[i], name, id, i, subgroup, schedule_get(node, id)); - } - } - } - - return new Transition(subgroups, this._parents, name, id); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selectAll.js - - - - -/* harmony default export */ function transition_selectAll(select) { - var name = this._name, - id = this._id; - - if (typeof select !== "function") select = selectorAll(select); - - for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - for (var children = select.call(node, node.__data__, i, group), child, inherit = schedule_get(node, id), k = 0, l = children.length; k < l; ++k) { - if (child = children[k]) { - schedule(child, name, id, k, children, inherit); - } - } - subgroups.push(children); - parents.push(node); - } - } - } - - return new Transition(subgroups, parents, name, id); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selection.js - - -var selection_Selection = src_selection.prototype.constructor; - -/* harmony default export */ function transition_selection() { - return new selection_Selection(this._groups, this._parents); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/style.js - - - - - - -function styleNull(name, interpolate) { - var string00, - string10, - interpolate0; - return function() { - var string0 = styleValue(this, name), - string1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, string10 = string1); - }; -} - -function style_styleRemove(name) { - return function() { - this.style.removeProperty(name); - }; -} - -function style_styleConstant(name, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = styleValue(this, name); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function style_styleFunction(name, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0 = styleValue(this, name), - value1 = value(this), - string1 = value1 + ""; - if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function styleMaybeRemove(id, name) { - var on0, on1, listener0, key = "style." + name, event = "end." + key, remove; - return function() { - var schedule = schedule_set(this, id), - on = schedule.on, - listener = schedule.value[key] == null ? remove || (remove = style_styleRemove(name)) : undefined; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0 || listener0 !== listener) (on1 = (on0 = on).copy()).on(event, listener0 = listener); - - schedule.on = on1; - }; -} - -/* harmony default export */ function transition_style(name, value, priority) { - var i = (name += "") === "transform" ? interpolateTransformCss : interpolate; - return value == null ? this - .styleTween(name, styleNull(name, i)) - .on("end.style." + name, style_styleRemove(name)) - : typeof value === "function" ? this - .styleTween(name, style_styleFunction(name, i, tweenValue(this, "style." + name, value))) - .each(styleMaybeRemove(this._id, name)) - : this - .styleTween(name, style_styleConstant(name, i, value), priority) - .on("end.style." + name, null); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/styleTween.js -function styleInterpolate(name, i, priority) { - return function(t) { - this.style.setProperty(name, i.call(this, t), priority); - }; -} - -function styleTween(name, value, priority) { - var t, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority); - return t; - } - tween._value = value; - return tween; -} - -/* harmony default export */ function transition_styleTween(name, value, priority) { - var key = "style." + (name += ""); - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - return this.tween(key, styleTween(name, value, priority == null ? "" : priority)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/text.js - - -function text_textConstant(value) { - return function() { - this.textContent = value; - }; -} - -function text_textFunction(value) { - return function() { - var value1 = value(this); - this.textContent = value1 == null ? "" : value1; - }; -} - -/* harmony default export */ function transition_text(value) { - return this.tween("text", typeof value === "function" - ? text_textFunction(tweenValue(this, "text", value)) - : text_textConstant(value == null ? "" : value + "")); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/textTween.js -function textInterpolate(i) { - return function(t) { - this.textContent = i.call(this, t); - }; -} - -function textTween(value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && textInterpolate(i); - return t0; - } - tween._value = value; - return tween; -} - -/* harmony default export */ function transition_textTween(value) { - var key = "text"; - if (arguments.length < 1) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - return this.tween(key, textTween(value)); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/transition.js - - - -/* harmony default export */ function transition() { - var name = this._name, - id0 = this._id, - id1 = newId(); - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - var inherit = schedule_get(node, id0); - schedule(node, name, id1, i, group, { - time: inherit.time + inherit.delay + inherit.duration, - delay: 0, - duration: inherit.duration, - ease: inherit.ease - }); - } - } - } - - return new Transition(groups, this._parents, name, id1); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/end.js - - -/* harmony default export */ function end() { - var on0, on1, that = this, id = that._id, size = that.size(); - return new Promise(function(resolve, reject) { - var cancel = {value: reject}, - end = {value: function() { if (--size === 0) resolve(); }}; - - that.each(function() { - var schedule = schedule_set(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) { - on1 = (on0 = on).copy(); - on1._.cancel.push(cancel); - on1._.interrupt.push(cancel); - on1._.end.push(end); - } - - schedule.on = on1; - }); - - // The selection was empty, resolve end immediately - if (size === 0) resolve(); - }); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/index.js - - - - - - - - - - - - - - - - - - - - - - -var id = 0; - -function Transition(groups, parents, name, id) { - this._groups = groups; - this._parents = parents; - this._name = name; - this._id = id; -} - -function transition_transition(name) { - return src_selection().transition(name); -} - -function newId() { - return ++id; -} - -var selection_prototype = src_selection.prototype; - -Transition.prototype = transition_transition.prototype = { - constructor: Transition, - select: transition_select, - selectAll: transition_selectAll, - selectChild: selection_prototype.selectChild, - selectChildren: selection_prototype.selectChildren, - filter: transition_filter, - merge: transition_merge, - selection: transition_selection, - transition: transition, - call: selection_prototype.call, - nodes: selection_prototype.nodes, - node: selection_prototype.node, - size: selection_prototype.size, - empty: selection_prototype.empty, - each: selection_prototype.each, - on: transition_on, - attr: transition_attr, - attrTween: transition_attrTween, - style: transition_style, - styleTween: transition_styleTween, - text: transition_text, - textTween: transition_textTween, - remove: transition_remove, - tween: tween, - delay: delay, - duration: duration, - ease: ease, - easeVarying: transition_easeVarying, - end: end, - [Symbol.iterator]: selection_prototype[Symbol.iterator] -}; - -;// CONCATENATED MODULE: ../node_modules/d3-ease/src/cubic.js -function cubicIn(t) { - return t * t * t; -} - -function cubicOut(t) { - return --t * t * t + 1; -} - -function cubicInOut(t) { - return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/transition.js - - - - - -var defaultTiming = { - time: null, // Set on use. - delay: 0, - duration: 250, - ease: cubicInOut -}; - -function inherit(node, id) { - var timing; - while (!(timing = node.__transition) || !(timing = timing[id])) { - if (!(node = node.parentNode)) { - throw new Error(`transition ${id} not found`); - } - } - return timing; -} - -/* harmony default export */ function selection_transition(name) { - var id, - timing; - - if (name instanceof Transition) { - id = name._id, name = name._name; - } else { - id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + ""; - } - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - schedule(node, name, id, i, group, timing || inherit(node, id)); - } - } - } - - return new Transition(groups, this._parents, name, id); -} - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/index.js - - - - -src_selection.prototype.interrupt = selection_interrupt; -src_selection.prototype.transition = selection_transition; - -;// CONCATENATED MODULE: ../node_modules/d3-transition/src/index.js - - - - - -;// CONCATENATED MODULE: ./tooltip.js -/* global event */ - - - - - - -function defaultLabel (d) { - return d.data.name -} - -function defaultFlamegraphTooltip () { - var rootElement = src_select('body') - var tooltip = null - // Function to get HTML content from data. - var html = defaultLabel - // Function to get text content from data. - var text = defaultLabel - // Whether to use d3's .html() to set content, otherwise use .text(). - var contentIsHTML = false - - function tip () { - tooltip = rootElement - .append('div') - .style('display', 'none') - .style('position', 'absolute') - .style('opacity', 0) - .style('pointer-events', 'none') - .attr('class', 'd3-flame-graph-tip') - } - - tip.show = function (d) { - tooltip - .style('display', 'block') - .style('left', event.pageX + 5 + 'px') - .style('top', event.pageY + 5 + 'px') - .transition() - .duration(200) - .style('opacity', 1) - .style('pointer-events', 'all') - - if (contentIsHTML) { - tooltip.html(html(d)) - } else { - tooltip.text(text(d)) - } - - return tip - } - - tip.hide = function () { - tooltip - .style('display', 'none') - .transition() - .duration(200) - .style('opacity', 0) - .style('pointer-events', 'none') - - return tip - } - - /** - * Gets/sets a function converting the d3 data into the tooltip's textContent. - * - * Cannot be combined with tip.html(). - */ - tip.text = function (_) { - if (!arguments.length) return text - text = _ - contentIsHTML = false - return tip - } - - /** - * Gets/sets a function converting the d3 data into the tooltip's innerHTML. - * - * Cannot be combined with tip.text(). - * - * @deprecated prefer tip.text(). - */ - tip.html = function (_) { - if (!arguments.length) return html - html = _ - contentIsHTML = true - return tip - } - - tip.destroy = function () { - tooltip.remove() - } - - return tip -} - -/******/ return __webpack_exports__; -/******/ })() -; -}); \ No newline at end of file diff --git a/development/charts/flamegraph/lib/d3-flamegraph.css b/development/charts/flamegraph/lib/d3-flamegraph.css deleted file mode 100644 index fa6f345ff7a9..000000000000 --- a/development/charts/flamegraph/lib/d3-flamegraph.css +++ /dev/null @@ -1,46 +0,0 @@ -.d3-flame-graph rect { - stroke: #EEEEEE; - fill-opacity: .8; -} - -.d3-flame-graph rect:hover { - stroke: #474747; - stroke-width: 0.5; - cursor: pointer; -} - -.d3-flame-graph-label { - pointer-events: none; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - font-size: 12px; - font-family: Verdana; - margin-left: 4px; - margin-right: 4px; - line-height: 1.5; - padding: 0 0 0; - font-weight: 400; - color: black; - text-align: left; -} - -.d3-flame-graph .fade { - opacity: 0.6 !important; -} - -.d3-flame-graph .title { - font-size: 20px; - font-family: Verdana; -} - -.d3-flame-graph-tip { - background-color: black; - border: none; - border-radius: 3px; - padding: 5px 10px 5px 10px; - min-width: 250px; - text-align: left; - color: white; - z-index: 10; -} \ No newline at end of file diff --git a/development/charts/flamegraph/lib/d3-flamegraph.js b/development/charts/flamegraph/lib/d3-flamegraph.js deleted file mode 100644 index eabb2c44972c..000000000000 --- a/development/charts/flamegraph/lib/d3-flamegraph.js +++ /dev/null @@ -1,5719 +0,0 @@ -(function webpackUniversalModuleDefinition(root, factory) { - if (typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if (typeof define === 'function' && define.amd) define([], factory); - else if (typeof exports === 'object') exports['flamegraph'] = factory(); - else root['flamegraph'] = factory(); -})(self, function () { - return /******/ (() => { - // webpackBootstrap - /******/ 'use strict'; // The require scope - /******/ /******/ var __webpack_require__ = {}; /* webpack/runtime/define property getters */ - /******/ - /************************************************************************/ - /******/ /******/ (() => { - /******/ // define getter functions for harmony exports - /******/ __webpack_require__.d = (exports, definition) => { - /******/ for (var key in definition) { - /******/ if ( - __webpack_require__.o(definition, key) && - !__webpack_require__.o(exports, key) - ) { - /******/ Object.defineProperty(exports, key, { - enumerable: true, - get: definition[key], - }); - /******/ - } - /******/ - } - /******/ - }; - /******/ - })(); /* webpack/runtime/hasOwnProperty shorthand */ - /******/ - /******/ /******/ (() => { - /******/ __webpack_require__.o = (obj, prop) => - Object.prototype.hasOwnProperty.call(obj, prop); - /******/ - })(); - /******/ - /************************************************************************/ - var __webpack_exports__ = {}; - - // EXPORTS - __webpack_require__.d(__webpack_exports__, { - default: () => /* binding */ flamegraph, - }); // CONCATENATED MODULE: ../node_modules/d3-selection/src/selector.js - - function none() {} - - /* harmony default export */ function selector(selector) { - return selector == null - ? none - : function () { - return this.querySelector(selector); - }; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/select.js - - /* harmony default export */ function selection_select(select) { - if (typeof select !== 'function') select = selector(select); - - for ( - var groups = this._groups, - m = groups.length, - subgroups = new Array(m), - j = 0; - j < m; - ++j - ) { - for ( - var group = groups[j], - n = group.length, - subgroup = (subgroups[j] = new Array(n)), - node, - subnode, - i = 0; - i < n; - ++i - ) { - if ( - (node = group[i]) && - (subnode = select.call(node, node.__data__, i, group)) - ) { - if ('__data__' in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - } - } - } - - return new Selection(subgroups, this._parents); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/array.js - - // Given something array like (or null), returns something that is strictly an - // array. This is used to ensure that array-like objects passed to d3.selectAll - // or selection.selectAll are converted into proper arrays when creating a - // selection; we don’t ever want to create a selection backed by a live - // HTMLCollection or NodeList. However, note that selection.selectAll will use a - // static NodeList as a group, since it safely derived from querySelectorAll. - function array(x) { - return x == null ? [] : Array.isArray(x) ? x : Array.from(x); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selectorAll.js - - function empty() { - return []; - } - - /* harmony default export */ function selectorAll(selector) { - return selector == null - ? empty - : function () { - return this.querySelectorAll(selector); - }; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectAll.js - - function arrayAll(select) { - return function () { - return array(select.apply(this, arguments)); - }; - } - - /* harmony default export */ function selectAll(select) { - if (typeof select === 'function') select = arrayAll(select); - else select = selectorAll(select); - - for ( - var groups = this._groups, - m = groups.length, - subgroups = [], - parents = [], - j = 0; - j < m; - ++j - ) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if ((node = group[i])) { - subgroups.push(select.call(node, node.__data__, i, group)); - parents.push(node); - } - } - } - - return new Selection(subgroups, parents); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/matcher.js - - /* harmony default export */ function matcher(selector) { - return function () { - return this.matches(selector); - }; - } - - function childMatcher(selector) { - return function (node) { - return node.matches(selector); - }; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChild.js - - var find = Array.prototype.find; - - function childFind(match) { - return function () { - return find.call(this.children, match); - }; - } - - function childFirst() { - return this.firstElementChild; - } - - /* harmony default export */ function selectChild(match) { - return this.select( - match == null - ? childFirst - : childFind( - typeof match === 'function' ? match : childMatcher(match), - ), - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChildren.js - - var filter = Array.prototype.filter; - - function children() { - return Array.from(this.children); - } - - function childrenFilter(match) { - return function () { - return filter.call(this.children, match); - }; - } - - /* harmony default export */ function selectChildren(match) { - return this.selectAll( - match == null - ? children - : childrenFilter( - typeof match === 'function' ? match : childMatcher(match), - ), - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/filter.js - - /* harmony default export */ function selection_filter(match) { - if (typeof match !== 'function') match = matcher(match); - - for ( - var groups = this._groups, - m = groups.length, - subgroups = new Array(m), - j = 0; - j < m; - ++j - ) { - for ( - var group = groups[j], - n = group.length, - subgroup = (subgroups[j] = []), - node, - i = 0; - i < n; - ++i - ) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Selection(subgroups, this._parents); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sparse.js - - /* harmony default export */ function sparse(update) { - return new Array(update.length); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/enter.js - - /* harmony default export */ function enter() { - return new Selection( - this._enter || this._groups.map(sparse), - this._parents, - ); - } - - function EnterNode(parent, datum) { - this.ownerDocument = parent.ownerDocument; - this.namespaceURI = parent.namespaceURI; - this._next = null; - this._parent = parent; - this.__data__ = datum; - } - - EnterNode.prototype = { - constructor: EnterNode, - appendChild: function (child) { - return this._parent.insertBefore(child, this._next); - }, - insertBefore: function (child, next) { - return this._parent.insertBefore(child, next); - }, - querySelector: function (selector) { - return this._parent.querySelector(selector); - }, - querySelectorAll: function (selector) { - return this._parent.querySelectorAll(selector); - }, - }; // CONCATENATED MODULE: ../node_modules/d3-selection/src/constant.js - - /* harmony default export */ function src_constant(x) { - return function () { - return x; - }; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/data.js - - function bindIndex(parent, group, enter, update, exit, data) { - var i = 0, - node, - groupLength = group.length, - dataLength = data.length; - - // Put any non-null nodes that fit into update. - // Put any null nodes into enter. - // Put any remaining data into enter. - for (; i < dataLength; ++i) { - if ((node = group[i])) { - node.__data__ = data[i]; - update[i] = node; - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Put any non-null nodes that don’t fit into exit. - for (; i < groupLength; ++i) { - if ((node = group[i])) { - exit[i] = node; - } - } - } - - function bindKey(parent, group, enter, update, exit, data, key) { - var i, - node, - nodeByKeyValue = new Map(), - groupLength = group.length, - dataLength = data.length, - keyValues = new Array(groupLength), - keyValue; - - // Compute the key for each node. - // If multiple nodes have the same key, the duplicates are added to exit. - for (i = 0; i < groupLength; ++i) { - if ((node = group[i])) { - keyValues[i] = keyValue = - key.call(node, node.__data__, i, group) + ''; - if (nodeByKeyValue.has(keyValue)) { - exit[i] = node; - } else { - nodeByKeyValue.set(keyValue, node); - } - } - } - - // Compute the key for each datum. - // If there a node associated with this key, join and add it to update. - // If there is not (or the key is a duplicate), add it to enter. - for (i = 0; i < dataLength; ++i) { - keyValue = key.call(parent, data[i], i, data) + ''; - if ((node = nodeByKeyValue.get(keyValue))) { - update[i] = node; - node.__data__ = data[i]; - nodeByKeyValue.delete(keyValue); - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Add any remaining nodes that were not bound to data to exit. - for (i = 0; i < groupLength; ++i) { - if ((node = group[i]) && nodeByKeyValue.get(keyValues[i]) === node) { - exit[i] = node; - } - } - } - - function datum(node) { - return node.__data__; - } - - /* harmony default export */ function data(value, key) { - if (!arguments.length) return Array.from(this, datum); - - var bind = key ? bindKey : bindIndex, - parents = this._parents, - groups = this._groups; - - if (typeof value !== 'function') value = src_constant(value); - - for ( - var m = groups.length, - update = new Array(m), - enter = new Array(m), - exit = new Array(m), - j = 0; - j < m; - ++j - ) { - var parent = parents[j], - group = groups[j], - groupLength = group.length, - data = arraylike( - value.call(parent, parent && parent.__data__, j, parents), - ), - dataLength = data.length, - enterGroup = (enter[j] = new Array(dataLength)), - updateGroup = (update[j] = new Array(dataLength)), - exitGroup = (exit[j] = new Array(groupLength)); - - bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); - - // Now connect the enter nodes to their following update node, such that - // appendChild can insert the materialized enter node before this node, - // rather than at the end of the parent node. - for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { - if ((previous = enterGroup[i0])) { - if (i0 >= i1) i1 = i0 + 1; - while (!(next = updateGroup[i1]) && ++i1 < dataLength); - previous._next = next || null; - } - } - } - - update = new Selection(update, parents); - update._enter = enter; - update._exit = exit; - return update; - } - - // Given some data, this returns an array-like view of it: an object that - // exposes a length property and allows numeric indexing. Note that unlike - // selectAll, this isn’t worried about “live” collections because the resulting - // array will only be used briefly while data is being bound. (It is possible to - // cause the data to change while iterating by using a key function, but please - // don’t; we’d rather avoid a gratuitous copy.) - function arraylike(data) { - return typeof data === 'object' && 'length' in data - ? data // Array, TypedArray, NodeList, array-like - : Array.from(data); // Map, Set, iterable, string, or anything else - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/exit.js - - /* harmony default export */ function exit() { - return new Selection( - this._exit || this._groups.map(sparse), - this._parents, - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/join.js - - /* harmony default export */ function join(onenter, onupdate, onexit) { - var enter = this.enter(), - update = this, - exit = this.exit(); - if (typeof onenter === 'function') { - enter = onenter(enter); - if (enter) enter = enter.selection(); - } else { - enter = enter.append(onenter + ''); - } - if (onupdate != null) { - update = onupdate(update); - if (update) update = update.selection(); - } - if (onexit == null) exit.remove(); - else onexit(exit); - return enter && update ? enter.merge(update).order() : update; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/merge.js - - /* harmony default export */ function merge(context) { - var selection = context.selection ? context.selection() : context; - - for ( - var groups0 = this._groups, - groups1 = selection._groups, - m0 = groups0.length, - m1 = groups1.length, - m = Math.min(m0, m1), - merges = new Array(m0), - j = 0; - j < m; - ++j - ) { - for ( - var group0 = groups0[j], - group1 = groups1[j], - n = group0.length, - merge = (merges[j] = new Array(n)), - node, - i = 0; - i < n; - ++i - ) { - if ((node = group0[i] || group1[i])) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Selection(merges, this._parents); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/order.js - - /* harmony default export */ function order() { - for (var groups = this._groups, j = -1, m = groups.length; ++j < m; ) { - for ( - var group = groups[j], i = group.length - 1, next = group[i], node; - --i >= 0; - - ) { - if ((node = group[i])) { - if (next && node.compareDocumentPosition(next) ^ 4) - next.parentNode.insertBefore(node, next); - next = node; - } - } - } - - return this; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sort.js - - /* harmony default export */ function sort(compare) { - if (!compare) compare = ascending; - - function compareNode(a, b) { - return a && b ? compare(a.__data__, b.__data__) : !a - !b; - } - - for ( - var groups = this._groups, - m = groups.length, - sortgroups = new Array(m), - j = 0; - j < m; - ++j - ) { - for ( - var group = groups[j], - n = group.length, - sortgroup = (sortgroups[j] = new Array(n)), - node, - i = 0; - i < n; - ++i - ) { - if ((node = group[i])) { - sortgroup[i] = node; - } - } - sortgroup.sort(compareNode); - } - - return new Selection(sortgroups, this._parents).order(); - } - - function ascending(a, b) { - return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/call.js - - /* harmony default export */ function call() { - var callback = arguments[0]; - arguments[0] = this; - callback.apply(null, arguments); - return this; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/nodes.js - - /* harmony default export */ function nodes() { - return Array.from(this); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/node.js - - /* harmony default export */ function node() { - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { - var node = group[i]; - if (node) return node; - } - } - - return null; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/size.js - - /* harmony default export */ function size() { - let size = 0; - for (const node of this) ++size; // eslint-disable-line no-unused-vars - return size; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/empty.js - - /* harmony default export */ function selection_empty() { - return !this.node(); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/each.js - - /* harmony default export */ function each(callback) { - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if ((node = group[i])) callback.call(node, node.__data__, i, group); - } - } - - return this; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/namespaces.js - - var xhtml = 'http://www.w3.org/1999/xhtml'; - - /* harmony default export */ const namespaces = { - svg: 'http://www.w3.org/2000/svg', - xhtml: xhtml, - xlink: 'http://www.w3.org/1999/xlink', - xml: 'http://www.w3.org/XML/1998/namespace', - xmlns: 'http://www.w3.org/2000/xmlns/', - }; // CONCATENATED MODULE: ../node_modules/d3-selection/src/namespace.js - - /* harmony default export */ function namespace(name) { - var prefix = (name += ''), - i = prefix.indexOf(':'); - if (i >= 0 && (prefix = name.slice(0, i)) !== 'xmlns') - name = name.slice(i + 1); - return namespaces.hasOwnProperty(prefix) - ? { space: namespaces[prefix], local: name } - : name; // eslint-disable-line no-prototype-builtins - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/attr.js - - function attrRemove(name) { - return function () { - this.removeAttribute(name); - }; - } - - function attrRemoveNS(fullname) { - return function () { - this.removeAttributeNS(fullname.space, fullname.local); - }; - } - - function attrConstant(name, value) { - return function () { - this.setAttribute(name, value); - }; - } - - function attrConstantNS(fullname, value) { - return function () { - this.setAttributeNS(fullname.space, fullname.local, value); - }; - } - - function attrFunction(name, value) { - return function () { - var v = value.apply(this, arguments); - if (v == null) this.removeAttribute(name); - else this.setAttribute(name, v); - }; - } - - function attrFunctionNS(fullname, value) { - return function () { - var v = value.apply(this, arguments); - if (v == null) this.removeAttributeNS(fullname.space, fullname.local); - else this.setAttributeNS(fullname.space, fullname.local, v); - }; - } - - /* harmony default export */ function attr(name, value) { - var fullname = namespace(name); - - if (arguments.length < 2) { - var node = this.node(); - return fullname.local - ? node.getAttributeNS(fullname.space, fullname.local) - : node.getAttribute(fullname); - } - - return this.each( - (value == null - ? fullname.local - ? attrRemoveNS - : attrRemove - : typeof value === 'function' - ? fullname.local - ? attrFunctionNS - : attrFunction - : fullname.local - ? attrConstantNS - : attrConstant)(fullname, value), - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/window.js - - /* harmony default export */ function src_window(node) { - return ( - (node.ownerDocument && node.ownerDocument.defaultView) || // node is a Node - (node.document && node) || // node is a Window - node.defaultView - ); // node is a Document - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/style.js - - function styleRemove(name) { - return function () { - this.style.removeProperty(name); - }; - } - - function styleConstant(name, value, priority) { - return function () { - this.style.setProperty(name, value, priority); - }; - } - - function styleFunction(name, value, priority) { - return function () { - var v = value.apply(this, arguments); - if (v == null) this.style.removeProperty(name); - else this.style.setProperty(name, v, priority); - }; - } - - /* harmony default export */ function style(name, value, priority) { - return arguments.length > 1 - ? this.each( - (value == null - ? styleRemove - : typeof value === 'function' - ? styleFunction - : styleConstant)(name, value, priority == null ? '' : priority), - ) - : styleValue(this.node(), name); - } - - function styleValue(node, name) { - return ( - node.style.getPropertyValue(name) || - src_window(node).getComputedStyle(node, null).getPropertyValue(name) - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/property.js - - function propertyRemove(name) { - return function () { - delete this[name]; - }; - } - - function propertyConstant(name, value) { - return function () { - this[name] = value; - }; - } - - function propertyFunction(name, value) { - return function () { - var v = value.apply(this, arguments); - if (v == null) delete this[name]; - else this[name] = v; - }; - } - - /* harmony default export */ function property(name, value) { - return arguments.length > 1 - ? this.each( - (value == null - ? propertyRemove - : typeof value === 'function' - ? propertyFunction - : propertyConstant)(name, value), - ) - : this.node()[name]; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/classed.js - - function classArray(string) { - return string.trim().split(/^|\s+/); - } - - function classList(node) { - return node.classList || new ClassList(node); - } - - function ClassList(node) { - this._node = node; - this._names = classArray(node.getAttribute('class') || ''); - } - - ClassList.prototype = { - add: function (name) { - var i = this._names.indexOf(name); - if (i < 0) { - this._names.push(name); - this._node.setAttribute('class', this._names.join(' ')); - } - }, - remove: function (name) { - var i = this._names.indexOf(name); - if (i >= 0) { - this._names.splice(i, 1); - this._node.setAttribute('class', this._names.join(' ')); - } - }, - contains: function (name) { - return this._names.indexOf(name) >= 0; - }, - }; - - function classedAdd(node, names) { - var list = classList(node), - i = -1, - n = names.length; - while (++i < n) list.add(names[i]); - } - - function classedRemove(node, names) { - var list = classList(node), - i = -1, - n = names.length; - while (++i < n) list.remove(names[i]); - } - - function classedTrue(names) { - return function () { - classedAdd(this, names); - }; - } - - function classedFalse(names) { - return function () { - classedRemove(this, names); - }; - } - - function classedFunction(names, value) { - return function () { - (value.apply(this, arguments) ? classedAdd : classedRemove)( - this, - names, - ); - }; - } - - /* harmony default export */ function classed(name, value) { - var names = classArray(name + ''); - - if (arguments.length < 2) { - var list = classList(this.node()), - i = -1, - n = names.length; - while (++i < n) if (!list.contains(names[i])) return false; - return true; - } - - return this.each( - (typeof value === 'function' - ? classedFunction - : value - ? classedTrue - : classedFalse)(names, value), - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/text.js - - function textRemove() { - this.textContent = ''; - } - - function textConstant(value) { - return function () { - this.textContent = value; - }; - } - - function textFunction(value) { - return function () { - var v = value.apply(this, arguments); - this.textContent = v == null ? '' : v; - }; - } - - /* harmony default export */ function selection_text(value) { - return arguments.length - ? this.each( - value == null - ? textRemove - : (typeof value === 'function' ? textFunction : textConstant)( - value, - ), - ) - : this.node().textContent; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/html.js - - function htmlRemove() { - this.innerHTML = ''; - } - - function htmlConstant(value) { - return function () { - this.innerHTML = value; - }; - } - - function htmlFunction(value) { - return function () { - var v = value.apply(this, arguments); - this.innerHTML = v == null ? '' : v; - }; - } - - /* harmony default export */ function html(value) { - return arguments.length - ? this.each( - value == null - ? htmlRemove - : (typeof value === 'function' ? htmlFunction : htmlConstant)( - value, - ), - ) - : this.node().innerHTML; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/raise.js - - function raise() { - if (this.nextSibling) this.parentNode.appendChild(this); - } - - /* harmony default export */ function selection_raise() { - return this.each(raise); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/lower.js - - function lower() { - if (this.previousSibling) - this.parentNode.insertBefore(this, this.parentNode.firstChild); - } - - /* harmony default export */ function selection_lower() { - return this.each(lower); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/creator.js - - function creatorInherit(name) { - return function () { - var document = this.ownerDocument, - uri = this.namespaceURI; - return uri === xhtml && document.documentElement.namespaceURI === xhtml - ? document.createElement(name) - : document.createElementNS(uri, name); - }; - } - - function creatorFixed(fullname) { - return function () { - return this.ownerDocument.createElementNS( - fullname.space, - fullname.local, - ); - }; - } - - /* harmony default export */ function creator(name) { - var fullname = namespace(name); - return (fullname.local ? creatorFixed : creatorInherit)(fullname); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/append.js - - /* harmony default export */ function append(name) { - var create = typeof name === 'function' ? name : creator(name); - return this.select(function () { - return this.appendChild(create.apply(this, arguments)); - }); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/insert.js - - function constantNull() { - return null; - } - - /* harmony default export */ function insert(name, before) { - var create = typeof name === 'function' ? name : creator(name), - select = - before == null - ? constantNull - : typeof before === 'function' - ? before - : selector(before); - return this.select(function () { - return this.insertBefore( - create.apply(this, arguments), - select.apply(this, arguments) || null, - ); - }); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/remove.js - - function remove() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); - } - - /* harmony default export */ function selection_remove() { - return this.each(remove); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/clone.js - - function selection_cloneShallow() { - var clone = this.cloneNode(false), - parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; - } - - function selection_cloneDeep() { - var clone = this.cloneNode(true), - parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; - } - - /* harmony default export */ function clone(deep) { - return this.select(deep ? selection_cloneDeep : selection_cloneShallow); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/datum.js - - /* harmony default export */ function selection_datum(value) { - return arguments.length - ? this.property('__data__', value) - : this.node().__data__; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/on.js - - function contextListener(listener) { - return function (event) { - listener.call(this, event, this.__data__); - }; - } - - function parseTypenames(typenames) { - return typenames - .trim() - .split(/^|\s+/) - .map(function (t) { - var name = '', - i = t.indexOf('.'); - if (i >= 0) (name = t.slice(i + 1)), (t = t.slice(0, i)); - return { type: t, name: name }; - }); - } - - function onRemove(typename) { - return function () { - var on = this.__on; - if (!on) return; - for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { - if ( - ((o = on[j]), - (!typename.type || o.type === typename.type) && - o.name === typename.name) - ) { - this.removeEventListener(o.type, o.listener, o.options); - } else { - on[++i] = o; - } - } - if (++i) on.length = i; - else delete this.__on; - }; - } - - function onAdd(typename, value, options) { - return function () { - var on = this.__on, - o, - listener = contextListener(value); - if (on) - for (var j = 0, m = on.length; j < m; ++j) { - if ( - (o = on[j]).type === typename.type && - o.name === typename.name - ) { - this.removeEventListener(o.type, o.listener, o.options); - this.addEventListener( - o.type, - (o.listener = listener), - (o.options = options), - ); - o.value = value; - return; - } - } - this.addEventListener(typename.type, listener, options); - o = { - type: typename.type, - name: typename.name, - value: value, - listener: listener, - options: options, - }; - if (!on) this.__on = [o]; - else on.push(o); - }; - } - - /* harmony default export */ function on(typename, value, options) { - var typenames = parseTypenames(typename + ''), - i, - n = typenames.length, - t; - - if (arguments.length < 2) { - var on = this.node().__on; - if (on) - for (var j = 0, m = on.length, o; j < m; ++j) { - for (i = 0, o = on[j]; i < n; ++i) { - if ((t = typenames[i]).type === o.type && t.name === o.name) { - return o.value; - } - } - } - return; - } - - on = value ? onAdd : onRemove; - for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options)); - return this; - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/dispatch.js - - function dispatchEvent(node, type, params) { - var window = src_window(node), - event = window.CustomEvent; - - if (typeof event === 'function') { - event = new event(type, params); - } else { - event = window.document.createEvent('Event'); - if (params) - event.initEvent(type, params.bubbles, params.cancelable), - (event.detail = params.detail); - else event.initEvent(type, false, false); - } - - node.dispatchEvent(event); - } - - function dispatchConstant(type, params) { - return function () { - return dispatchEvent(this, type, params); - }; - } - - function dispatchFunction(type, params) { - return function () { - return dispatchEvent(this, type, params.apply(this, arguments)); - }; - } - - /* harmony default export */ function dispatch(type, params) { - return this.each( - (typeof params === 'function' ? dispatchFunction : dispatchConstant)( - type, - params, - ), - ); - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/iterator.js - - /* harmony default export */ function* iterator() { - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if ((node = group[i])) yield node; - } - } - } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/index.js - - var root = [null]; - - function Selection(groups, parents) { - this._groups = groups; - this._parents = parents; - } - - function selection() { - return new Selection([[document.documentElement]], root); - } - - function selection_selection() { - return this; - } - - Selection.prototype = selection.prototype = { - constructor: Selection, - select: selection_select, - selectAll: selectAll, - selectChild: selectChild, - selectChildren: selectChildren, - filter: selection_filter, - data: data, - enter: enter, - exit: exit, - join: join, - merge: merge, - selection: selection_selection, - order: order, - sort: sort, - call: call, - nodes: nodes, - node: node, - size: size, - empty: selection_empty, - each: each, - attr: attr, - style: style, - property: property, - classed: classed, - text: selection_text, - html: html, - raise: selection_raise, - lower: selection_lower, - append: append, - insert: insert, - remove: selection_remove, - clone: clone, - datum: selection_datum, - on: on, - dispatch: dispatch, - [Symbol.iterator]: iterator, - }; - - /* harmony default export */ const src_selection = selection; // CONCATENATED MODULE: ../node_modules/d3-selection/src/select.js - - /* harmony default export */ function src_select(selector) { - return typeof selector === 'string' - ? new Selection( - [[document.querySelector(selector)]], - [document.documentElement], - ) - : new Selection([[selector]], root); - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatDecimal.js - - /* harmony default export */ function formatDecimal(x) { - return Math.abs((x = Math.round(x))) >= 1e21 - ? x.toLocaleString('en').replace(/,/g, '') - : x.toString(10); - } - - // Computes the decimal coefficient and exponent of the specified number x with - // significant digits p, where x is positive and p is in [1, 21] or undefined. - // For example, formatDecimalParts(1.23) returns ["123", 0]. - function formatDecimalParts(x, p) { - if ( - (i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf( - 'e', - )) < 0 - ) - return null; // NaN, ±Infinity - var i, - coefficient = x.slice(0, i); - - // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ - // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). - return [ - coefficient.length > 1 - ? coefficient[0] + coefficient.slice(2) - : coefficient, - +x.slice(i + 1), - ]; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/exponent.js - - /* harmony default export */ function exponent(x) { - return (x = formatDecimalParts(Math.abs(x))), x ? x[1] : NaN; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatGroup.js - - /* harmony default export */ function formatGroup(grouping, thousands) { - return function (value, width) { - var i = value.length, - t = [], - j = 0, - g = grouping[0], - length = 0; - - while (i > 0 && g > 0) { - if (length + g + 1 > width) g = Math.max(1, width - length); - t.push(value.substring((i -= g), i + g)); - if ((length += g + 1) > width) break; - g = grouping[(j = (j + 1) % grouping.length)]; - } - - return t.reverse().join(thousands); - }; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatNumerals.js - - /* harmony default export */ function formatNumerals(numerals) { - return function (value) { - return value.replace(/[0-9]/g, function (i) { - return numerals[+i]; - }); - }; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatSpecifier.js - - // [[fill]align][sign][symbol][0][width][,][.precision][~][type] - var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; - - function formatSpecifier(specifier) { - if (!(match = re.exec(specifier))) - throw new Error('invalid format: ' + specifier); - var match; - return new FormatSpecifier({ - fill: match[1], - align: match[2], - sign: match[3], - symbol: match[4], - zero: match[5], - width: match[6], - comma: match[7], - precision: match[8] && match[8].slice(1), - trim: match[9], - type: match[10], - }); - } - - formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof - - function FormatSpecifier(specifier) { - this.fill = specifier.fill === undefined ? ' ' : specifier.fill + ''; - this.align = specifier.align === undefined ? '>' : specifier.align + ''; - this.sign = specifier.sign === undefined ? '-' : specifier.sign + ''; - this.symbol = specifier.symbol === undefined ? '' : specifier.symbol + ''; - this.zero = !!specifier.zero; - this.width = specifier.width === undefined ? undefined : +specifier.width; - this.comma = !!specifier.comma; - this.precision = - specifier.precision === undefined ? undefined : +specifier.precision; - this.trim = !!specifier.trim; - this.type = specifier.type === undefined ? '' : specifier.type + ''; - } - - FormatSpecifier.prototype.toString = function () { - return ( - this.fill + - this.align + - this.sign + - this.symbol + - (this.zero ? '0' : '') + - (this.width === undefined ? '' : Math.max(1, this.width | 0)) + - (this.comma ? ',' : '') + - (this.precision === undefined - ? '' - : '.' + Math.max(0, this.precision | 0)) + - (this.trim ? '~' : '') + - this.type - ); - }; // CONCATENATED MODULE: ../node_modules/d3-format/src/formatTrim.js - - // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k. - /* harmony default export */ function formatTrim(s) { - out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { - switch (s[i]) { - case '.': - i0 = i1 = i; - break; - case '0': - if (i0 === 0) i0 = i; - i1 = i; - break; - default: - if (!+s[i]) break out; - if (i0 > 0) i0 = 0; - break; - } - } - return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatPrefixAuto.js - - var prefixExponent; - - /* harmony default export */ function formatPrefixAuto(x, p) { - var d = formatDecimalParts(x, p); - if (!d) return x + ''; - var coefficient = d[0], - exponent = d[1], - i = - exponent - - (prefixExponent = - Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + - 1, - n = coefficient.length; - return i === n - ? coefficient - : i > n - ? coefficient + new Array(i - n + 1).join('0') - : i > 0 - ? coefficient.slice(0, i) + '.' + coefficient.slice(i) - : '0.' + - new Array(1 - i).join('0') + - formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y! - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatRounded.js - - /* harmony default export */ function formatRounded(x, p) { - var d = formatDecimalParts(x, p); - if (!d) return x + ''; - var coefficient = d[0], - exponent = d[1]; - return exponent < 0 - ? '0.' + new Array(-exponent).join('0') + coefficient - : coefficient.length > exponent + 1 - ? coefficient.slice(0, exponent + 1) + - '.' + - coefficient.slice(exponent + 1) - : coefficient + new Array(exponent - coefficient.length + 2).join('0'); - } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatTypes.js - - /* harmony default export */ const formatTypes = { - '%': (x, p) => (x * 100).toFixed(p), - b: (x) => Math.round(x).toString(2), - c: (x) => x + '', - d: formatDecimal, - e: (x, p) => x.toExponential(p), - f: (x, p) => x.toFixed(p), - g: (x, p) => x.toPrecision(p), - o: (x) => Math.round(x).toString(8), - p: (x, p) => formatRounded(x * 100, p), - r: formatRounded, - s: formatPrefixAuto, - X: (x) => Math.round(x).toString(16).toUpperCase(), - x: (x) => Math.round(x).toString(16), - }; // CONCATENATED MODULE: ../node_modules/d3-format/src/identity.js - - /* harmony default export */ function identity(x) { - return x; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/locale.js - - var map = Array.prototype.map, - prefixes = [ - 'y', - 'z', - 'a', - 'f', - 'p', - 'n', - 'µ', - 'm', - '', - 'k', - 'M', - 'G', - 'T', - 'P', - 'E', - 'Z', - 'Y', - ]; - - /* harmony default export */ function locale(locale) { - var group = - locale.grouping === undefined || locale.thousands === undefined - ? identity - : formatGroup( - map.call(locale.grouping, Number), - locale.thousands + '', - ), - currencyPrefix = - locale.currency === undefined ? '' : locale.currency[0] + '', - currencySuffix = - locale.currency === undefined ? '' : locale.currency[1] + '', - decimal = locale.decimal === undefined ? '.' : locale.decimal + '', - numerals = - locale.numerals === undefined - ? identity - : formatNumerals(map.call(locale.numerals, String)), - percent = locale.percent === undefined ? '%' : locale.percent + '', - minus = locale.minus === undefined ? '−' : locale.minus + '', - nan = locale.nan === undefined ? 'NaN' : locale.nan + ''; - - function newFormat(specifier) { - specifier = formatSpecifier(specifier); - - var fill = specifier.fill, - align = specifier.align, - sign = specifier.sign, - symbol = specifier.symbol, - zero = specifier.zero, - width = specifier.width, - comma = specifier.comma, - precision = specifier.precision, - trim = specifier.trim, - type = specifier.type; - - // The "n" type is an alias for ",g". - if (type === 'n') (comma = true), (type = 'g'); - // The "" type, and any invalid type, is an alias for ".12~g". - else if (!formatTypes[type]) - precision === undefined && (precision = 12), - (trim = true), - (type = 'g'); - - // If zero fill is specified, padding goes after sign and before digits. - if (zero || (fill === '0' && align === '=')) - (zero = true), (fill = '0'), (align = '='); - - // Compute the prefix and suffix. - // For SI-prefix, the suffix is lazily computed. - var prefix = - symbol === '$' - ? currencyPrefix - : symbol === '#' && /[boxX]/.test(type) - ? '0' + type.toLowerCase() - : '', - suffix = - symbol === '$' ? currencySuffix : /[%p]/.test(type) ? percent : ''; - - // What format function should we use? - // Is this an integer type? - // Can this type generate exponential notation? - var formatType = formatTypes[type], - maybeSuffix = /[defgprs%]/.test(type); - - // Set the default precision if not specified, - // or clamp the specified precision to the supported range. - // For significant precision, it must be in [1, 21]. - // For fixed precision, it must be in [0, 20]. - precision = - precision === undefined - ? 6 - : /[gprs]/.test(type) - ? Math.max(1, Math.min(21, precision)) - : Math.max(0, Math.min(20, precision)); - - function format(value) { - var valuePrefix = prefix, - valueSuffix = suffix, - i, - n, - c; - - if (type === 'c') { - valueSuffix = formatType(value) + valueSuffix; - value = ''; - } else { - value = +value; - - // Determine the sign. -0 is not less than 0, but 1 / -0 is! - var valueNegative = value < 0 || 1 / value < 0; - - // Perform the initial formatting. - value = isNaN(value) ? nan : formatType(Math.abs(value), precision); - - // Trim insignificant zeros. - if (trim) value = formatTrim(value); - - // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign. - if (valueNegative && +value === 0 && sign !== '+') - valueNegative = false; - - // Compute the prefix and suffix. - valuePrefix = - (valueNegative - ? sign === '(' - ? sign - : minus - : sign === '-' || sign === '(' - ? '' - : sign) + valuePrefix; - valueSuffix = - (type === 's' ? prefixes[8 + prefixExponent / 3] : '') + - valueSuffix + - (valueNegative && sign === '(' ? ')' : ''); - - // Break the formatted value into the integer “value” part that can be - // grouped, and fractional or exponential “suffix” part that is not. - if (maybeSuffix) { - (i = -1), (n = value.length); - while (++i < n) { - if (((c = value.charCodeAt(i)), 48 > c || c > 57)) { - valueSuffix = - (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + - valueSuffix; - value = value.slice(0, i); - break; - } - } - } - } - - // If the fill character is not "0", grouping is applied before padding. - if (comma && !zero) value = group(value, Infinity); - - // Compute the padding. - var length = valuePrefix.length + value.length + valueSuffix.length, - padding = - length < width ? new Array(width - length + 1).join(fill) : ''; - - // If the fill character is "0", grouping is applied after padding. - if (comma && zero) - (value = group( - padding + value, - padding.length ? width - valueSuffix.length : Infinity, - )), - (padding = ''); - - // Reconstruct the final output based on the desired alignment. - switch (align) { - case '<': - value = valuePrefix + value + valueSuffix + padding; - break; - case '=': - value = valuePrefix + padding + value + valueSuffix; - break; - case '^': - value = - padding.slice(0, (length = padding.length >> 1)) + - valuePrefix + - value + - valueSuffix + - padding.slice(length); - break; - default: - value = padding + valuePrefix + value + valueSuffix; - break; - } - - return numerals(value); - } - - format.toString = function () { - return specifier + ''; - }; - - return format; - } - - function formatPrefix(specifier, value) { - var f = newFormat( - ((specifier = formatSpecifier(specifier)), - (specifier.type = 'f'), - specifier), - ), - e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, - k = Math.pow(10, -e), - prefix = prefixes[8 + e / 3]; - return function (value) { - return f(k * value) + prefix; - }; - } - - return { - format: newFormat, - formatPrefix: formatPrefix, - }; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/defaultLocale.js - - var defaultLocale_locale; - var format; - var formatPrefix; - - defaultLocale({ - thousands: ',', - grouping: [3], - currency: ['$', ''], - }); - - function defaultLocale(definition) { - defaultLocale_locale = locale(definition); - format = defaultLocale_locale.format; - formatPrefix = defaultLocale_locale.formatPrefix; - return defaultLocale_locale; - } // CONCATENATED MODULE: ../node_modules/d3-array/src/ascending.js - - function ascending_ascending(a, b) { - return a == null || b == null - ? NaN - : a < b - ? -1 - : a > b - ? 1 - : a >= b - ? 0 - : NaN; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/treemap/round.js - - /* harmony default export */ function treemap_round(node) { - node.x0 = Math.round(node.x0); - node.y0 = Math.round(node.y0); - node.x1 = Math.round(node.x1); - node.y1 = Math.round(node.y1); - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/treemap/dice.js - - /* harmony default export */ function dice(parent, x0, y0, x1, y1) { - var nodes = parent.children, - node, - i = -1, - n = nodes.length, - k = parent.value && (x1 - x0) / parent.value; - - while (++i < n) { - (node = nodes[i]), (node.y0 = y0), (node.y1 = y1); - (node.x0 = x0), (node.x1 = x0 += node.value * k); - } - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/partition.js - - /* harmony default export */ function partition() { - var dx = 1, - dy = 1, - padding = 0, - round = false; - - function partition(root) { - var n = root.height + 1; - root.x0 = root.y0 = padding; - root.x1 = dx; - root.y1 = dy / n; - root.eachBefore(positionNode(dy, n)); - if (round) root.eachBefore(treemap_round); - return root; - } - - function positionNode(dy, n) { - return function (node) { - if (node.children) { - dice( - node, - node.x0, - (dy * (node.depth + 1)) / n, - node.x1, - (dy * (node.depth + 2)) / n, - ); - } - var x0 = node.x0, - y0 = node.y0, - x1 = node.x1 - padding, - y1 = node.y1 - padding; - if (x1 < x0) x0 = x1 = (x0 + x1) / 2; - if (y1 < y0) y0 = y1 = (y0 + y1) / 2; - node.x0 = x0; - node.y0 = y0; - node.x1 = x1; - node.y1 = y1; - }; - } - - partition.round = function (x) { - return arguments.length ? ((round = !!x), partition) : round; - }; - - partition.size = function (x) { - return arguments.length - ? ((dx = +x[0]), (dy = +x[1]), partition) - : [dx, dy]; - }; - - partition.padding = function (x) { - return arguments.length ? ((padding = +x), partition) : padding; - }; - - return partition; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/count.js - - function count(node) { - var sum = 0, - children = node.children, - i = children && children.length; - if (!i) sum = 1; - else while (--i >= 0) sum += children[i].value; - node.value = sum; - } - - /* harmony default export */ function hierarchy_count() { - return this.eachAfter(count); - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/each.js - - /* harmony default export */ function hierarchy_each(callback, that) { - let index = -1; - for (const node of this) { - callback.call(that, node, ++index, this); - } - return this; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/eachBefore.js - - /* harmony default export */ function eachBefore(callback, that) { - var node = this, - nodes = [node], - children, - i, - index = -1; - while ((node = nodes.pop())) { - callback.call(that, node, ++index, this); - if ((children = node.children)) { - for (i = children.length - 1; i >= 0; --i) { - nodes.push(children[i]); - } - } - } - return this; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/eachAfter.js - - /* harmony default export */ function eachAfter(callback, that) { - var node = this, - nodes = [node], - next = [], - children, - i, - n, - index = -1; - while ((node = nodes.pop())) { - next.push(node); - if ((children = node.children)) { - for (i = 0, n = children.length; i < n; ++i) { - nodes.push(children[i]); - } - } - } - while ((node = next.pop())) { - callback.call(that, node, ++index, this); - } - return this; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/find.js - - /* harmony default export */ function hierarchy_find(callback, that) { - let index = -1; - for (const node of this) { - if (callback.call(that, node, ++index, this)) { - return node; - } - } - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/sum.js - - /* harmony default export */ function sum(value) { - return this.eachAfter(function (node) { - var sum = +value(node.data) || 0, - children = node.children, - i = children && children.length; - while (--i >= 0) sum += children[i].value; - node.value = sum; - }); - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/sort.js - - /* harmony default export */ function hierarchy_sort(compare) { - return this.eachBefore(function (node) { - if (node.children) { - node.children.sort(compare); - } - }); - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/path.js - - /* harmony default export */ function path(end) { - var start = this, - ancestor = leastCommonAncestor(start, end), - nodes = [start]; - while (start !== ancestor) { - start = start.parent; - nodes.push(start); - } - var k = nodes.length; - while (end !== ancestor) { - nodes.splice(k, 0, end); - end = end.parent; - } - return nodes; - } - - function leastCommonAncestor(a, b) { - if (a === b) return a; - var aNodes = a.ancestors(), - bNodes = b.ancestors(), - c = null; - a = aNodes.pop(); - b = bNodes.pop(); - while (a === b) { - c = a; - a = aNodes.pop(); - b = bNodes.pop(); - } - return c; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/ancestors.js - - /* harmony default export */ function ancestors() { - var node = this, - nodes = [node]; - while ((node = node.parent)) { - nodes.push(node); - } - return nodes; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/descendants.js - - /* harmony default export */ function descendants() { - return Array.from(this); - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/leaves.js - - /* harmony default export */ function leaves() { - var leaves = []; - this.eachBefore(function (node) { - if (!node.children) { - leaves.push(node); - } - }); - return leaves; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/links.js - - /* harmony default export */ function links() { - var root = this, - links = []; - root.each(function (node) { - if (node !== root) { - // Don’t include the root’s parent, if any. - links.push({ source: node.parent, target: node }); - } - }); - return links; - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/iterator.js - - /* harmony default export */ function* hierarchy_iterator() { - var node = this, - current, - next = [node], - children, - i, - n; - do { - (current = next.reverse()), (next = []); - while ((node = current.pop())) { - yield node; - if ((children = node.children)) { - for (i = 0, n = children.length; i < n; ++i) { - next.push(children[i]); - } - } - } - } while (next.length); - } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/index.js - - function hierarchy(data, children) { - if (data instanceof Map) { - data = [undefined, data]; - if (children === undefined) children = mapChildren; - } else if (children === undefined) { - children = objectChildren; - } - - var root = new Node(data), - node, - nodes = [root], - child, - childs, - i, - n; - - while ((node = nodes.pop())) { - if ( - (childs = children(node.data)) && - (n = (childs = Array.from(childs)).length) - ) { - node.children = childs; - for (i = n - 1; i >= 0; --i) { - nodes.push((child = childs[i] = new Node(childs[i]))); - child.parent = node; - child.depth = node.depth + 1; - } - } - } - - return root.eachBefore(computeHeight); - } - - function node_copy() { - return hierarchy(this).eachBefore(copyData); - } - - function objectChildren(d) { - return d.children; - } - - function mapChildren(d) { - return Array.isArray(d) ? d[1] : null; - } - - function copyData(node) { - if (node.data.value !== undefined) node.value = node.data.value; - node.data = node.data.data; - } - - function computeHeight(node) { - var height = 0; - do node.height = height; - while ((node = node.parent) && node.height < ++height); - } - - function Node(data) { - this.data = data; - this.depth = this.height = 0; - this.parent = null; - } - - Node.prototype = hierarchy.prototype = { - constructor: Node, - count: hierarchy_count, - each: hierarchy_each, - eachAfter: eachAfter, - eachBefore: eachBefore, - find: hierarchy_find, - sum: sum, - sort: hierarchy_sort, - path: path, - ancestors: ancestors, - descendants: descendants, - leaves: leaves, - links: links, - copy: node_copy, - [Symbol.iterator]: hierarchy_iterator, - }; // CONCATENATED MODULE: ../node_modules/d3-array/src/ticks.js - - var e10 = Math.sqrt(50), - e5 = Math.sqrt(10), - e2 = Math.sqrt(2); - - function ticks(start, stop, count) { - var reverse, - i = -1, - n, - ticks, - step; - - (stop = +stop), (start = +start), (count = +count); - if (start === stop && count > 0) return [start]; - if ((reverse = stop < start)) (n = start), (start = stop), (stop = n); - if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step)) - return []; - - if (step > 0) { - let r0 = Math.round(start / step), - r1 = Math.round(stop / step); - if (r0 * step < start) ++r0; - if (r1 * step > stop) --r1; - ticks = new Array((n = r1 - r0 + 1)); - while (++i < n) ticks[i] = (r0 + i) * step; - } else { - step = -step; - let r0 = Math.round(start * step), - r1 = Math.round(stop * step); - if (r0 / step < start) ++r0; - if (r1 / step > stop) --r1; - ticks = new Array((n = r1 - r0 + 1)); - while (++i < n) ticks[i] = (r0 + i) / step; - } - - if (reverse) ticks.reverse(); - - return ticks; - } - - function tickIncrement(start, stop, count) { - var step = (stop - start) / Math.max(0, count), - power = Math.floor(Math.log(step) / Math.LN10), - error = step / Math.pow(10, power); - return power >= 0 - ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * - Math.pow(10, power) - : -Math.pow(10, -power) / - (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1); - } - - function tickStep(start, stop, count) { - var step0 = Math.abs(stop - start) / Math.max(0, count), - step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), - error = step0 / step1; - if (error >= e10) step1 *= 10; - else if (error >= e5) step1 *= 5; - else if (error >= e2) step1 *= 2; - return stop < start ? -step1 : step1; - } // CONCATENATED MODULE: ../node_modules/d3-array/src/bisector.js - - function bisector(f) { - let delta = f; - let compare1 = f; - let compare2 = f; - - if (f.length !== 2) { - delta = (d, x) => f(d) - x; - compare1 = ascending_ascending; - compare2 = (d, x) => ascending_ascending(f(d), x); - } - - function left(a, x, lo = 0, hi = a.length) { - if (lo < hi) { - if (compare1(x, x) !== 0) return hi; - do { - const mid = (lo + hi) >>> 1; - if (compare2(a[mid], x) < 0) lo = mid + 1; - else hi = mid; - } while (lo < hi); - } - return lo; - } - - function right(a, x, lo = 0, hi = a.length) { - if (lo < hi) { - if (compare1(x, x) !== 0) return hi; - do { - const mid = (lo + hi) >>> 1; - if (compare2(a[mid], x) <= 0) lo = mid + 1; - else hi = mid; - } while (lo < hi); - } - return lo; - } - - function center(a, x, lo = 0, hi = a.length) { - const i = left(a, x, lo, hi - 1); - return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i; - } - - return { left, center, right }; - } // CONCATENATED MODULE: ../node_modules/d3-array/src/number.js - - function number(x) { - return x === null ? NaN : +x; - } - - function* numbers(values, valueof) { - if (valueof === undefined) { - for (let value of values) { - if (value != null && (value = +value) >= value) { - yield value; - } - } - } else { - let index = -1; - for (let value of values) { - if ( - (value = valueof(value, ++index, values)) != null && - (value = +value) >= value - ) { - yield value; - } - } - } - } // CONCATENATED MODULE: ../node_modules/d3-array/src/bisect.js - - const ascendingBisect = bisector(ascending_ascending); - const bisectRight = ascendingBisect.right; - const bisectLeft = ascendingBisect.left; - const bisectCenter = bisector(number).center; - /* harmony default export */ const bisect = bisectRight; // CONCATENATED MODULE: ../node_modules/d3-color/src/define.js - - /* harmony default export */ function src_define( - constructor, - factory, - prototype, - ) { - constructor.prototype = factory.prototype = prototype; - prototype.constructor = constructor; - } - - function extend(parent, definition) { - var prototype = Object.create(parent.prototype); - for (var key in definition) prototype[key] = definition[key]; - return prototype; - } // CONCATENATED MODULE: ../node_modules/d3-color/src/color.js - - function Color() {} - - var darker = 0.7; - var brighter = 1 / darker; - - var reI = '\\s*([+-]?\\d+)\\s*', - reN = '\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*', - reP = '\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*', - reHex = /^#([0-9a-f]{3,8})$/, - reRgbInteger = new RegExp('^rgb\\(' + [reI, reI, reI] + '\\)$'), - reRgbPercent = new RegExp('^rgb\\(' + [reP, reP, reP] + '\\)$'), - reRgbaInteger = new RegExp('^rgba\\(' + [reI, reI, reI, reN] + '\\)$'), - reRgbaPercent = new RegExp('^rgba\\(' + [reP, reP, reP, reN] + '\\)$'), - reHslPercent = new RegExp('^hsl\\(' + [reN, reP, reP] + '\\)$'), - reHslaPercent = new RegExp('^hsla\\(' + [reN, reP, reP, reN] + '\\)$'); - - var named = { - aliceblue: 0xf0f8ff, - antiquewhite: 0xfaebd7, - aqua: 0x00ffff, - aquamarine: 0x7fffd4, - azure: 0xf0ffff, - beige: 0xf5f5dc, - bisque: 0xffe4c4, - black: 0x000000, - blanchedalmond: 0xffebcd, - blue: 0x0000ff, - blueviolet: 0x8a2be2, - brown: 0xa52a2a, - burlywood: 0xdeb887, - cadetblue: 0x5f9ea0, - chartreuse: 0x7fff00, - chocolate: 0xd2691e, - coral: 0xff7f50, - cornflowerblue: 0x6495ed, - cornsilk: 0xfff8dc, - crimson: 0xdc143c, - cyan: 0x00ffff, - darkblue: 0x00008b, - darkcyan: 0x008b8b, - darkgoldenrod: 0xb8860b, - darkgray: 0xa9a9a9, - darkgreen: 0x006400, - darkgrey: 0xa9a9a9, - darkkhaki: 0xbdb76b, - darkmagenta: 0x8b008b, - darkolivegreen: 0x556b2f, - darkorange: 0xff8c00, - darkorchid: 0x9932cc, - darkred: 0x8b0000, - darksalmon: 0xe9967a, - darkseagreen: 0x8fbc8f, - darkslateblue: 0x483d8b, - darkslategray: 0x2f4f4f, - darkslategrey: 0x2f4f4f, - darkturquoise: 0x00ced1, - darkviolet: 0x9400d3, - deeppink: 0xff1493, - deepskyblue: 0x00bfff, - dimgray: 0x696969, - dimgrey: 0x696969, - dodgerblue: 0x1e90ff, - firebrick: 0xb22222, - floralwhite: 0xfffaf0, - forestgreen: 0x228b22, - fuchsia: 0xff00ff, - gainsboro: 0xdcdcdc, - ghostwhite: 0xf8f8ff, - gold: 0xffd700, - goldenrod: 0xdaa520, - gray: 0x808080, - green: 0x008000, - greenyellow: 0xadff2f, - grey: 0x808080, - honeydew: 0xf0fff0, - hotpink: 0xff69b4, - indianred: 0xcd5c5c, - indigo: 0x4b0082, - ivory: 0xfffff0, - khaki: 0xf0e68c, - lavender: 0xe6e6fa, - lavenderblush: 0xfff0f5, - lawngreen: 0x7cfc00, - lemonchiffon: 0xfffacd, - lightblue: 0xadd8e6, - lightcoral: 0xf08080, - lightcyan: 0xe0ffff, - lightgoldenrodyellow: 0xfafad2, - lightgray: 0xd3d3d3, - lightgreen: 0x90ee90, - lightgrey: 0xd3d3d3, - lightpink: 0xffb6c1, - lightsalmon: 0xffa07a, - lightseagreen: 0x20b2aa, - lightskyblue: 0x87cefa, - lightslategray: 0x778899, - lightslategrey: 0x778899, - lightsteelblue: 0xb0c4de, - lightyellow: 0xffffe0, - lime: 0x00ff00, - limegreen: 0x32cd32, - linen: 0xfaf0e6, - magenta: 0xff00ff, - maroon: 0x800000, - mediumaquamarine: 0x66cdaa, - mediumblue: 0x0000cd, - mediumorchid: 0xba55d3, - mediumpurple: 0x9370db, - mediumseagreen: 0x3cb371, - mediumslateblue: 0x7b68ee, - mediumspringgreen: 0x00fa9a, - mediumturquoise: 0x48d1cc, - mediumvioletred: 0xc71585, - midnightblue: 0x191970, - mintcream: 0xf5fffa, - mistyrose: 0xffe4e1, - moccasin: 0xffe4b5, - navajowhite: 0xffdead, - navy: 0x000080, - oldlace: 0xfdf5e6, - olive: 0x808000, - olivedrab: 0x6b8e23, - orange: 0xffa500, - orangered: 0xff4500, - orchid: 0xda70d6, - palegoldenrod: 0xeee8aa, - palegreen: 0x98fb98, - paleturquoise: 0xafeeee, - palevioletred: 0xdb7093, - papayawhip: 0xffefd5, - peachpuff: 0xffdab9, - peru: 0xcd853f, - pink: 0xffc0cb, - plum: 0xdda0dd, - powderblue: 0xb0e0e6, - purple: 0x800080, - rebeccapurple: 0x663399, - red: 0xff0000, - rosybrown: 0xbc8f8f, - royalblue: 0x4169e1, - saddlebrown: 0x8b4513, - salmon: 0xfa8072, - sandybrown: 0xf4a460, - seagreen: 0x2e8b57, - seashell: 0xfff5ee, - sienna: 0xa0522d, - silver: 0xc0c0c0, - skyblue: 0x87ceeb, - slateblue: 0x6a5acd, - slategray: 0x708090, - slategrey: 0x708090, - snow: 0xfffafa, - springgreen: 0x00ff7f, - steelblue: 0x4682b4, - tan: 0xd2b48c, - teal: 0x008080, - thistle: 0xd8bfd8, - tomato: 0xff6347, - turquoise: 0x40e0d0, - violet: 0xee82ee, - wheat: 0xf5deb3, - white: 0xffffff, - whitesmoke: 0xf5f5f5, - yellow: 0xffff00, - yellowgreen: 0x9acd32, - }; - - src_define(Color, color, { - copy: function (channels) { - return Object.assign(new this.constructor(), this, channels); - }, - displayable: function () { - return this.rgb().displayable(); - }, - hex: color_formatHex, // Deprecated! Use color.formatHex. - formatHex: color_formatHex, - formatHsl: color_formatHsl, - formatRgb: color_formatRgb, - toString: color_formatRgb, - }); - - function color_formatHex() { - return this.rgb().formatHex(); - } - - function color_formatHsl() { - return hslConvert(this).formatHsl(); - } - - function color_formatRgb() { - return this.rgb().formatRgb(); - } - - function color(format) { - var m, l; - format = (format + '').trim().toLowerCase(); - return (m = reHex.exec(format)) - ? ((l = m[1].length), - (m = parseInt(m[1], 16)), - l === 6 - ? rgbn(m) // #ff0000 - : l === 3 - ? new Rgb( - ((m >> 8) & 0xf) | ((m >> 4) & 0xf0), - ((m >> 4) & 0xf) | (m & 0xf0), - ((m & 0xf) << 4) | (m & 0xf), - 1, - ) // #f00 - : l === 8 - ? rgba( - (m >> 24) & 0xff, - (m >> 16) & 0xff, - (m >> 8) & 0xff, - (m & 0xff) / 0xff, - ) // #ff000000 - : l === 4 - ? rgba( - ((m >> 12) & 0xf) | ((m >> 8) & 0xf0), - ((m >> 8) & 0xf) | ((m >> 4) & 0xf0), - ((m >> 4) & 0xf) | (m & 0xf0), - (((m & 0xf) << 4) | (m & 0xf)) / 0xff, - ) // #f000 - : null) // invalid hex - : (m = reRgbInteger.exec(format)) - ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) - : (m = reRgbPercent.exec(format)) - ? new Rgb((m[1] * 255) / 100, (m[2] * 255) / 100, (m[3] * 255) / 100, 1) // rgb(100%, 0%, 0%) - : (m = reRgbaInteger.exec(format)) - ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) - : (m = reRgbaPercent.exec(format)) - ? rgba((m[1] * 255) / 100, (m[2] * 255) / 100, (m[3] * 255) / 100, m[4]) // rgb(100%, 0%, 0%, 1) - : (m = reHslPercent.exec(format)) - ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) - : (m = reHslaPercent.exec(format)) - ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) - : named.hasOwnProperty(format) - ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins - : format === 'transparent' - ? new Rgb(NaN, NaN, NaN, 0) - : null; - } - - function rgbn(n) { - return new Rgb((n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff, 1); - } - - function rgba(r, g, b, a) { - if (a <= 0) r = g = b = NaN; - return new Rgb(r, g, b, a); - } - - function rgbConvert(o) { - if (!(o instanceof Color)) o = color(o); - if (!o) return new Rgb(); - o = o.rgb(); - return new Rgb(o.r, o.g, o.b, o.opacity); - } - - function color_rgb(r, g, b, opacity) { - return arguments.length === 1 - ? rgbConvert(r) - : new Rgb(r, g, b, opacity == null ? 1 : opacity); - } - - function Rgb(r, g, b, opacity) { - this.r = +r; - this.g = +g; - this.b = +b; - this.opacity = +opacity; - } - - src_define( - Rgb, - color_rgb, - extend(Color, { - brighter: function (k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - darker: function (k) { - k = k == null ? darker : Math.pow(darker, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - rgb: function () { - return this; - }, - displayable: function () { - return ( - -0.5 <= this.r && - this.r < 255.5 && - -0.5 <= this.g && - this.g < 255.5 && - -0.5 <= this.b && - this.b < 255.5 && - 0 <= this.opacity && - this.opacity <= 1 - ); - }, - hex: rgb_formatHex, // Deprecated! Use color.formatHex. - formatHex: rgb_formatHex, - formatRgb: rgb_formatRgb, - toString: rgb_formatRgb, - }), - ); - - function rgb_formatHex() { - return '#' + hex(this.r) + hex(this.g) + hex(this.b); - } - - function rgb_formatRgb() { - var a = this.opacity; - a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); - return ( - (a === 1 ? 'rgb(' : 'rgba(') + - Math.max(0, Math.min(255, Math.round(this.r) || 0)) + - ', ' + - Math.max(0, Math.min(255, Math.round(this.g) || 0)) + - ', ' + - Math.max(0, Math.min(255, Math.round(this.b) || 0)) + - (a === 1 ? ')' : ', ' + a + ')') - ); - } - - function hex(value) { - value = Math.max(0, Math.min(255, Math.round(value) || 0)); - return (value < 16 ? '0' : '') + value.toString(16); - } - - function hsla(h, s, l, a) { - if (a <= 0) h = s = l = NaN; - else if (l <= 0 || l >= 1) h = s = NaN; - else if (s <= 0) h = NaN; - return new Hsl(h, s, l, a); - } - - function hslConvert(o) { - if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); - if (!(o instanceof Color)) o = color(o); - if (!o) return new Hsl(); - if (o instanceof Hsl) return o; - o = o.rgb(); - var r = o.r / 255, - g = o.g / 255, - b = o.b / 255, - min = Math.min(r, g, b), - max = Math.max(r, g, b), - h = NaN, - s = max - min, - l = (max + min) / 2; - if (s) { - if (r === max) h = (g - b) / s + (g < b) * 6; - else if (g === max) h = (b - r) / s + 2; - else h = (r - g) / s + 4; - s /= l < 0.5 ? max + min : 2 - max - min; - h *= 60; - } else { - s = l > 0 && l < 1 ? 0 : h; - } - return new Hsl(h, s, l, o.opacity); - } - - function hsl(h, s, l, opacity) { - return arguments.length === 1 - ? hslConvert(h) - : new Hsl(h, s, l, opacity == null ? 1 : opacity); - } - - function Hsl(h, s, l, opacity) { - this.h = +h; - this.s = +s; - this.l = +l; - this.opacity = +opacity; - } - - src_define( - Hsl, - hsl, - extend(Color, { - brighter: function (k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - darker: function (k) { - k = k == null ? darker : Math.pow(darker, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - rgb: function () { - var h = (this.h % 360) + (this.h < 0) * 360, - s = isNaN(h) || isNaN(this.s) ? 0 : this.s, - l = this.l, - m2 = l + (l < 0.5 ? l : 1 - l) * s, - m1 = 2 * l - m2; - return new Rgb( - hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), - hsl2rgb(h, m1, m2), - hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), - this.opacity, - ); - }, - displayable: function () { - return ( - ((0 <= this.s && this.s <= 1) || isNaN(this.s)) && - 0 <= this.l && - this.l <= 1 && - 0 <= this.opacity && - this.opacity <= 1 - ); - }, - formatHsl: function () { - var a = this.opacity; - a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); - return ( - (a === 1 ? 'hsl(' : 'hsla(') + - (this.h || 0) + - ', ' + - (this.s || 0) * 100 + - '%, ' + - (this.l || 0) * 100 + - '%' + - (a === 1 ? ')' : ', ' + a + ')') - ); - }, - }), - ); - - /* From FvD 13.37, CSS Color Module Level 3 */ - function hsl2rgb(h, m1, m2) { - return ( - (h < 60 - ? m1 + ((m2 - m1) * h) / 60 - : h < 180 - ? m2 - : h < 240 - ? m1 + ((m2 - m1) * (240 - h)) / 60 - : m1) * 255 - ); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basis.js - - function basis(t1, v0, v1, v2, v3) { - var t2 = t1 * t1, - t3 = t2 * t1; - return ( - ((1 - 3 * t1 + 3 * t2 - t3) * v0 + - (4 - 6 * t2 + 3 * t3) * v1 + - (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 + - t3 * v3) / - 6 - ); - } - - /* harmony default export */ function src_basis(values) { - var n = values.length - 1; - return function (t) { - var i = - t <= 0 ? (t = 0) : t >= 1 ? ((t = 1), n - 1) : Math.floor(t * n), - v1 = values[i], - v2 = values[i + 1], - v0 = i > 0 ? values[i - 1] : 2 * v1 - v2, - v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1; - return basis((t - i / n) * n, v0, v1, v2, v3); - }; - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basisClosed.js - - /* harmony default export */ function basisClosed(values) { - var n = values.length; - return function (t) { - var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n), - v0 = values[(i + n - 1) % n], - v1 = values[i % n], - v2 = values[(i + 1) % n], - v3 = values[(i + 2) % n]; - return basis((t - i / n) * n, v0, v1, v2, v3); - }; - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/constant.js - - /* harmony default export */ const d3_interpolate_src_constant = ( - x, - ) => () => x; // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/color.js - - function linear(a, d) { - return function (t) { - return a + t * d; - }; - } - - function exponential(a, b, y) { - return ( - (a = Math.pow(a, y)), - (b = Math.pow(b, y) - a), - (y = 1 / y), - function (t) { - return Math.pow(a + t * b, y); - } - ); - } - - function hue(a, b) { - var d = b - a; - return d - ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) - : constant(isNaN(a) ? b : a); - } - - function gamma(y) { - return (y = +y) === 1 - ? nogamma - : function (a, b) { - return b - a - ? exponential(a, b, y) - : d3_interpolate_src_constant(isNaN(a) ? b : a); - }; - } - - function nogamma(a, b) { - var d = b - a; - return d ? linear(a, d) : d3_interpolate_src_constant(isNaN(a) ? b : a); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/rgb.js - - /* harmony default export */ const rgb = (function rgbGamma(y) { - var color = gamma(y); - - function rgb(start, end) { - var r = color((start = color_rgb(start)).r, (end = color_rgb(end)).r), - g = color(start.g, end.g), - b = color(start.b, end.b), - opacity = nogamma(start.opacity, end.opacity); - return function (t) { - start.r = r(t); - start.g = g(t); - start.b = b(t); - start.opacity = opacity(t); - return start + ''; - }; - } - - rgb.gamma = rgbGamma; - - return rgb; - })(1); - - function rgbSpline(spline) { - return function (colors) { - var n = colors.length, - r = new Array(n), - g = new Array(n), - b = new Array(n), - i, - color; - for (i = 0; i < n; ++i) { - color = color_rgb(colors[i]); - r[i] = color.r || 0; - g[i] = color.g || 0; - b[i] = color.b || 0; - } - r = spline(r); - g = spline(g); - b = spline(b); - color.opacity = 1; - return function (t) { - color.r = r(t); - color.g = g(t); - color.b = b(t); - return color + ''; - }; - }; - } - - var rgbBasis = rgbSpline(src_basis); - var rgbBasisClosed = rgbSpline(basisClosed); // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/array.js - - /* harmony default export */ function src_array(a, b) { - return (isNumberArray(b) ? numberArray : genericArray)(a, b); - } - - function genericArray(a, b) { - var nb = b ? b.length : 0, - na = a ? Math.min(nb, a.length) : 0, - x = new Array(na), - c = new Array(nb), - i; - - for (i = 0; i < na; ++i) x[i] = value(a[i], b[i]); - for (; i < nb; ++i) c[i] = b[i]; - - return function (t) { - for (i = 0; i < na; ++i) c[i] = x[i](t); - return c; - }; - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/date.js - - /* harmony default export */ function date(a, b) { - var d = new Date(); - return ( - (a = +a), - (b = +b), - function (t) { - return d.setTime(a * (1 - t) + b * t), d; - } - ); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/number.js - - /* harmony default export */ function src_number(a, b) { - return ( - (a = +a), - (b = +b), - function (t) { - return a * (1 - t) + b * t; - } - ); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/object.js - - /* harmony default export */ function object(a, b) { - var i = {}, - c = {}, - k; - - if (a === null || typeof a !== 'object') a = {}; - if (b === null || typeof b !== 'object') b = {}; - - for (k in b) { - if (k in a) { - i[k] = value(a[k], b[k]); - } else { - c[k] = b[k]; - } - } - - return function (t) { - for (k in i) c[k] = i[k](t); - return c; - }; - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/string.js - - var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, - reB = new RegExp(reA.source, 'g'); - - function zero(b) { - return function () { - return b; - }; - } - - function one(b) { - return function (t) { - return b(t) + ''; - }; - } - - /* harmony default export */ function string(a, b) { - var bi = (reA.lastIndex = reB.lastIndex = 0), // scan index for next number in b - am, // current match in a - bm, // current match in b - bs, // string preceding current number in b, if any - i = -1, // index in s - s = [], // string constants and placeholders - q = []; // number interpolators - - // Coerce inputs to strings. - (a = a + ''), (b = b + ''); - - // Interpolate pairs of numbers in a & b. - while ((am = reA.exec(a)) && (bm = reB.exec(b))) { - if ((bs = bm.index) > bi) { - // a string precedes the next number in b - bs = b.slice(bi, bs); - if (s[i]) s[i] += bs; - // coalesce with previous string - else s[++i] = bs; - } - if ((am = am[0]) === (bm = bm[0])) { - // numbers in a & b match - if (s[i]) s[i] += bm; - // coalesce with previous string - else s[++i] = bm; - } else { - // interpolate non-matching numbers - s[++i] = null; - q.push({ i: i, x: src_number(am, bm) }); - } - bi = reB.lastIndex; - } - - // Add remains of b. - if (bi < b.length) { - bs = b.slice(bi); - if (s[i]) s[i] += bs; - // coalesce with previous string - else s[++i] = bs; - } - - // Special optimization for only a single match. - // Otherwise, interpolate each of the numbers and rejoin the string. - return s.length < 2 - ? q[0] - ? one(q[0].x) - : zero(b) - : ((b = q.length), - function (t) { - for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(''); - }); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/numberArray.js - - /* harmony default export */ function src_numberArray(a, b) { - if (!b) b = []; - var n = a ? Math.min(b.length, a.length) : 0, - c = b.slice(), - i; - return function (t) { - for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t; - return c; - }; - } - - function numberArray_isNumberArray(x) { - return ArrayBuffer.isView(x) && !(x instanceof DataView); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/value.js - - /* harmony default export */ function value(a, b) { - var t = typeof b, - c; - return b == null || t === 'boolean' - ? d3_interpolate_src_constant(b) - : (t === 'number' - ? src_number - : t === 'string' - ? (c = color(b)) - ? ((b = c), rgb) - : string - : b instanceof color - ? rgb - : b instanceof Date - ? date - : numberArray_isNumberArray(b) - ? src_numberArray - : Array.isArray(b) - ? genericArray - : (typeof b.valueOf !== 'function' && - typeof b.toString !== 'function') || - isNaN(b) - ? object - : src_number)(a, b); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/round.js - - /* harmony default export */ function round(a, b) { - return ( - (a = +a), - (b = +b), - function (t) { - return Math.round(a * (1 - t) + b * t); - } - ); - } // CONCATENATED MODULE: ../node_modules/d3-scale/src/constant.js - - function constants(x) { - return function () { - return x; - }; - } // CONCATENATED MODULE: ../node_modules/d3-scale/src/number.js - - function number_number(x) { - return +x; - } // CONCATENATED MODULE: ../node_modules/d3-scale/src/continuous.js - - var unit = [0, 1]; - - function continuous_identity(x) { - return x; - } - - function normalize(a, b) { - return (b -= a = +a) - ? function (x) { - return (x - a) / b; - } - : constants(isNaN(b) ? NaN : 0.5); - } - - function clamper(a, b) { - var t; - if (a > b) (t = a), (a = b), (b = t); - return function (x) { - return Math.max(a, Math.min(b, x)); - }; - } - - // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. - // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. - function bimap(domain, range, interpolate) { - var d0 = domain[0], - d1 = domain[1], - r0 = range[0], - r1 = range[1]; - if (d1 < d0) (d0 = normalize(d1, d0)), (r0 = interpolate(r1, r0)); - else (d0 = normalize(d0, d1)), (r0 = interpolate(r0, r1)); - return function (x) { - return r0(d0(x)); - }; - } - - function polymap(domain, range, interpolate) { - var j = Math.min(domain.length, range.length) - 1, - d = new Array(j), - r = new Array(j), - i = -1; - - // Reverse descending domains. - if (domain[j] < domain[0]) { - domain = domain.slice().reverse(); - range = range.slice().reverse(); - } - - while (++i < j) { - d[i] = normalize(domain[i], domain[i + 1]); - r[i] = interpolate(range[i], range[i + 1]); - } - - return function (x) { - var i = bisect(domain, x, 1, j) - 1; - return r[i](d[i](x)); - }; - } - - function copy(source, target) { - return target - .domain(source.domain()) - .range(source.range()) - .interpolate(source.interpolate()) - .clamp(source.clamp()) - .unknown(source.unknown()); - } - - function transformer() { - var domain = unit, - range = unit, - interpolate = value, - transform, - untransform, - unknown, - clamp = continuous_identity, - piecewise, - output, - input; - - function rescale() { - var n = Math.min(domain.length, range.length); - if (clamp !== continuous_identity) - clamp = clamper(domain[0], domain[n - 1]); - piecewise = n > 2 ? polymap : bimap; - output = input = null; - return scale; - } - - function scale(x) { - return x == null || isNaN((x = +x)) - ? unknown - : ( - output || - (output = piecewise(domain.map(transform), range, interpolate)) - )(transform(clamp(x))); - } - - scale.invert = function (y) { - return clamp( - untransform( - ( - input || - (input = piecewise(range, domain.map(transform), src_number)) - )(y), - ), - ); - }; - - scale.domain = function (_) { - return arguments.length - ? ((domain = Array.from(_, number_number)), rescale()) - : domain.slice(); - }; - - scale.range = function (_) { - return arguments.length - ? ((range = Array.from(_)), rescale()) - : range.slice(); - }; - - scale.rangeRound = function (_) { - return (range = Array.from(_)), (interpolate = round), rescale(); - }; - - scale.clamp = function (_) { - return arguments.length - ? ((clamp = _ ? true : continuous_identity), rescale()) - : clamp !== continuous_identity; - }; - - scale.interpolate = function (_) { - return arguments.length ? ((interpolate = _), rescale()) : interpolate; - }; - - scale.unknown = function (_) { - return arguments.length ? ((unknown = _), scale) : unknown; - }; - - return function (t, u) { - (transform = t), (untransform = u); - return rescale(); - }; - } - - function continuous() { - return transformer()(continuous_identity, continuous_identity); - } // CONCATENATED MODULE: ../node_modules/d3-scale/src/init.js - - function initRange(domain, range) { - switch (arguments.length) { - case 0: - break; - case 1: - this.range(domain); - break; - default: - this.range(range).domain(domain); - break; - } - return this; - } - - function initInterpolator(domain, interpolator) { - switch (arguments.length) { - case 0: - break; - case 1: { - if (typeof domain === 'function') this.interpolator(domain); - else this.range(domain); - break; - } - default: { - this.domain(domain); - if (typeof interpolator === 'function') - this.interpolator(interpolator); - else this.range(interpolator); - break; - } - } - return this; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionPrefix.js - - /* harmony default export */ function precisionPrefix(step, value) { - return Math.max( - 0, - Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - - exponent(Math.abs(step)), - ); - } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionRound.js - - /* harmony default export */ function precisionRound(step, max) { - (step = Math.abs(step)), (max = Math.abs(max) - step); - return Math.max(0, exponent(max) - exponent(step)) + 1; - } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionFixed.js - - /* harmony default export */ function precisionFixed(step) { - return Math.max(0, -exponent(Math.abs(step))); - } // CONCATENATED MODULE: ../node_modules/d3-scale/src/tickFormat.js - - function tickFormat(start, stop, count, specifier) { - var step = tickStep(start, stop, count), - precision; - specifier = formatSpecifier(specifier == null ? ',f' : specifier); - switch (specifier.type) { - case 's': { - var value = Math.max(Math.abs(start), Math.abs(stop)); - if ( - specifier.precision == null && - !isNaN((precision = precisionPrefix(step, value))) - ) - specifier.precision = precision; - return formatPrefix(specifier, value); - } - case '': - case 'e': - case 'g': - case 'p': - case 'r': { - if ( - specifier.precision == null && - !isNaN( - (precision = precisionRound( - step, - Math.max(Math.abs(start), Math.abs(stop)), - )), - ) - ) - specifier.precision = precision - (specifier.type === 'e'); - break; - } - case 'f': - case '%': { - if ( - specifier.precision == null && - !isNaN((precision = precisionFixed(step))) - ) - specifier.precision = precision - (specifier.type === '%') * 2; - break; - } - } - return format(specifier); - } // CONCATENATED MODULE: ../node_modules/d3-scale/src/linear.js - - function linearish(scale) { - var domain = scale.domain; - - scale.ticks = function (count) { - var d = domain(); - return ticks(d[0], d[d.length - 1], count == null ? 10 : count); - }; - - scale.tickFormat = function (count, specifier) { - var d = domain(); - return tickFormat( - d[0], - d[d.length - 1], - count == null ? 10 : count, - specifier, - ); - }; - - scale.nice = function (count) { - if (count == null) count = 10; - - var d = domain(); - var i0 = 0; - var i1 = d.length - 1; - var start = d[i0]; - var stop = d[i1]; - var prestep; - var step; - var maxIter = 10; - - if (stop < start) { - (step = start), (start = stop), (stop = step); - (step = i0), (i0 = i1), (i1 = step); - } - - while (maxIter-- > 0) { - step = tickIncrement(start, stop, count); - if (step === prestep) { - d[i0] = start; - d[i1] = stop; - return domain(d); - } else if (step > 0) { - start = Math.floor(start / step) * step; - stop = Math.ceil(stop / step) * step; - } else if (step < 0) { - start = Math.ceil(start * step) / step; - stop = Math.floor(stop * step) / step; - } else { - break; - } - prestep = step; - } - - return scale; - }; - - return scale; - } - - function linear_linear() { - var scale = continuous(); - - scale.copy = function () { - return copy(scale, linear_linear()); - }; - - initRange.apply(scale, arguments); - - return linearish(scale); - } // CONCATENATED MODULE: ../node_modules/d3-ease/src/cubic.js - - function cubicIn(t) { - return t * t * t; - } - - function cubicOut(t) { - return --t * t * t + 1; - } - - function cubicInOut(t) { - return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; - } // CONCATENATED MODULE: ../node_modules/d3-dispatch/src/dispatch.js - - var noop = { value: () => {} }; - - function dispatch_dispatch() { - for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { - if (!(t = arguments[i] + '') || t in _ || /[\s.]/.test(t)) - throw new Error('illegal type: ' + t); - _[t] = []; - } - return new Dispatch(_); - } - - function Dispatch(_) { - this._ = _; - } - - function dispatch_parseTypenames(typenames, types) { - return typenames - .trim() - .split(/^|\s+/) - .map(function (t) { - var name = '', - i = t.indexOf('.'); - if (i >= 0) (name = t.slice(i + 1)), (t = t.slice(0, i)); - if (t && !types.hasOwnProperty(t)) - throw new Error('unknown type: ' + t); - return { type: t, name: name }; - }); - } - - Dispatch.prototype = dispatch_dispatch.prototype = { - constructor: Dispatch, - on: function (typename, callback) { - var _ = this._, - T = dispatch_parseTypenames(typename + '', _), - t, - i = -1, - n = T.length; - - // If no callback was specified, return the callback of the given type and name. - if (arguments.length < 2) { - while (++i < n) - if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) - return t; - return; - } - - // If a type was specified, set the callback for the given type and name. - // Otherwise, if a null callback was specified, remove callbacks of the given name. - if (callback != null && typeof callback !== 'function') - throw new Error('invalid callback: ' + callback); - while (++i < n) { - if ((t = (typename = T[i]).type)) - _[t] = set(_[t], typename.name, callback); - else if (callback == null) - for (t in _) _[t] = set(_[t], typename.name, null); - } - - return this; - }, - copy: function () { - var copy = {}, - _ = this._; - for (var t in _) copy[t] = _[t].slice(); - return new Dispatch(copy); - }, - call: function (type, that) { - if ((n = arguments.length - 2) > 0) - for (var args = new Array(n), i = 0, n, t; i < n; ++i) - args[i] = arguments[i + 2]; - if (!this._.hasOwnProperty(type)) - throw new Error('unknown type: ' + type); - for (t = this._[type], i = 0, n = t.length; i < n; ++i) - t[i].value.apply(that, args); - }, - apply: function (type, that, args) { - if (!this._.hasOwnProperty(type)) - throw new Error('unknown type: ' + type); - for (var t = this._[type], i = 0, n = t.length; i < n; ++i) - t[i].value.apply(that, args); - }, - }; - - function get(type, name) { - for (var i = 0, n = type.length, c; i < n; ++i) { - if ((c = type[i]).name === name) { - return c.value; - } - } - } - - function set(type, name, callback) { - for (var i = 0, n = type.length; i < n; ++i) { - if (type[i].name === name) { - (type[i] = noop), (type = type.slice(0, i).concat(type.slice(i + 1))); - break; - } - } - if (callback != null) type.push({ name: name, value: callback }); - return type; - } - - /* harmony default export */ const src_dispatch = dispatch_dispatch; // CONCATENATED MODULE: ../node_modules/d3-timer/src/timer.js - - var timer_frame = 0, // is an animation frame pending? - timeout = 0, // is a timeout pending? - interval = 0, // are any timers active? - pokeDelay = 1000, // how frequently we check for clock skew - taskHead, - taskTail, - clockLast = 0, - clockNow = 0, - clockSkew = 0, - clock = - typeof performance === 'object' && performance.now ? performance : Date, - setFrame = - typeof window === 'object' && window.requestAnimationFrame - ? window.requestAnimationFrame.bind(window) - : function (f) { - setTimeout(f, 17); - }; - - function now() { - return ( - clockNow || (setFrame(clearNow), (clockNow = clock.now() + clockSkew)) - ); - } - - function clearNow() { - clockNow = 0; - } - - function Timer() { - this._call = this._time = this._next = null; - } - - Timer.prototype = timer.prototype = { - constructor: Timer, - restart: function (callback, delay, time) { - if (typeof callback !== 'function') - throw new TypeError('callback is not a function'); - time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); - if (!this._next && taskTail !== this) { - if (taskTail) taskTail._next = this; - else taskHead = this; - taskTail = this; - } - this._call = callback; - this._time = time; - sleep(); - }, - stop: function () { - if (this._call) { - this._call = null; - this._time = Infinity; - sleep(); - } - }, - }; - - function timer(callback, delay, time) { - var t = new Timer(); - t.restart(callback, delay, time); - return t; - } - - function timerFlush() { - now(); // Get the current time, if not already set. - ++timer_frame; // Pretend we’ve set an alarm, if we haven’t already. - var t = taskHead, - e; - while (t) { - if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e); - t = t._next; - } - --timer_frame; - } - - function wake() { - clockNow = (clockLast = clock.now()) + clockSkew; - timer_frame = timeout = 0; - try { - timerFlush(); - } finally { - timer_frame = 0; - nap(); - clockNow = 0; - } - } - - function poke() { - var now = clock.now(), - delay = now - clockLast; - if (delay > pokeDelay) (clockSkew -= delay), (clockLast = now); - } - - function nap() { - var t0, - t1 = taskHead, - t2, - time = Infinity; - while (t1) { - if (t1._call) { - if (time > t1._time) time = t1._time; - (t0 = t1), (t1 = t1._next); - } else { - (t2 = t1._next), (t1._next = null); - t1 = t0 ? (t0._next = t2) : (taskHead = t2); - } - } - taskTail = t0; - sleep(time); - } - - function sleep(time) { - if (timer_frame) return; // Soonest alarm already set, or will be. - if (timeout) timeout = clearTimeout(timeout); - var delay = time - clockNow; // Strictly less than if we recomputed clockNow. - if (delay > 24) { - if (time < Infinity) - timeout = setTimeout(wake, time - clock.now() - clockSkew); - if (interval) interval = clearInterval(interval); - } else { - if (!interval) - (clockLast = clock.now()), (interval = setInterval(poke, pokeDelay)); - (timer_frame = 1), setFrame(wake); - } - } // CONCATENATED MODULE: ../node_modules/d3-timer/src/timeout.js - - /* harmony default export */ function src_timeout(callback, delay, time) { - var t = new Timer(); - delay = delay == null ? 0 : +delay; - t.restart( - (elapsed) => { - t.stop(); - callback(elapsed + delay); - }, - delay, - time, - ); - return t; - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/schedule.js - - var emptyOn = src_dispatch('start', 'end', 'cancel', 'interrupt'); - var emptyTween = []; - - var CREATED = 0; - var SCHEDULED = 1; - var STARTING = 2; - var STARTED = 3; - var RUNNING = 4; - var ENDING = 5; - var ENDED = 6; - - /* harmony default export */ function schedule( - node, - name, - id, - index, - group, - timing, - ) { - var schedules = node.__transition; - if (!schedules) node.__transition = {}; - else if (id in schedules) return; - create(node, id, { - name: name, - index: index, // For context during callback. - group: group, // For context during callback. - on: emptyOn, - tween: emptyTween, - time: timing.time, - delay: timing.delay, - duration: timing.duration, - ease: timing.ease, - timer: null, - state: CREATED, - }); - } - - function init(node, id) { - var schedule = schedule_get(node, id); - if (schedule.state > CREATED) - throw new Error('too late; already scheduled'); - return schedule; - } - - function schedule_set(node, id) { - var schedule = schedule_get(node, id); - if (schedule.state > STARTED) - throw new Error('too late; already running'); - return schedule; - } - - function schedule_get(node, id) { - var schedule = node.__transition; - if (!schedule || !(schedule = schedule[id])) - throw new Error('transition not found'); - return schedule; - } - - function create(node, id, self) { - var schedules = node.__transition, - tween; - - // Initialize the self timer when the transition is created. - // Note the actual delay is not known until the first callback! - schedules[id] = self; - self.timer = timer(schedule, 0, self.time); - - function schedule(elapsed) { - self.state = SCHEDULED; - self.timer.restart(start, self.delay, self.time); - - // If the elapsed delay is less than our first sleep, start immediately. - if (self.delay <= elapsed) start(elapsed - self.delay); - } - - function start(elapsed) { - var i, j, n, o; - - // If the state is not SCHEDULED, then we previously errored on start. - if (self.state !== SCHEDULED) return stop(); - - for (i in schedules) { - o = schedules[i]; - if (o.name !== self.name) continue; - - // While this element already has a starting transition during this frame, - // defer starting an interrupting transition until that transition has a - // chance to tick (and possibly end); see d3/d3-transition#54! - if (o.state === STARTED) return src_timeout(start); - - // Interrupt the active transition, if any. - if (o.state === RUNNING) { - o.state = ENDED; - o.timer.stop(); - o.on.call('interrupt', node, node.__data__, o.index, o.group); - delete schedules[i]; - } - - // Cancel any pre-empted transitions. - else if (+i < id) { - o.state = ENDED; - o.timer.stop(); - o.on.call('cancel', node, node.__data__, o.index, o.group); - delete schedules[i]; - } - } - - // Defer the first tick to end of the current frame; see d3/d3#1576. - // Note the transition may be canceled after start and before the first tick! - // Note this must be scheduled before the start event; see d3/d3-transition#16! - // Assuming this is successful, subsequent callbacks go straight to tick. - src_timeout(function () { - if (self.state === STARTED) { - self.state = RUNNING; - self.timer.restart(tick, self.delay, self.time); - tick(elapsed); - } - }); - - // Dispatch the start event. - // Note this must be done before the tween are initialized. - self.state = STARTING; - self.on.call('start', node, node.__data__, self.index, self.group); - if (self.state !== STARTING) return; // interrupted - self.state = STARTED; - - // Initialize the tween, deleting null tween. - tween = new Array((n = self.tween.length)); - for (i = 0, j = -1; i < n; ++i) { - if ( - (o = self.tween[i].value.call( - node, - node.__data__, - self.index, - self.group, - )) - ) { - tween[++j] = o; - } - } - tween.length = j + 1; - } - - function tick(elapsed) { - var t = - elapsed < self.duration - ? self.ease.call(null, elapsed / self.duration) - : (self.timer.restart(stop), (self.state = ENDING), 1), - i = -1, - n = tween.length; - - while (++i < n) { - tween[i].call(node, t); - } - - // Dispatch the end event. - if (self.state === ENDING) { - self.on.call('end', node, node.__data__, self.index, self.group); - stop(); - } - } - - function stop() { - self.state = ENDED; - self.timer.stop(); - delete schedules[id]; - for (var i in schedules) return; // eslint-disable-line no-unused-vars - delete node.__transition; - } - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/interrupt.js - - /* harmony default export */ function interrupt(node, name) { - var schedules = node.__transition, - schedule, - active, - empty = true, - i; - - if (!schedules) return; - - name = name == null ? null : name + ''; - - for (i in schedules) { - if ((schedule = schedules[i]).name !== name) { - empty = false; - continue; - } - active = schedule.state > STARTING && schedule.state < ENDING; - schedule.state = ENDED; - schedule.timer.stop(); - schedule.on.call( - active ? 'interrupt' : 'cancel', - node, - node.__data__, - schedule.index, - schedule.group, - ); - delete schedules[i]; - } - - if (empty) delete node.__transition; - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/interrupt.js - - /* harmony default export */ function selection_interrupt(name) { - return this.each(function () { - interrupt(this, name); - }); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/decompose.js - - var degrees = 180 / Math.PI; - - var decompose_identity = { - translateX: 0, - translateY: 0, - rotate: 0, - skewX: 0, - scaleX: 1, - scaleY: 1, - }; - - /* harmony default export */ function decompose(a, b, c, d, e, f) { - var scaleX, scaleY, skewX; - if ((scaleX = Math.sqrt(a * a + b * b))) (a /= scaleX), (b /= scaleX); - if ((skewX = a * c + b * d)) (c -= a * skewX), (d -= b * skewX); - if ((scaleY = Math.sqrt(c * c + d * d))) - (c /= scaleY), (d /= scaleY), (skewX /= scaleY); - if (a * d < b * c) - (a = -a), (b = -b), (skewX = -skewX), (scaleX = -scaleX); - return { - translateX: e, - translateY: f, - rotate: Math.atan2(b, a) * degrees, - skewX: Math.atan(skewX) * degrees, - scaleX: scaleX, - scaleY: scaleY, - }; - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/parse.js - - var svgNode; - - /* eslint-disable no-undef */ - function parseCss(value) { - const m = new (typeof DOMMatrix === 'function' - ? DOMMatrix - : WebKitCSSMatrix)(value + ''); - return m.isIdentity - ? decompose_identity - : decompose(m.a, m.b, m.c, m.d, m.e, m.f); - } - - function parseSvg(value) { - if (value == null) return decompose_identity; - if (!svgNode) - svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - svgNode.setAttribute('transform', value); - if (!(value = svgNode.transform.baseVal.consolidate())) - return decompose_identity; - value = value.matrix; - return decompose(value.a, value.b, value.c, value.d, value.e, value.f); - } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/index.js - - function interpolateTransform(parse, pxComma, pxParen, degParen) { - function pop(s) { - return s.length ? s.pop() + ' ' : ''; - } - - function translate(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push('translate(', null, pxComma, null, pxParen); - q.push( - { i: i - 4, x: src_number(xa, xb) }, - { i: i - 2, x: src_number(ya, yb) }, - ); - } else if (xb || yb) { - s.push('translate(' + xb + pxComma + yb + pxParen); - } - } - - function rotate(a, b, s, q) { - if (a !== b) { - if (a - b > 180) b += 360; - else if (b - a > 180) a += 360; // shortest path - q.push({ - i: s.push(pop(s) + 'rotate(', null, degParen) - 2, - x: src_number(a, b), - }); - } else if (b) { - s.push(pop(s) + 'rotate(' + b + degParen); - } - } - - function skewX(a, b, s, q) { - if (a !== b) { - q.push({ - i: s.push(pop(s) + 'skewX(', null, degParen) - 2, - x: src_number(a, b), - }); - } else if (b) { - s.push(pop(s) + 'skewX(' + b + degParen); - } - } - - function scale(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push(pop(s) + 'scale(', null, ',', null, ')'); - q.push( - { i: i - 4, x: src_number(xa, xb) }, - { i: i - 2, x: src_number(ya, yb) }, - ); - } else if (xb !== 1 || yb !== 1) { - s.push(pop(s) + 'scale(' + xb + ',' + yb + ')'); - } - } - - return function (a, b) { - var s = [], // string constants and placeholders - q = []; // number interpolators - (a = parse(a)), (b = parse(b)); - translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); - rotate(a.rotate, b.rotate, s, q); - skewX(a.skewX, b.skewX, s, q); - scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); - a = b = null; // gc - return function (t) { - var i = -1, - n = q.length, - o; - while (++i < n) s[(o = q[i]).i] = o.x(t); - return s.join(''); - }; - }; - } - - var interpolateTransformCss = interpolateTransform( - parseCss, - 'px, ', - 'px)', - 'deg)', - ); - var interpolateTransformSvg = interpolateTransform( - parseSvg, - ', ', - ')', - ')', - ); // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/tween.js - - function tweenRemove(id, name) { - var tween0, tween1; - return function () { - var schedule = schedule_set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = tween0 = tween; - for (var i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1 = tween1.slice(); - tween1.splice(i, 1); - break; - } - } - } - - schedule.tween = tween1; - }; - } - - function tweenFunction(id, name, value) { - var tween0, tween1; - if (typeof value !== 'function') throw new Error(); - return function () { - var schedule = schedule_set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = (tween0 = tween).slice(); - for ( - var t = { name: name, value: value }, i = 0, n = tween1.length; - i < n; - ++i - ) { - if (tween1[i].name === name) { - tween1[i] = t; - break; - } - } - if (i === n) tween1.push(t); - } - - schedule.tween = tween1; - }; - } - - /* harmony default export */ function tween(name, value) { - var id = this._id; - - name += ''; - - if (arguments.length < 2) { - var tween = schedule_get(this.node(), id).tween; - for (var i = 0, n = tween.length, t; i < n; ++i) { - if ((t = tween[i]).name === name) { - return t.value; - } - } - return null; - } - - return this.each( - (value == null ? tweenRemove : tweenFunction)(id, name, value), - ); - } - - function tweenValue(transition, name, value) { - var id = transition._id; - - transition.each(function () { - var schedule = schedule_set(this, id); - (schedule.value || (schedule.value = {}))[name] = value.apply( - this, - arguments, - ); - }); - - return function (node) { - return schedule_get(node, id).value[name]; - }; - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/interpolate.js - - /* harmony default export */ function interpolate(a, b) { - var c; - return (typeof b === 'number' - ? src_number - : b instanceof color - ? rgb - : (c = color(b)) - ? ((b = c), rgb) - : string)(a, b); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attr.js - - function attr_attrRemove(name) { - return function () { - this.removeAttribute(name); - }; - } - - function attr_attrRemoveNS(fullname) { - return function () { - this.removeAttributeNS(fullname.space, fullname.local); - }; - } - - function attr_attrConstant(name, interpolate, value1) { - var string00, - string1 = value1 + '', - interpolate0; - return function () { - var string0 = this.getAttribute(name); - return string0 === string1 - ? null - : string0 === string00 - ? interpolate0 - : (interpolate0 = interpolate((string00 = string0), value1)); - }; - } - - function attr_attrConstantNS(fullname, interpolate, value1) { - var string00, - string1 = value1 + '', - interpolate0; - return function () { - var string0 = this.getAttributeNS(fullname.space, fullname.local); - return string0 === string1 - ? null - : string0 === string00 - ? interpolate0 - : (interpolate0 = interpolate((string00 = string0), value1)); - }; - } - - function attr_attrFunction(name, interpolate, value) { - var string00, string10, interpolate0; - return function () { - var string0, - value1 = value(this), - string1; - if (value1 == null) return void this.removeAttribute(name); - string0 = this.getAttribute(name); - string1 = value1 + ''; - return string0 === string1 - ? null - : string0 === string00 && string1 === string10 - ? interpolate0 - : ((string10 = string1), - (interpolate0 = interpolate((string00 = string0), value1))); - }; - } - - function attr_attrFunctionNS(fullname, interpolate, value) { - var string00, string10, interpolate0; - return function () { - var string0, - value1 = value(this), - string1; - if (value1 == null) - return void this.removeAttributeNS(fullname.space, fullname.local); - string0 = this.getAttributeNS(fullname.space, fullname.local); - string1 = value1 + ''; - return string0 === string1 - ? null - : string0 === string00 && string1 === string10 - ? interpolate0 - : ((string10 = string1), - (interpolate0 = interpolate((string00 = string0), value1))); - }; - } - - /* harmony default export */ function transition_attr(name, value) { - var fullname = namespace(name), - i = fullname === 'transform' ? interpolateTransformSvg : interpolate; - return this.attrTween( - name, - typeof value === 'function' - ? (fullname.local ? attr_attrFunctionNS : attr_attrFunction)( - fullname, - i, - tweenValue(this, 'attr.' + name, value), - ) - : value == null - ? (fullname.local ? attr_attrRemoveNS : attr_attrRemove)(fullname) - : (fullname.local ? attr_attrConstantNS : attr_attrConstant)( - fullname, - i, - value, - ), - ); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attrTween.js - - function attrInterpolate(name, i) { - return function (t) { - this.setAttribute(name, i.call(this, t)); - }; - } - - function attrInterpolateNS(fullname, i) { - return function (t) { - this.setAttributeNS(fullname.space, fullname.local, i.call(this, t)); - }; - } - - function attrTweenNS(fullname, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i); - return t0; - } - tween._value = value; - return tween; - } - - function attrTween(name, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i); - return t0; - } - tween._value = value; - return tween; - } - - /* harmony default export */ function transition_attrTween(name, value) { - var key = 'attr.' + name; - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== 'function') throw new Error(); - var fullname = namespace(name); - return this.tween( - key, - (fullname.local ? attrTweenNS : attrTween)(fullname, value), - ); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/delay.js - - function delayFunction(id, value) { - return function () { - init(this, id).delay = +value.apply(this, arguments); - }; - } - - function delayConstant(id, value) { - return ( - (value = +value), - function () { - init(this, id).delay = value; - } - ); - } - - /* harmony default export */ function delay(value) { - var id = this._id; - - return arguments.length - ? this.each( - (typeof value === 'function' ? delayFunction : delayConstant)( - id, - value, - ), - ) - : schedule_get(this.node(), id).delay; - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/duration.js - - function durationFunction(id, value) { - return function () { - schedule_set(this, id).duration = +value.apply(this, arguments); - }; - } - - function durationConstant(id, value) { - return ( - (value = +value), - function () { - schedule_set(this, id).duration = value; - } - ); - } - - /* harmony default export */ function duration(value) { - var id = this._id; - - return arguments.length - ? this.each( - (typeof value === 'function' ? durationFunction : durationConstant)( - id, - value, - ), - ) - : schedule_get(this.node(), id).duration; - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/ease.js - - function easeConstant(id, value) { - if (typeof value !== 'function') throw new Error(); - return function () { - schedule_set(this, id).ease = value; - }; - } - - /* harmony default export */ function ease(value) { - var id = this._id; - - return arguments.length - ? this.each(easeConstant(id, value)) - : schedule_get(this.node(), id).ease; - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/easeVarying.js - - function easeVarying(id, value) { - return function () { - var v = value.apply(this, arguments); - if (typeof v !== 'function') throw new Error(); - schedule_set(this, id).ease = v; - }; - } - - /* harmony default export */ function transition_easeVarying(value) { - if (typeof value !== 'function') throw new Error(); - return this.each(easeVarying(this._id, value)); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/filter.js - - /* harmony default export */ function transition_filter(match) { - if (typeof match !== 'function') match = matcher(match); - - for ( - var groups = this._groups, - m = groups.length, - subgroups = new Array(m), - j = 0; - j < m; - ++j - ) { - for ( - var group = groups[j], - n = group.length, - subgroup = (subgroups[j] = []), - node, - i = 0; - i < n; - ++i - ) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Transition(subgroups, this._parents, this._name, this._id); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/merge.js - - /* harmony default export */ function transition_merge(transition) { - if (transition._id !== this._id) throw new Error(); - - for ( - var groups0 = this._groups, - groups1 = transition._groups, - m0 = groups0.length, - m1 = groups1.length, - m = Math.min(m0, m1), - merges = new Array(m0), - j = 0; - j < m; - ++j - ) { - for ( - var group0 = groups0[j], - group1 = groups1[j], - n = group0.length, - merge = (merges[j] = new Array(n)), - node, - i = 0; - i < n; - ++i - ) { - if ((node = group0[i] || group1[i])) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Transition(merges, this._parents, this._name, this._id); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/on.js - - function start(name) { - return (name + '') - .trim() - .split(/^|\s+/) - .every(function (t) { - var i = t.indexOf('.'); - if (i >= 0) t = t.slice(0, i); - return !t || t === 'start'; - }); - } - - function onFunction(id, name, listener) { - var on0, - on1, - sit = start(name) ? init : schedule_set; - return function () { - var schedule = sit(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener); - - schedule.on = on1; - }; - } - - /* harmony default export */ function transition_on(name, listener) { - var id = this._id; - - return arguments.length < 2 - ? schedule_get(this.node(), id).on.on(name) - : this.each(onFunction(id, name, listener)); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/remove.js - - function removeFunction(id) { - return function () { - var parent = this.parentNode; - for (var i in this.__transition) if (+i !== id) return; - if (parent) parent.removeChild(this); - }; - } - - /* harmony default export */ function transition_remove() { - return this.on('end.remove', removeFunction(this._id)); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/select.js - - /* harmony default export */ function transition_select(select) { - var name = this._name, - id = this._id; - - if (typeof select !== 'function') select = selector(select); - - for ( - var groups = this._groups, - m = groups.length, - subgroups = new Array(m), - j = 0; - j < m; - ++j - ) { - for ( - var group = groups[j], - n = group.length, - subgroup = (subgroups[j] = new Array(n)), - node, - subnode, - i = 0; - i < n; - ++i - ) { - if ( - (node = group[i]) && - (subnode = select.call(node, node.__data__, i, group)) - ) { - if ('__data__' in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - schedule( - subgroup[i], - name, - id, - i, - subgroup, - schedule_get(node, id), - ); - } - } - } - - return new Transition(subgroups, this._parents, name, id); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selectAll.js - - /* harmony default export */ function transition_selectAll(select) { - var name = this._name, - id = this._id; - - if (typeof select !== 'function') select = selectorAll(select); - - for ( - var groups = this._groups, - m = groups.length, - subgroups = [], - parents = [], - j = 0; - j < m; - ++j - ) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if ((node = group[i])) { - for ( - var children = select.call(node, node.__data__, i, group), - child, - inherit = schedule_get(node, id), - k = 0, - l = children.length; - k < l; - ++k - ) { - if ((child = children[k])) { - schedule(child, name, id, k, children, inherit); - } - } - subgroups.push(children); - parents.push(node); - } - } - } - - return new Transition(subgroups, parents, name, id); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selection.js - - var selection_Selection = src_selection.prototype.constructor; - - /* harmony default export */ function transition_selection() { - return new selection_Selection(this._groups, this._parents); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/style.js - - function styleNull(name, interpolate) { - var string00, string10, interpolate0; - return function () { - var string0 = styleValue(this, name), - string1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 - ? null - : string0 === string00 && string1 === string10 - ? interpolate0 - : (interpolate0 = interpolate( - (string00 = string0), - (string10 = string1), - )); - }; - } - - function style_styleRemove(name) { - return function () { - this.style.removeProperty(name); - }; - } - - function style_styleConstant(name, interpolate, value1) { - var string00, - string1 = value1 + '', - interpolate0; - return function () { - var string0 = styleValue(this, name); - return string0 === string1 - ? null - : string0 === string00 - ? interpolate0 - : (interpolate0 = interpolate((string00 = string0), value1)); - }; - } - - function style_styleFunction(name, interpolate, value) { - var string00, string10, interpolate0; - return function () { - var string0 = styleValue(this, name), - value1 = value(this), - string1 = value1 + ''; - if (value1 == null) - string1 = value1 = - (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 - ? null - : string0 === string00 && string1 === string10 - ? interpolate0 - : ((string10 = string1), - (interpolate0 = interpolate((string00 = string0), value1))); - }; - } - - function styleMaybeRemove(id, name) { - var on0, - on1, - listener0, - key = 'style.' + name, - event = 'end.' + key, - remove; - return function () { - var schedule = schedule_set(this, id), - on = schedule.on, - listener = - schedule.value[key] == null - ? remove || (remove = style_styleRemove(name)) - : undefined; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0 || listener0 !== listener) - (on1 = (on0 = on).copy()).on(event, (listener0 = listener)); - - schedule.on = on1; - }; - } - - /* harmony default export */ function transition_style( - name, - value, - priority, - ) { - var i = - (name += '') === 'transform' ? interpolateTransformCss : interpolate; - return value == null - ? this.styleTween(name, styleNull(name, i)).on( - 'end.style.' + name, - style_styleRemove(name), - ) - : typeof value === 'function' - ? this.styleTween( - name, - style_styleFunction( - name, - i, - tweenValue(this, 'style.' + name, value), - ), - ).each(styleMaybeRemove(this._id, name)) - : this.styleTween( - name, - style_styleConstant(name, i, value), - priority, - ).on('end.style.' + name, null); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/styleTween.js - - function styleInterpolate(name, i, priority) { - return function (t) { - this.style.setProperty(name, i.call(this, t), priority); - }; - } - - function styleTween(name, value, priority) { - var t, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority); - return t; - } - tween._value = value; - return tween; - } - - /* harmony default export */ function transition_styleTween( - name, - value, - priority, - ) { - var key = 'style.' + (name += ''); - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== 'function') throw new Error(); - return this.tween( - key, - styleTween(name, value, priority == null ? '' : priority), - ); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/text.js - - function text_textConstant(value) { - return function () { - this.textContent = value; - }; - } - - function text_textFunction(value) { - return function () { - var value1 = value(this); - this.textContent = value1 == null ? '' : value1; - }; - } - - /* harmony default export */ function transition_text(value) { - return this.tween( - 'text', - typeof value === 'function' - ? text_textFunction(tweenValue(this, 'text', value)) - : text_textConstant(value == null ? '' : value + ''), - ); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/textTween.js - - function textInterpolate(i) { - return function (t) { - this.textContent = i.call(this, t); - }; - } - - function textTween(value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && textInterpolate(i); - return t0; - } - tween._value = value; - return tween; - } - - /* harmony default export */ function transition_textTween(value) { - var key = 'text'; - if (arguments.length < 1) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== 'function') throw new Error(); - return this.tween(key, textTween(value)); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/transition.js - - /* harmony default export */ function transition() { - var name = this._name, - id0 = this._id, - id1 = newId(); - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if ((node = group[i])) { - var inherit = schedule_get(node, id0); - schedule(node, name, id1, i, group, { - time: inherit.time + inherit.delay + inherit.duration, - delay: 0, - duration: inherit.duration, - ease: inherit.ease, - }); - } - } - } - - return new Transition(groups, this._parents, name, id1); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/end.js - - /* harmony default export */ function end() { - var on0, - on1, - that = this, - id = that._id, - size = that.size(); - return new Promise(function (resolve, reject) { - var cancel = { value: reject }, - end = { - value: function () { - if (--size === 0) resolve(); - }, - }; - - that.each(function () { - var schedule = schedule_set(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) { - on1 = (on0 = on).copy(); - on1._.cancel.push(cancel); - on1._.interrupt.push(cancel); - on1._.end.push(end); - } - - schedule.on = on1; - }); - - // The selection was empty, resolve end immediately - if (size === 0) resolve(); - }); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/index.js - - var id = 0; - - function Transition(groups, parents, name, id) { - this._groups = groups; - this._parents = parents; - this._name = name; - this._id = id; - } - - function transition_transition(name) { - return src_selection().transition(name); - } - - function newId() { - return ++id; - } - - var selection_prototype = src_selection.prototype; - - Transition.prototype = transition_transition.prototype = { - constructor: Transition, - select: transition_select, - selectAll: transition_selectAll, - selectChild: selection_prototype.selectChild, - selectChildren: selection_prototype.selectChildren, - filter: transition_filter, - merge: transition_merge, - selection: transition_selection, - transition: transition, - call: selection_prototype.call, - nodes: selection_prototype.nodes, - node: selection_prototype.node, - size: selection_prototype.size, - empty: selection_prototype.empty, - each: selection_prototype.each, - on: transition_on, - attr: transition_attr, - attrTween: transition_attrTween, - style: transition_style, - styleTween: transition_styleTween, - text: transition_text, - textTween: transition_textTween, - remove: transition_remove, - tween: tween, - delay: delay, - duration: duration, - ease: ease, - easeVarying: transition_easeVarying, - end: end, - [Symbol.iterator]: selection_prototype[Symbol.iterator], - }; // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/transition.js - - var defaultTiming = { - time: null, // Set on use. - delay: 0, - duration: 250, - ease: cubicInOut, - }; - - function inherit(node, id) { - var timing; - while (!(timing = node.__transition) || !(timing = timing[id])) { - if (!(node = node.parentNode)) { - throw new Error(`transition ${id} not found`); - } - } - return timing; - } - - /* harmony default export */ function selection_transition(name) { - var id, timing; - - if (name instanceof Transition) { - (id = name._id), (name = name._name); - } else { - (id = newId()), - ((timing = defaultTiming).time = now()), - (name = name == null ? null : name + ''); - } - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if ((node = group[i])) { - schedule(node, name, id, i, group, timing || inherit(node, id)); - } - } - } - - return new Transition(groups, this._parents, name, id); - } // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/index.js - - src_selection.prototype.interrupt = selection_interrupt; - src_selection.prototype.transition = selection_transition; // CONCATENATED MODULE: ../node_modules/d3-transition/src/index.js // CONCATENATED MODULE: ./colorUtils.js - - function generateHash(name) { - // Return a vector (0.0->1.0) that is a hash of the input string. - // The hash is computed to favor early characters over later ones, so - // that strings with similar starts have similar vectors. Only the first - // 6 characters are considered. - const MAX_CHAR = 6; - - let hash = 0; - let maxHash = 0; - let weight = 1; - const mod = 10; - - if (name) { - for (let i = 0; i < name.length; i++) { - if (i > MAX_CHAR) { - break; - } - hash += weight * (name.charCodeAt(i) % mod); - maxHash += weight * (mod - 1); - weight *= 0.7; - } - if (maxHash > 0) { - hash = hash / maxHash; - } - } - return hash; - } - - function generateColorVector(name) { - let vector = 0; - if (name) { - const nameArr = name.split('`'); - if (nameArr.length > 1) { - name = nameArr[nameArr.length - 1]; // drop module name if present - } - name = name.split('(')[0]; // drop extra info - vector = generateHash(name); - } - return vector; - } // CONCATENATED MODULE: ./colorScheme.js - - function calculateColor(hue, vector) { - let r; - let g; - let b; - - if (hue === 'red') { - r = 200 + Math.round(55 * vector); - g = 50 + Math.round(80 * vector); - b = g; - } else if (hue === 'orange') { - r = 190 + Math.round(65 * vector); - g = 90 + Math.round(65 * vector); - b = 0; - } else if (hue === 'yellow') { - r = 175 + Math.round(55 * vector); - g = r; - b = 50 + Math.round(20 * vector); - } else if (hue === 'green') { - r = 50 + Math.round(60 * vector); - g = 200 + Math.round(55 * vector); - b = r; - } else if (hue === 'pastelgreen') { - // rgb(163,195,72) - rgb(238,244,221) - r = 163 + Math.round(75 * vector); - g = 195 + Math.round(49 * vector); - b = 72 + Math.round(149 * vector); - } else if (hue === 'blue') { - // rgb(91,156,221) - rgb(217,232,247) - r = 91 + Math.round(126 * vector); - g = 156 + Math.round(76 * vector); - b = 221 + Math.round(26 * vector); - } else if (hue === 'aqua') { - r = 50 + Math.round(60 * vector); - g = 165 + Math.round(55 * vector); - b = g; - } else if (hue === 'cold') { - r = 0 + Math.round(55 * (1 - vector)); - g = 0 + Math.round(230 * (1 - vector)); - b = 200 + Math.round(55 * vector); - } else { - // original warm palette - r = 200 + Math.round(55 * vector); - g = 0 + Math.round(230 * (1 - vector)); - b = 0 + Math.round(55 * (1 - vector)); - } - - return 'rgb(' + r + ',' + g + ',' + b + ')'; - } // CONCATENATED MODULE: ./flamegraph.js - - /* harmony default export */ function flamegraph() { - let w = 960; // graph width - let h = null; // graph height - let c = 18; // cell height - let selection = null; // selection - let tooltip = null; // tooltip - let title = ''; // graph title - let transitionDuration = 750; - let transitionEase = cubicInOut; // tooltip offset - let sort = false; - let inverted = false; // invert the graph direction - let clickHandler = null; - let hoverHandler = null; - let minFrameSize = 0; - let detailsElement = null; - let searchDetails = null; - let selfValue = false; - let resetHeightOnZoom = false; - let scrollOnZoom = false; - let minHeight = null; - let computeDelta = false; - let colorHue = null; - - let getName = function (d) { - return d.data.n || d.data.name; - }; - - let getValue = function (d) { - if ('v' in d) { - return d.v; - } else { - return d.value; - } - }; - - let getChildren = function (d) { - return d.c || d.children; - }; - - let getLibtype = function (d) { - return d.data.l || d.data.libtype; - }; - - let getDelta = function (d) { - if ('d' in d.data) { - return d.data.d; - } else { - return d.data.delta; - } - }; - - let searchHandler = function (searchResults, searchSum, totalValue) { - searchDetails = () => { - if (detailsElement) { - detailsElement.textContent = - 'search: ' + - searchSum + - ' of ' + - totalValue + - ' total time ( ' + - format('.3f')(100 * (searchSum / totalValue), 3) + - '%)'; - } - }; - searchDetails(); - }; - const originalSearchHandler = searchHandler; - - let searchMatch = (d, term, ignoreCase = false) => { - if (!term) { - return false; - } - let label = getName(d); - if (ignoreCase) { - term = term.toLowerCase(); - label = label.toLowerCase(); - } - const re = new RegExp(term); - return typeof label !== 'undefined' && label && label.match(re); - }; - const originalSearchMatch = searchMatch; - - let detailsHandler = function (d) { - if (detailsElement) { - if (d) { - detailsElement.textContent = d; - } else { - if (typeof searchDetails === 'function') { - searchDetails(); - } else { - detailsElement.textContent = ''; - } - } - } - }; - const originalDetailsHandler = detailsHandler; - - let labelHandler = function (d) { - return ( - getName(d) + - ' (' + - format('.3f')(100 * (d.x1 - d.x0), 3) + - '%, ' + - getValue(d) + - ' ms)' - ); - }; - - let colorMapper = function (d) { - return d.highlight ? '#E600E6' : colorHash(getName(d), getLibtype(d)); - }; - const originalColorMapper = colorMapper; - - function colorHash(name, libtype) { - // Return a color for the given name and library type. The library type - // selects the hue, and the name is hashed to a color in that hue. - - // default when libtype is not in use - let hue = colorHue || 'warm'; - - if (!colorHue && !(typeof libtype === 'undefined' || libtype === '')) { - // Select hue. Order is important. - hue = 'red'; - if (typeof name !== 'undefined' && name && name.match(/::/)) { - hue = 'yellow'; - } - if (libtype === 'kernel') { - hue = 'orange'; - } else if (libtype === 'jit') { - hue = 'green'; - } else if (libtype === 'inlined') { - hue = 'aqua'; - } - } - - const vector = generateColorVector(name); - return calculateColor(hue, vector); - } - - function show(d) { - d.data.fade = false; - d.data.hide = false; - if (d.children) { - d.children.forEach(show); - } - } - - function hideSiblings(node) { - let child = node; - let parent = child.parent; - let children, i, sibling; - while (parent) { - children = parent.children; - i = children.length; - while (i--) { - sibling = children[i]; - if (sibling !== child) { - sibling.data.hide = true; - } - } - child = parent; - parent = child.parent; - } - } - - function fadeAncestors(d) { - if (d.parent) { - d.parent.data.fade = true; - fadeAncestors(d.parent); - } - } - - function zoom(d) { - if (tooltip) tooltip.hide(); - hideSiblings(d); - show(d); - fadeAncestors(d); - update(); - if (scrollOnZoom) { - const chartOffset = src_select(this).select('svg')._groups[0][0] - .parentNode.offsetTop; - const maxFrames = (window.innerHeight - chartOffset) / c; - const frameOffset = (d.height - maxFrames + 10) * c; - window.scrollTo({ - top: chartOffset + frameOffset, - left: 0, - behavior: 'smooth', - }); - } - if (typeof clickHandler === 'function') { - clickHandler(d); - } - } - - function searchTree(d, term) { - const results = []; - let sum = 0; - - function searchInner(d, foundParent) { - let found = false; - - if (searchMatch(d, term)) { - d.highlight = true; - found = true; - if (!foundParent) { - sum += getValue(d); - } - results.push(d); - } else { - d.highlight = false; - } - - if (getChildren(d)) { - getChildren(d).forEach(function (child) { - searchInner(child, foundParent || found); - }); - } - } - searchInner(d, false); - - return [results, sum]; - } - - function findTree(d, id) { - if (d.id === id) { - return d; - } else { - const children = getChildren(d); - if (children) { - for (let i = 0; i < children.length; i++) { - const found = findTree(children[i], id); - if (found) { - return found; - } - } - } - } - } - - function clear(d) { - d.highlight = false; - if (getChildren(d)) { - getChildren(d).forEach(function (child) { - clear(child); - }); - } - } - - function doSort(a, b) { - if (typeof sort === 'function') { - return sort(a, b); - } else if (sort) { - return ascending_ascending(getName(a), getName(b)); - } - } - - const p = partition(); - - function filterNodes(root) { - let nodeList = root.descendants(); - if (minFrameSize > 0) { - const kx = w / (root.x1 - root.x0); - nodeList = nodeList.filter(function (el) { - return (el.x1 - el.x0) * kx > minFrameSize; - }); - } - return nodeList; - } - - function update() { - selection.each(function (root) { - const x = linear_linear().range([0, w]); - const y = linear_linear().range([0, c]); - - reappraiseNode(root); - - if (sort) root.sort(doSort); - - p(root); - - const kx = w / (root.x1 - root.x0); - function width(d) { - return (d.x1 - d.x0) * kx; - } - - const descendants = filterNodes(root); - const svg = src_select(this).select('svg'); - svg.attr('width', w); - - let g = svg.selectAll('g').data(descendants, function (d) { - return d.id; - }); - - // if height is not set: set height on first update, after nodes were filtered by minFrameSize - if (!h || resetHeightOnZoom) { - const maxDepth = Math.max.apply( - null, - descendants.map(function (n) { - return n.depth; - }), - ); - - h = (maxDepth + 3) * c; - if (h < minHeight) h = minHeight; - - svg.attr('height', h); - } - - g.transition() - .duration(transitionDuration) - .ease(transitionEase) - .attr('transform', function (d) { - return ( - 'translate(' + - x(d.x0) + - ',' + - (inverted ? y(d.depth) : h - y(d.depth) - c) + - ')' - ); - }); - - g.select('rect') - .transition() - .duration(transitionDuration) - .ease(transitionEase) - .attr('width', width); - - const node = g - .enter() - .append('svg:g') - .attr('transform', function (d) { - return ( - 'translate(' + - x(d.x0) + - ',' + - (inverted ? y(d.depth) : h - y(d.depth) - c) + - ')' - ); - }); - - node - .append('svg:rect') - .transition() - .delay(transitionDuration / 2) - .attr('width', width); - - if (!tooltip) { - node.append('svg:title'); - } - - node.append('foreignObject').append('xhtml:div'); - - // Now we have to re-select to see the new elements (why?). - g = svg.selectAll('g').data(descendants, function (d) { - return d.id; - }); - - g.attr('width', width) - .attr('height', function (d) { - return c; - }) - .attr('name', function (d) { - return getName(d); - }) - .attr('class', function (d) { - return d.data.fade ? 'frame fade' : 'frame'; - }); - - g.select('rect') - .attr('height', function (d) { - return c; - }) - .attr('fill', function (d) { - return colorMapper(d); - }); - - if (!tooltip) { - g.select('title').text(labelHandler); - } - - g.select('foreignObject') - .attr('width', width) - .attr('height', function (d) { - return c; - }) - .select('div') - .attr('class', 'd3-flame-graph-label') - .style('display', function (d) { - return width(d) < 35 ? 'none' : 'block'; - }) - .transition() - .delay(transitionDuration) - .text(getName); - - g.on('click', (_, d) => { - zoom(d); - }); - - g.exit().remove(); - - g.on('mouseover', function (_, d) { - if (tooltip) tooltip.show(d, this); - detailsHandler(labelHandler(d)); - if (typeof hoverHandler === 'function') { - hoverHandler(d); - } - }).on('mouseout', function () { - if (tooltip) tooltip.hide(); - detailsHandler(null); - }); - }); - } - - function merge(data, samples) { - samples.forEach(function (sample) { - const node = data.find(function (element) { - return element.name === sample.name; - }); - - if (node) { - node.value += sample.value; - if (sample.children) { - if (!node.children) { - node.children = []; - } - merge(node.children, sample.children); - } - } else { - data.push(sample); - } - }); - } - - function forEachNode(node, f) { - f(node); - let children = node.children; - if (children) { - const stack = [children]; - let count, child, grandChildren; - while (stack.length) { - children = stack.pop(); - count = children.length; - while (count--) { - child = children[count]; - f(child); - grandChildren = child.children; - if (grandChildren) { - stack.push(grandChildren); - } - } - } - } - } - - function adoptNode(node) { - let id = 0; - forEachNode(node, function (n) { - n.id = id++; - }); - } - - function reappraiseNode(root) { - let node, - children, - grandChildren, - childrenValue, - i, - j, - child, - childValue; - const stack = []; - const included = []; - const excluded = []; - const compoundValue = !selfValue; - let item = root.data; - if (item.hide) { - root.value = 0; - children = root.children; - if (children) { - excluded.push(children); - } - } else { - root.value = item.fade ? 0 : getValue(item); - stack.push(root); - } - // First DFS pass: - // 1. Update node.value with node's self value - // 2. Populate excluded list with children under hidden nodes - // 3. Populate included list with children under visible nodes - while ((node = stack.pop())) { - children = node.children; - if (children && (i = children.length)) { - childrenValue = 0; - while (i--) { - child = children[i]; - item = child.data; - if (item.hide) { - child.value = 0; - grandChildren = child.children; - if (grandChildren) { - excluded.push(grandChildren); - } - continue; - } - if (item.fade) { - child.value = 0; - } else { - childValue = getValue(item); - child.value = childValue; - childrenValue += childValue; - } - stack.push(child); - } - // Here second part of `&&` is actually checking for `node.data.fade`. However, - // checking for node.value is faster and presents more oportunities for JS optimizer. - if (compoundValue && node.value) { - node.value -= childrenValue; - } - included.push(children); - } - } - // Postorder traversal to compute compound value of each visible node. - i = included.length; - while (i--) { - children = included[i]; - childrenValue = 0; - j = children.length; - while (j--) { - childrenValue += children[j].value; - } - children[0].parent.value += childrenValue; - } - // Continue DFS to set value of all hidden nodes to 0. - while (excluded.length) { - children = excluded.pop(); - j = children.length; - while (j--) { - child = children[j]; - child.value = 0; - grandChildren = child.children; - if (grandChildren) { - excluded.push(grandChildren); - } - } - } - } - - function processData() { - selection.datum((data) => { - if (data.constructor.name !== 'Node') { - // creating a root hierarchical structure - const root = hierarchy(data, getChildren); - - // augumenting nodes with ids - adoptNode(root); - - // calculate actual value - reappraiseNode(root); - - // store value for later use - root.originalValue = root.value; - - // computing deltas for differentials - if (computeDelta) { - root.eachAfter((node) => { - let sum = getDelta(node); - const children = node.children; - let i = children && children.length; - while (--i >= 0) sum += children[i].delta; - node.delta = sum; - }); - } - - // setting the bound data for the selection - return root; - } - }); - } - - function chart(s) { - if (!arguments.length) { - return chart; - } - - // saving the selection on `.call` - selection = s; - - // processing raw data to be used in the chart - processData(); - - // create chart svg - selection.each(function (data) { - if (src_select(this).select('svg').size() === 0) { - const svg = src_select(this) - .append('svg:svg') - .attr('width', w) - .attr('class', 'partition d3-flame-graph'); - - if (h) { - if (h < minHeight) h = minHeight; - svg.attr('height', h); - } - - svg - .append('svg:text') - .attr('class', 'title') - .attr('text-anchor', 'middle') - .attr('y', '25') - .attr('x', w / 2) - .attr('fill', '#808080') - .text(title); - - if (tooltip) svg.call(tooltip); - } - }); - - // first draw - update(); - } - - chart.height = function (_) { - if (!arguments.length) { - return h; - } - h = _; - return chart; - }; - - chart.minHeight = function (_) { - if (!arguments.length) { - return minHeight; - } - minHeight = _; - return chart; - }; - - chart.width = function (_) { - if (!arguments.length) { - return w; - } - w = _; - return chart; - }; - - chart.cellHeight = function (_) { - if (!arguments.length) { - return c; - } - c = _; - return chart; - }; - - chart.tooltip = function (_) { - if (!arguments.length) { - return tooltip; - } - if (typeof _ === 'function') { - tooltip = _; - } - return chart; - }; - - chart.title = function (_) { - if (!arguments.length) { - return title; - } - title = _; - return chart; - }; - - chart.transitionDuration = function (_) { - if (!arguments.length) { - return transitionDuration; - } - transitionDuration = _; - return chart; - }; - - chart.transitionEase = function (_) { - if (!arguments.length) { - return transitionEase; - } - transitionEase = _; - return chart; - }; - - chart.sort = function (_) { - if (!arguments.length) { - return sort; - } - sort = _; - return chart; - }; - - chart.inverted = function (_) { - if (!arguments.length) { - return inverted; - } - inverted = _; - return chart; - }; - - chart.computeDelta = function (_) { - if (!arguments.length) { - return computeDelta; - } - computeDelta = _; - return chart; - }; - - chart.setLabelHandler = function (_) { - if (!arguments.length) { - return labelHandler; - } - labelHandler = _; - return chart; - }; - // Kept for backwards compatibility. - chart.label = chart.setLabelHandler; - - chart.search = function (term) { - const searchResults = []; - let searchSum = 0; - let totalValue = 0; - selection.each(function (data) { - const res = searchTree(data, term); - searchResults.push(...res[0]); - searchSum += res[1]; - totalValue += data.originalValue; - }); - searchHandler(searchResults, searchSum, totalValue); - update(); - }; - - chart.findById = function (id) { - if (typeof id === 'undefined' || id === null) { - return null; - } - let found = null; - selection.each(function (data) { - if (found === null) { - found = findTree(data, id); - } - }); - return found; - }; - - chart.clear = function () { - detailsHandler(null); - selection.each(function (root) { - clear(root); - update(); - }); - }; - - chart.zoomTo = function (d) { - zoom(d); - }; - - chart.resetZoom = function () { - selection.each(function (root) { - zoom(root); // zoom to root - }); - }; - - chart.onClick = function (_) { - if (!arguments.length) { - return clickHandler; - } - clickHandler = _; - return chart; - }; - - chart.onHover = function (_) { - if (!arguments.length) { - return hoverHandler; - } - hoverHandler = _; - return chart; - }; - - chart.merge = function (data) { - if (!selection) { - return chart; - } - - // TODO: Fix merge with zoom - // Merging a zoomed chart doesn't work properly, so - // clearing zoom before merge. - // To apply zoom on merge, we would need to set hide - // and fade on new data according to current data. - // New ids are generated for the whole data structure, - // so previous ids might not be the same. For merge to - // work with zoom, previous ids should be maintained. - this.resetZoom(); - - // Clear search details - // Merge requires a new search, updating data and - // the details handler with search results. - // Since we don't store the search term, can't - // perform search again. - searchDetails = null; - detailsHandler(null); - - selection.datum((root) => { - merge([root.data], [data]); - return root.data; - }); - processData(); - update(); - return chart; - }; - - chart.update = function (data) { - if (!selection) { - return chart; - } - if (data) { - selection.datum(data); - processData(); - } - update(); - return chart; - }; - - chart.destroy = function () { - if (!selection) { - return chart; - } - if (tooltip) { - tooltip.hide(); - if (typeof tooltip.destroy === 'function') { - tooltip.destroy(); - } - } - selection.selectAll('svg').remove(); - return chart; - }; - - chart.setColorMapper = function (_) { - if (!arguments.length) { - colorMapper = originalColorMapper; - return chart; - } - colorMapper = (d) => { - const originalColor = originalColorMapper(d); - return _(d, originalColor); - }; - return chart; - }; - // Kept for backwards compatibility. - chart.color = chart.setColorMapper; - - chart.setColorHue = function (_) { - if (!arguments.length) { - colorHue = null; - return chart; - } - colorHue = _; - return chart; - }; - - chart.minFrameSize = function (_) { - if (!arguments.length) { - return minFrameSize; - } - minFrameSize = _; - return chart; - }; - - chart.setDetailsElement = function (_) { - if (!arguments.length) { - return detailsElement; - } - detailsElement = _; - return chart; - }; - // Kept for backwards compatibility. - chart.details = chart.setDetailsElement; - - chart.selfValue = function (_) { - if (!arguments.length) { - return selfValue; - } - selfValue = _; - return chart; - }; - - chart.resetHeightOnZoom = function (_) { - if (!arguments.length) { - return resetHeightOnZoom; - } - resetHeightOnZoom = _; - return chart; - }; - - chart.scrollOnZoom = function (_) { - if (!arguments.length) { - return scrollOnZoom; - } - scrollOnZoom = _; - return chart; - }; - - chart.getName = function (_) { - if (!arguments.length) { - return getName; - } - getName = _; - return chart; - }; - - chart.getValue = function (_) { - if (!arguments.length) { - return getValue; - } - getValue = _; - return chart; - }; - - chart.getChildren = function (_) { - if (!arguments.length) { - return getChildren; - } - getChildren = _; - return chart; - }; - - chart.getLibtype = function (_) { - if (!arguments.length) { - return getLibtype; - } - getLibtype = _; - return chart; - }; - - chart.getDelta = function (_) { - if (!arguments.length) { - return getDelta; - } - getDelta = _; - return chart; - }; - - chart.setSearchHandler = function (_) { - if (!arguments.length) { - searchHandler = originalSearchHandler; - return chart; - } - searchHandler = _; - return chart; - }; - - chart.setDetailsHandler = function (_) { - if (!arguments.length) { - detailsHandler = originalDetailsHandler; - return chart; - } - detailsHandler = _; - return chart; - }; - - chart.setSearchMatch = function (_) { - if (!arguments.length) { - searchMatch = originalSearchMatch; - return chart; - } - searchMatch = _; - return chart; - }; - - return chart; - } - - __webpack_exports__ = __webpack_exports__['default']; - /******/ return __webpack_exports__; - /******/ - })(); -}); diff --git a/development/charts/table/index.html b/development/charts/table/index.html deleted file mode 100644 index 8fa7c604f91b..000000000000 --- a/development/charts/table/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - -
-
- - - - - - - -
S.NoNameTotalTime
-
-
- - diff --git a/development/charts/table/jquery.min.js b/development/charts/table/jquery.min.js deleted file mode 100644 index 8cdc80eb85d8..000000000000 --- a/development/charts/table/jquery.min.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * jQuery JavaScript Library v1.6.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Jun 30 14:16:56 2011 -0400 - */ -(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. -shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j -)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/development/fitness-functions/rules/index.ts b/development/fitness-functions/rules/index.ts index 6ba0f1198684..5afb72fa5343 100644 --- a/development/fitness-functions/rules/index.ts +++ b/development/fitness-functions/rules/index.ts @@ -6,7 +6,7 @@ const RULES: IRule[] = [ name: "Don't use `sinon` or `assert` in unit tests", fn: preventSinonAssertSyntax, errorMessage: - '`sinon` or `assert` was detected in the diff. Please use Jest instead. For more info: https://github.com/MetaMask/metamask-extension/blob/develop/docs/testing.md#favor-jest-instead-of-mocha', + '`sinon` or `assert` was detected in the diff. Please use Jest instead. For more info: https://github.com/MetaMask/metamask-extension/blob/main/docs/testing.md#favor-jest-instead-of-mocha', }, { name: "Don't add JS or JSX files", diff --git a/development/highlights/index.js b/development/highlights/index.js index 2b263005fb96..9efd3073d031 100644 --- a/development/highlights/index.js +++ b/development/highlights/index.js @@ -6,11 +6,11 @@ module.exports = { getHighlights }; async function getHighlights({ artifactBase }) { let highlights = ''; - // here we assume the PR base branch ("target") is `develop` in lieu of doing + // here we assume the PR base branch ("target") is `main` in lieu of doing // a query against the github api which requires an access token // see https://discuss.circleci.com/t/how-to-retrieve-a-pull-requests-base-branch-name-github/36911 - const changedFiles = await getChangedFiles({ target: 'develop' }); - console.log(`detected changed files vs develop:`); + const changedFiles = await getChangedFiles({ target: 'origin/main' }); + console.log(`detected changed files vs main:`); for (const filename of changedFiles) { console.log(` ${filename}`); } diff --git a/development/lib/get-manifest-flag.ts b/development/lib/get-manifest-flag.ts new file mode 100644 index 000000000000..225b3a05121c --- /dev/null +++ b/development/lib/get-manifest-flag.ts @@ -0,0 +1,101 @@ +import path from 'node:path'; +import fs from 'node:fs/promises'; +import { promisify } from 'node:util'; +import { exec as callbackExec } from 'node:child_process'; + +import { hasProperty } from '@metamask/utils'; +import { merge } from 'lodash'; + +import type { ManifestFlags } from '../../app/scripts/lib/manifestFlags'; + +const exec = promisify(callbackExec); +const PR_BODY_FILEPATH = path.resolve( + __dirname, + '..', + '..', + 'changed-files', + 'pr-body.txt', +); + +/** + * Search a string for `flags = {...}` and return ManifestFlags if it exists + * + * @param str - The string to search + * @param errorType - The type of error to log if parsing fails + * @returns The ManifestFlags object if valid, otherwise undefined + */ +function regexSearchForFlags(str: string, errorType: string): ManifestFlags { + // Search str for `flags = {...}` + const flagsMatch = str.match(/flags\s*=\s*(\{.*\})/u); + + if (flagsMatch) { + try { + // Get 1st capturing group from regex + return JSON.parse(flagsMatch[1]); + } catch (error) { + console.error( + `Error parsing flags from ${errorType}, ignoring flags\n`, + error, + ); + } + } + + return {}; +} + +/** + * Get flags from the GitHub PR body if they are set + * + * To use this feature, add a line to your PR body like: + * `flags = {"sentry": {"tracesSampleRate": 0.1}}` + * (must be valid JSON) + * + * @returns Any manifest flags found in the PR body + */ +async function getFlagsFromPrBody(): Promise { + let body: string; + try { + body = await fs.readFile(PR_BODY_FILEPATH, 'utf8'); + } catch (error) { + if ( + error instanceof Error && + hasProperty(error, 'code') && + error.code === 'ENOENT' + ) { + return {}; + } + throw error; + } + + return regexSearchForFlags(body, 'PR body'); +} + +/** + * Get flags from the Git message if they are set + * + * To use this feature, add a line to your commit message like: + * `flags = {"sentry": {"tracesSampleRate": 0.1}}` + * (must be valid JSON) + * + * @returns Any manifest flags found in the commit message + */ +async function getFlagsFromGitMessage(): Promise { + const gitMessage = (await exec(`git show --format='%B' --no-patch "HEAD"`)) + .stdout; + + return regexSearchForFlags(gitMessage, 'git message'); +} + +/** + * Get any manifest flags found in the PR body and git message. + * + * @returns Any manifest flags found + */ +export async function fetchManifestFlagsFromPRAndGit(): Promise { + const [prBodyFlags, gitMessageFlags] = await Promise.all([ + getFlagsFromPrBody(), + getFlagsFromGitMessage(), + ]); + + return merge(prBodyFlags, gitMessageFlags); +} diff --git a/development/lib/run-command.js b/development/lib/run-command.js index 62fe2284ea6d..12e917eb128b 100644 --- a/development/lib/run-command.js +++ b/development/lib/run-command.js @@ -1,5 +1,5 @@ const fs = require('fs'); -const spawn = require('cross-spawn'); +const { spawn } = require('node:child_process'); /** * Run a command to completion using the system shell. diff --git a/development/master-sync.js b/development/master-sync.js index 548ae6611c2f..dc1d474eaebe 100644 --- a/development/master-sync.js +++ b/development/master-sync.js @@ -51,8 +51,8 @@ async function runGitCommands() { console.log('Executed: git reset --hard origin/master'); try { - await exec('git merge origin/develop'); - console.log('Executed: git merge origin/develop'); + await exec('git merge origin/main'); + console.log('Executed: git merge origin/main'); } catch (error) { // Handle the error but continue script execution if ( @@ -70,11 +70,11 @@ async function runGitCommands() { } await exec('git add .'); - await exec('git restore --source origin/develop .'); - console.log('Executed: it restore --source origin/develop .'); + await exec('git restore --source origin/main .'); + console.log('Executed: it restore --source origin/main .'); - await exec('git checkout origin/develop -- .'); - console.log('Executed: git checkout origin/develop -- .'); + await exec('git checkout origin/main -- .'); + console.log('Executed: git checkout origin/main -- .'); await exec('git checkout origin/master -- CHANGELOG.md'); console.log('Executed: git checkout origin/master -- CHANGELOG.md'); @@ -91,7 +91,7 @@ async function runGitCommands() { await exec('git add .'); console.log('Executed: git add .'); - await exec('git commit -m "Merge origin/develop into master-sync"'); + await exec('git commit -m "Merge origin/main into master-sync"'); console.log('Executed: git commit'); console.log('Your local master-sync branch is now ready to become a PR.'); diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js index f85d64faa887..3dbb5d0f8e37 100755 --- a/development/metamaskbot-build-announce.js +++ b/development/metamaskbot-build-announce.js @@ -1,11 +1,12 @@ #!/usr/bin/env node +const { promisify } = require('util'); const { promises: fs } = require('fs'); +const execFile = promisify(require('child_process').execFile); const path = require('path'); // Fetch is part of node js in future versions, thus triggering no-shadow // eslint-disable-next-line no-shadow const fetch = require('node-fetch'); -const glob = require('fast-glob'); const VERSION = require('../package.json').version; const { getHighlights } = require('./highlights'); @@ -38,104 +39,117 @@ function getPercentageChange(from, to) { return parseFloat(((to - from) / Math.abs(from)) * 100).toFixed(2); } +/** + * Check whether an artifact exists, + * + * @param {string} url - The URL of the artifact to check. + * @returns True if the artifact exists, false if it doesn't + */ +async function artifactExists(url) { + // Using a regular GET request here rather than HEAD because for some reason CircleCI always + // returns 404 for HEAD requests. + const response = await fetch(url); + return response.ok; +} + async function start() { const { - GITHUB_COMMENT_TOKEN, - CIRCLE_PULL_REQUEST, - CIRCLE_SHA1, + PR_COMMENT_TOKEN, + PR_NUMBER, + HEAD_COMMIT_HASH, + MERGE_BASE_COMMIT_HASH, CIRCLE_BUILD_NUM, CIRCLE_WORKFLOW_JOB_ID, - PARENT_COMMIT, + HOST_URL, } = process.env; - console.log('CIRCLE_PULL_REQUEST', CIRCLE_PULL_REQUEST); - console.log('CIRCLE_SHA1', CIRCLE_SHA1); + console.log('PR_NUMBER', PR_NUMBER); + console.log('HEAD_COMMIT_HASH', HEAD_COMMIT_HASH); + console.log('MERGE_BASE_COMMIT_HASH', MERGE_BASE_COMMIT_HASH); console.log('CIRCLE_BUILD_NUM', CIRCLE_BUILD_NUM); console.log('CIRCLE_WORKFLOW_JOB_ID', CIRCLE_WORKFLOW_JOB_ID); - console.log('PARENT_COMMIT', PARENT_COMMIT); + console.log('HOST_URL', HOST_URL); - if (!CIRCLE_PULL_REQUEST) { - console.warn(`No pull request detected for commit "${CIRCLE_SHA1}"`); + if (!PR_NUMBER) { + console.warn(`No pull request detected for commit "${HEAD_COMMIT_HASH}"`); return; } - const CIRCLE_PR_NUMBER = CIRCLE_PULL_REQUEST.split('/').pop(); - const SHORT_SHA1 = CIRCLE_SHA1.slice(0, 7); + const SHORT_SHA1 = HEAD_COMMIT_HASH.slice(0, 7); const BUILD_LINK_BASE = `https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0`; // build the github comment content // links to extension builds - const platforms = ['chrome', 'firefox']; - const buildLinks = platforms - .map((platform) => { - const url = - platform === 'firefox' - ? `${BUILD_LINK_BASE}/builds-mv2/metamask-${platform}-${VERSION}.zip` - : `${BUILD_LINK_BASE}/builds/metamask-${platform}-${VERSION}.zip`; - return `${platform}`; - }) - .join(', '); - const betaBuildLinks = `chrome`; - const flaskBuildLinks = platforms - .map((platform) => { - const url = - platform === 'firefox' - ? `${BUILD_LINK_BASE}/builds-flask-mv2/metamask-flask-${platform}-${VERSION}-flask.0.zip` - : `${BUILD_LINK_BASE}/builds-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`; - return `${platform}`; - }) - .join(', '); - const mmiBuildLinks = platforms - .map((platform) => { - const url = `${BUILD_LINK_BASE}/builds-mmi/metamask-mmi-${platform}-${VERSION}-mmi.0.zip`; - return `${platform}`; - }) - .join(', '); - const testBuildLinks = platforms - .map((platform) => { - const url = - platform === 'firefox' - ? `${BUILD_LINK_BASE}/builds-test-mv2/metamask-${platform}-${VERSION}.zip` - : `${BUILD_LINK_BASE}/builds-test/metamask-${platform}-${VERSION}.zip`; - return `${platform}`; - }) - .join(', '); - const testFlaskBuildLinks = platforms - .map((platform) => { - const url = - platform === 'firefox' - ? `${BUILD_LINK_BASE}/builds-test-flask-mv2/metamask-flask-${platform}-${VERSION}-flask.0.zip` - : `${BUILD_LINK_BASE}/builds-test-flask/metamask-flask-${platform}-${VERSION}-flask.0.zip`; + const buildMap = { + builds: { + chrome: `${BUILD_LINK_BASE}/builds/metamask-chrome-${VERSION}.zip`, + firefox: `${BUILD_LINK_BASE}/builds-mv2/metamask-firefox-${VERSION}.zip`, + }, + 'builds (flask)': { + chrome: `${BUILD_LINK_BASE}/builds-flask/metamask-flask-chrome-${VERSION}-flask.0.zip`, + firefox: `${BUILD_LINK_BASE}/builds-flask-mv2/metamask-flask-firefox-${VERSION}-flask.0.zip`, + }, + 'builds (MMI)': { + chrome: `${BUILD_LINK_BASE}/builds-mmi/metamask-mmi-chrome-${VERSION}-mmi.0.zip`, + }, + 'builds (test)': { + chrome: `${BUILD_LINK_BASE}/builds-test/metamask-chrome-${VERSION}.zip`, + firefox: `${BUILD_LINK_BASE}/builds-test-mv2/metamask-firefox-${VERSION}.zip`, + }, + 'builds (test-flask)': { + chrome: `${BUILD_LINK_BASE}/builds-test-flask/metamask-flask-chrome-${VERSION}-flask.0.zip`, + firefox: `${BUILD_LINK_BASE}/builds-test-flask-mv2/metamask-flask-firefox-${VERSION}-flask.0.zip`, + }, + }; + + const commitMessage = ( + await execFile('git', ['show', '-s', '--format=%s', HEAD_COMMIT_HASH]) + ).stdout.trim(); + const betaVersionRegex = /Version v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+/u; + const betaMatch = commitMessage.match(betaVersionRegex); + + // only include beta build link if a beta version is detected in the commit message + if (betaMatch) { + const betaVersion = betaMatch[0].split('-beta.')[1]; + console.log(`Beta version ${betaVersion} detected, adding beta build link`); + buildMap['builds (beta)'] = { + chrome: `${HOST_URL}/builds-beta/metamask-beta-chrome-${VERSION}-beta.${betaVersion}.zip`, + }; + } + + const buildContentRows = Object.entries(buildMap).map(([label, builds]) => { + const buildLinks = Object.entries(builds).map(([platform, url]) => { return `${platform}`; - }) - .join(', '); + }); + return `${label}: ${buildLinks.join(', ')}`; + }); // links to bundle browser builds const bundles = {}; - const fileType = '.html'; const sourceMapRoot = '/build-artifacts/source-map-explorer/'; - const bundleFiles = await glob(`.${sourceMapRoot}*${fileType}`); - - bundleFiles.forEach((bundleFile) => { - const fileName = bundleFile.split(sourceMapRoot)[1]; - const bundleName = fileName.split(fileType)[0]; - const url = `${BUILD_LINK_BASE}${sourceMapRoot}${fileName}`; - let fileRoot = bundleName; - let fileIndex = bundleName.match(/-[0-9]{1,}$/u)?.index; - - if (fileIndex) { - fileRoot = bundleName.slice(0, fileIndex); - fileIndex = bundleName.slice(fileIndex + 1, bundleName.length); - } - - const link = `${fileIndex || fileRoot}`; + const fileRoots = [ + 'background', + 'common', + 'ui', + 'content-script', + 'offscreen', + ]; - if (fileRoot in bundles) { + for (const fileRoot of fileRoots) { + bundles[fileRoot] = []; + let fileIndex = 0; + let url = `${BUILD_LINK_BASE}${sourceMapRoot}${fileRoot}-${fileIndex}.html`; + console.log(`Verifying ${url}`); + while (await artifactExists(url)) { + const link = `${fileIndex}`; bundles[fileRoot].push(link); - } else { - bundles[fileRoot] = [link]; + + fileIndex += 1; + url = `${BUILD_LINK_BASE}${sourceMapRoot}${fileRoot}-${fileIndex}.html`; + console.log(`Verifying ${url}`); } - }); + console.log(`Not found: ${url}`); + } const bundleMarkup = `
    ${Object.keys(bundles) .map((key) => `
  • ${key}: ${bundles[key].join(', ')}
  • `) @@ -144,9 +158,6 @@ async function start() { const bundleSizeDataUrl = 'https://raw.githubusercontent.com/MetaMask/extension_bundlesize_stats/main/stats/bundle_size_data.json'; - const coverageUrl = `${BUILD_LINK_BASE}/coverage/index.html`; - const coverageLink = `Report`; - const storybookUrl = `${BUILD_LINK_BASE}/storybook/index.html`; const storybookLink = `Storybook`; @@ -156,12 +167,6 @@ async function start() { // links to bundle browser builds const depVizUrl = `${BUILD_LINK_BASE}/build-artifacts/build-viz/index.html`; const depVizLink = `Build System`; - const moduleInitStatsBackgroundUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/initialisation/background/index.html`; - const moduleInitStatsBackgroundLink = `Background Module Init Stats`; - const moduleInitStatsUIUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/initialisation/ui/index.html`; - const moduleInitStatsUILink = `UI Init Stats`; - const moduleLoadStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/load_time/index.html`; - const moduleLoadStatsLink = `Module Load Stats`; const bundleSizeStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/bundle_size.json`; const bundleSizeStatsLink = `Bundle Size Stats`; const userActionsStatsUrl = `${BUILD_LINK_BASE}/test-artifacts/chrome/benchmark/user_actions.json`; @@ -171,19 +176,10 @@ async function start() { const allArtifactsUrl = `https://circleci.com/gh/MetaMask/metamask-extension/${CIRCLE_BUILD_NUM}#artifacts/containers/0`; const contentRows = [ - `builds: ${buildLinks}`, - `builds (beta): ${betaBuildLinks}`, - `builds (flask): ${flaskBuildLinks}`, - `builds (MMI): ${mmiBuildLinks}`, - `builds (test): ${testBuildLinks}`, - `builds (test-flask): ${testFlaskBuildLinks}`, + ...buildContentRows, `build viz: ${depVizLink}`, - `mv3: ${moduleInitStatsBackgroundLink}`, - `mv3: ${moduleInitStatsUILink}`, - `mv3: ${moduleLoadStatsLink}`, `mv3: ${bundleSizeStatsLink}`, `mv2: ${userActionsStatsLink}`, - `code coverage: ${coverageLink}`, `storybook: ${storybookLink}`, `typescript migration: ${tsMigrationDashboardLink}`, `all artifacts`, @@ -198,8 +194,9 @@ async function start() { const exposedContent = `Builds ready [${SHORT_SHA1}]`; const artifactsBody = `
    ${exposedContent}${hiddenContent}
    \n\n`; + const benchmarkPlatforms = ['chrome']; const benchmarkResults = {}; - for (const platform of platforms) { + for (const platform of benchmarkPlatforms) { const benchmarkPath = path.resolve( __dirname, '..', @@ -328,7 +325,7 @@ async function start() { }; const devSizes = Object.keys(prSizes).reduce((sizes, part) => { - sizes[part] = devBundleSizeStats[PARENT_COMMIT][part] || 0; + sizes[part] = devBundleSizeStats[MERGE_BASE_COMMIT_HASH][part] || 0; return sizes; }, {}); @@ -379,7 +376,7 @@ async function start() { } const JSON_PAYLOAD = JSON.stringify({ body: commentBody }); - const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${CIRCLE_PR_NUMBER}/comments`; + const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${PR_NUMBER}/comments`; console.log(`Announcement:\n${commentBody}`); console.log(`Posting to: ${POST_COMMENT_URI}`); @@ -388,7 +385,7 @@ async function start() { body: JSON_PAYLOAD, headers: { 'User-Agent': 'metamaskbot', - Authorization: `token ${GITHUB_COMMENT_TOKEN}`, + Authorization: `token ${PR_COMMENT_TOKEN}`, }, }); if (!response.ok) { diff --git a/development/webpack/build.ts b/development/webpack/build.ts index 12ce2c95e693..4c102512ed0b 100644 --- a/development/webpack/build.ts +++ b/development/webpack/build.ts @@ -1,7 +1,7 @@ import { webpack } from 'webpack'; import type WebpackDevServerType from 'webpack-dev-server'; import { noop, logStats, __HMR_READY__ } from './utils/helpers'; -import config from './webpack.config.js'; +import config from './webpack.config'; // disable browserslist stats as it needlessly traverses the filesystem multiple // times looking for a stats file that doesn't exist. diff --git a/development/webpack/utils/plugins/ManifestPlugin/index.ts b/development/webpack/utils/plugins/ManifestPlugin/index.ts index bd18541cc1e3..a29219084de6 100644 --- a/development/webpack/utils/plugins/ManifestPlugin/index.ts +++ b/development/webpack/utils/plugins/ManifestPlugin/index.ts @@ -25,16 +25,6 @@ type Assets = Compilation['assets']; const NAME = 'ManifestPlugin'; const BROWSER_TEMPLATE_RE = /\[browser\]/gu; -/** - * Clones a Buffer or Uint8Array and returns it - * - * @param data - * @returns - */ -function clone(data: Buffer | Uint8Array): Buffer { - return Buffer.from(data); -} - /** * Adds the given asset to the zip file * @@ -54,12 +44,23 @@ function addAssetToZip( zip: Zip, ): void { const zipFile = compress - ? new AsyncZipDeflate(assetName, compressionOptions) - : new ZipPassThrough(assetName); + ? // AsyncZipDeflate uses workers + new AsyncZipDeflate(assetName, compressionOptions) + : // ZipPassThrough doesn't use workers + new ZipPassThrough(assetName); zipFile.mtime = mtime; zip.add(zipFile); - // use a copy of the Buffer, as Zip will consume it - zipFile.push(asset, true); + // Use a copy of the Buffer via `Buffer.from(asset)`, as Zip will *consume* + // it, which breaks things if we are compiling for multiple browsers at once. + // `Buffer.from` uses the internal pool, so it's superior to `new Uint8Array` + // if we don't need to pass it off to a worker thread. + // + // Additionally, in Node.js 22+ a Buffer marked as "Untransferable" (like + // ours) can't be passed to a worker, which `AsyncZipDeflate` uses. + // See: https://github.com/101arrowz/fflate/issues/227#issuecomment-2540024304 + // this can probably be simplified to `zipFile.push(Buffer.from(asset), true);` + // if the above issue is resolved. + zipFile.push(compress ? new Uint8Array(asset) : Buffer.from(asset), true); } /** @@ -145,7 +146,7 @@ export class ManifestPlugin { errored = true; reject(error); } else { - zipSource.add(new RawSource(clone(data))); + zipSource.add(new RawSource(Buffer.from(data))); // we've received our final bit of data, return the zipSource if (final) resolve(zipSource); } @@ -171,9 +172,7 @@ export class ManifestPlugin { if (excludeExtensions.includes(extName)) continue; addAssetToZip( - // make a copy of the asset Buffer as Zipping will *consume* it, - // which breaks things if we are compiling for multiple browsers. - clone(asset.buffer()), + asset.buffer(), assetName, ManifestPlugin.compressibleFileTypes.has(extName), compressionOptions, diff --git a/docs/QA_MIGRATIONS_GUIDE.md b/docs/QA_MIGRATIONS_GUIDE.md index fbd2c38a182a..beb3a239dea8 100644 --- a/docs/QA_MIGRATIONS_GUIDE.md +++ b/docs/QA_MIGRATIONS_GUIDE.md @@ -2,7 +2,7 @@ Migrations are needed to change top-level state data, this can be found in the browser's storage. This can look like removing specific keys/value pairs from state, changing objects to an array of objects, changing the name of a controller, etc. Steps - 1. Create a new MetaMask directory\* folder locally with the source files before the migration, and load it as an unpacked extension in Chrome\*. If the migration is in a pull request, then build the `develop` branch to load. If the migration is already in `develop`, get a commit before the migration was added to build. + 1. Create a new MetaMask directory\* folder locally with the source files before the migration, and load it as an unpacked extension in Chrome\*. If the migration is in a pull request, then build the `main` branch to load. If the migration is already in `main`, get a commit before the migration was added to build. ![Load unpacked extension to chrome](./assets/load-build-chrome.gif) diff --git a/docs/confirmation-refactoring/confirmation-backend-architecture/README.md b/docs/confirmation-refactoring/confirmation-backend-architecture/README.md index a3f30eb88a56..b927b5789801 100644 --- a/docs/confirmation-refactoring/confirmation-backend-architecture/README.md +++ b/docs/confirmation-refactoring/confirmation-backend-architecture/README.md @@ -5,13 +5,13 @@ Current confirmation implementation in the background consists of following pieces: 1. `TransactionController` and utility, helper classes used by it: - `TransactionController` is very important piece in transaction processing. It is described [here](https://github.com/MetaMask/metamask-extension/tree/develop/app/scripts/controllers/transactions#transaction-controller). It consists of 4 important parts: + `TransactionController` is very important piece in transaction processing. It is described [here](https://github.com/MetaMask/metamask-extension/tree/main/app/scripts/controllers/transactions#transaction-controller). It consists of 4 important parts: - `txStateManager`: responsible for the state of a transaction and storing the transaction - `pendingTxTracker`: watching blocks for transactions to be include and emitting confirmed events - `txGasUtil`: gas calculations and safety buffering - `nonceTracker`: calculating nonces 2. `MessageManagers`: - There are 3 different message managers responsible for processing signature requests. These are detailed [here](https://github.com/MetaMask/metamask-extension/tree/develop/docs/refactoring/signature-request#proposed-refactoring). + There are 3 different message managers responsible for processing signature requests. These are detailed [here](https://github.com/MetaMask/metamask-extension/tree/main/docs/refactoring/signature-request#proposed-refactoring). 3. `MetamaskController `: `MetamaskController ` is responsible for gluing together the different pieces in transaction processing. It is responsible to inject dependencies in `TransactionController`, `MessageManagers`, handling different events, responses to DAPP requests, etc. @@ -19,8 +19,8 @@ Current confirmation implementation in the background consists of following piec 1. Migrating to `@metamask/transaction-controller`. `TransactionController` in extension repo should eventually get replaced by core repo [TransactionController](https://github.com/MetaMask/core/tree/main/packages/transaction-controller). This controller is maintained by core team and also used in Metamask Mobile App. 2. Migrating to `@metamask/message-manager`. Message Managers in extension repo should be deprecated in favor of core repo [MessageManagers](https://github.com/MetaMask/core/tree/main/packages/message-manager). -3. Cleanup Code in `MetamaskController`. [Metamaskcontroller](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/metamask-controller.js) is where `TransactionController` and different `MessageManagers` are initialized. It is responsible for injecting required dependencies. Also, it is responsible for handling incoming DAPP requests and invoking appropriate methods in these background classes. Over the period of time lot of code that should have been part of `TransactionController` and `MessageManagers` has ended up in `MetamaskController`. We need to cleanup this code and move to the appropriate classes. - - Code [here](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3097) to check if `eth_sign` is enabled in preferences and perform other validation on the incoming request should be part of [MessageManager](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/message-manager.js) +3. Cleanup Code in `MetamaskController`. [Metamaskcontroller](https://github.com/MetaMask/metamask-extension/blob/main/app/scripts/metamask-controller.js) is where `TransactionController` and different `MessageManagers` are initialized. It is responsible for injecting required dependencies. Also, it is responsible for handling incoming DAPP requests and invoking appropriate methods in these background classes. Over the period of time lot of code that should have been part of `TransactionController` and `MessageManagers` has ended up in `MetamaskController`. We need to cleanup this code and move to the appropriate classes. + - Code [here](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3097) to check if `eth_sign` is enabled in preferences and perform other validation on the incoming request should be part of [MessageManager](https://github.com/MetaMask/metamask-extension/blob/main/app/scripts/lib/message-manager.js) - Method to sign messages [signMessage](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3158), [signPersonalMessage](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3217), [signTypedMessage](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3470) can be simplified by injecting `KeyringController` into `MessageManagers`. - There are about 11 different methods to `add`, `approve`, `reject` different types of signature requests. These can probably be moved to a helper class, thus reducing lines of code from `MetamaskController `. - This [code](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L959) can better be placed in `TransactionController`. diff --git a/docs/confirmation-refactoring/confirmation-page-structure/README.md b/docs/confirmation-refactoring/confirmation-page-structure/README.md index a0ecc658836d..3ef31d0e0607 100644 --- a/docs/confirmation-refactoring/confirmation-page-structure/README.md +++ b/docs/confirmation-refactoring/confirmation-page-structure/README.md @@ -11,7 +11,7 @@ Currently we have following confirmation pages mapping to confirmation routes: 5. `pages/confirm-token-transaction-base` 6. `pages/confirm-contract-interaction` -![Confirmation Pages structure](https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/docs/confirmation-refactoring/confirmation-page-structure/current.png) +![Confirmation Pages structure](https://raw.githubusercontent.com/MetaMask/metamask-extension/main/docs/confirmation-refactoring/confirmation-page-structure/current.png) `confirm-page-container` component helps to define a structure for confirmation pages it includes: @@ -26,7 +26,7 @@ Other confirmation components listed above map to different types of transaction ## Areas of Refactoring: -1. ### [confirm-transaction-base](https://github.com/MetaMask/metamask-extension/tree/develop/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js) cleanup: +1. ### [confirm-transaction-base](https://github.com/MetaMask/metamask-extension/tree/main/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js) cleanup: The `confirm-transaction-base` component is huge 1200 lines component taking care of lot of complexity. We need to break it down into smaller components and move logic to hooks or utility classes. Layout related part can be moved to `confirm-page-container`. - Extract out code to render data into separate component from [here](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js#L641). - Extract out component to render hex data from [here](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js#L675). @@ -37,11 +37,11 @@ Other confirmation components listed above map to different types of transaction - Code to get error key [getErrorKey](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js#L230) can be moved to a util function. - As new component for gas selection popups is created this code [handleEditGas, handleCloseEditGas](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js#L276) can be moved to it. - Convert `confirm-transaction-base` into a functional components and extract out all of these functions into a hook - `handleEdit`, `handleCancelAll`, `handleCancel`, `handleSubmit`, `handleSetApprovalForAll`, etc. -2. ### [confirm-transaction-base-container](https://github.com/MetaMask/metamask-extension/tree/develop/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js) cleanup: +2. ### [confirm-transaction-base-container](https://github.com/MetaMask/metamask-extension/tree/main/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js) cleanup: This container is doing much work to query and get required transaction related values from state and pass over to `confirm-transaction-base` component. As we refactor state we should get rid of this component. - remove the use of `state.confirmTransaction` from the component - create hook to get values derived from metamask state and active transaction. - State cleanup is detailed more in a separate document [here](https://github.com/MetaMask/metamask-extension/tree/develop/docs/confirmation-refactoring/confirmation-state-management). + State cleanup is detailed more in a separate document [here](https://github.com/MetaMask/metamask-extension/tree/main/docs/confirmation-refactoring/confirmation-state-management). 3. ### [confirm-page-container](https://github.com/MetaMask/metamask-extension/tree/03ccc5366cf31c9fa0fedc2fac533ebc64e6f2b4/ui/components/app/confirm-page-container) cleanup: As described we should continue to have `confirm-page-container` components taking care of layout. Also wherever possible more re-usable smaller layout components for different part of confirmation page like gas details, gas selection popover, etc should be added. `confirm-page-container` defines a layout which is used by most comfirmation pages, but some pages like new token allowance implementation for `ERC20` differ from this layout. We will be able to use more and more of these re-usable components for other confirmation pages layouts also. @@ -53,9 +53,9 @@ Other confirmation components listed above map to different types of transaction There are 2 different versions popovers for gas editing: - - Legacy gas popover - [component](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/edit-gas-popover) - - EIP-1559 V2 gas popover - [component1](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/edit-gas-fee-popover), [component2](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/advanced-gas-fee-popover). - Context [transaction-modal-context](https://github.com/MetaMask/metamask-extension/blob/develop/ui/contexts/transaction-modal.js) is used to show hide EIP-1559 gas popovers. + - Legacy gas popover - [component](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/edit-gas-popover) + - EIP-1559 V2 gas popover - [component1](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/edit-gas-fee-popover), [component2](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/advanced-gas-fee-popover). + Context [transaction-modal-context](https://github.com/MetaMask/metamask-extension/blob/main/ui/contexts/transaction-modal.js) is used to show hide EIP-1559 gas popovers. A parent component can be created for gas editing popover which will wrap both the legacy and EIP-1559 gas popover. Depending on the type of transaction appropriate gas popover can be shown. `transaction-modal-context` can be used to take care to open/close both popovers. This parent component can be added to `confirm-transaction-base` and `token-allowance` components and thus will be available on all confirmation pages using gas editing. diff --git a/docs/confirmation-refactoring/confirmation-pages-routing/README.md b/docs/confirmation-refactoring/confirmation-pages-routing/README.md index ed4d7aef0788..a6abfcaf31ca 100644 --- a/docs/confirmation-refactoring/confirmation-pages-routing/README.md +++ b/docs/confirmation-refactoring/confirmation-pages-routing/README.md @@ -6,7 +6,7 @@ This document details how routing to confirmation pages is currently done and th The current flow of routing to confirmation pages is un-necessarily complicated and have issues. -![Confirmation Pages Routing - Current](https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/docs/confirmation-refactoring/confirmation-pages-routing/current.png) +![Confirmation Pages Routing - Current](https://raw.githubusercontent.com/MetaMask/metamask-extension/main/docs/confirmation-refactoring/confirmation-pages-routing/current.png) - There are 2 ways in which confirmation pages can be opened: 1. User triggers send flow from within Metamask @@ -24,7 +24,7 @@ The current flow of routing to confirmation pages is un-necessarily complicated The proposed routing of confirmation pages looks like. -![Confirmation Pages Routing - Proposed](https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/docs/confirmation-refactoring/confirmation-pages-routing/proposed.png) +![Confirmation Pages Routing - Proposed](https://raw.githubusercontent.com/MetaMask/metamask-extension/main/docs/confirmation-refactoring/confirmation-pages-routing/proposed.png) - There are 2 ways in which confirmation pages can be opened: 1. User triggers send flow from within Metamask @@ -52,14 +52,14 @@ The proposed routing of confirmation pages looks like. ## Areas of code refactoring -Current routing code is complicated, it is also currently tied to state change in confirmation pages that makes it more complicated. State refactoring as discussed in this [document](https://github.com/MetaMask/metamask-extension/tree/develop/docs/confirmation-refactoring/confirmation-state-management) will also help simplify it. +Current routing code is complicated, it is also currently tied to state change in confirmation pages that makes it more complicated. State refactoring as discussed in this [document](https://github.com/MetaMask/metamask-extension/tree/main/docs/confirmation-refactoring/confirmation-state-management) will also help simplify it. -- Any re-usable routing related code should be moved to [useRouting](https://github.com/MetaMask/metamask-extension/blob/develop/ui/hooks/useRouting.js) hook. +- Any re-usable routing related code should be moved to [useRouting](https://github.com/MetaMask/metamask-extension/blob/main/ui/hooks/useRouting.js) hook. - Logic to initially check state and redirect to `/pages/confirm-transaction` can be moved from `/pages/home` to `pages/routes` - All the route mapping code should be moved to `/pages/confirm-transaction`, this will require getting rid of route mappings in `/pages/confirm-transaction/confirm-token-transaction-switch`, `/pages/confirm-transaction-switch`. - `/pages/confirm-transaction-switch` has the code that checks the un-approved transaction / message in the state, and based on its type and asset redirect to a specific route, a utility method can be created to do this mapping and can be included in `/pages/confirm-transaction` component. - During the send flow initiated within metamask user can be redirected to specific confirmations route **`/confirm-transaction/${id}/XXXX`** -- Confirmation components have lot of props passing which needs to be reduced. Values can be obtained from redux state or other contexts directly using hooks. Component [confirm-token-transaction-switch](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction/confirm-token-transaction-switch.js) has a lot of un-necessary props passing which should be removed and will help to further refactor routing. +- Confirmation components have lot of props passing which needs to be reduced. Values can be obtained from redux state or other contexts directly using hooks. Component [confirm-token-transaction-switch](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction/confirm-token-transaction-switch.js) has a lot of un-necessary props passing which should be removed and will help to further refactor routing. - **Routing to mostRecentOverviewPage** Across confirmation pages there is code to re-direct to `mostRecentOverviewPage`. `mostRecentOverviewPage` is equal to default route `/` or `/asset` whichever was last opened. diff --git a/docs/confirmation-refactoring/confirmation-state-management/README.md b/docs/confirmation-refactoring/confirmation-state-management/README.md index a24184154e5a..da9cf143a754 100644 --- a/docs/confirmation-refactoring/confirmation-state-management/README.md +++ b/docs/confirmation-refactoring/confirmation-state-management/README.md @@ -12,11 +12,11 @@ State Management is very important piece to keep frontend confirmation code simp Refactorings: -- There are confirmations related ducks [here](https://github.com/MetaMask/metamask-extension/tree/develop/ui/ducks): - - [confirm-transaction](https://github.com/MetaMask/metamask-extension/tree/develop/ui/ducks/confirm-transaction): this is redundant and we should be able to get rid of it. - - [gas](https://github.com/MetaMask/metamask-extension/tree/develop/ui/ducks/gas): this is not used anywhere and can be removed. - - [send](https://github.com/MetaMask/metamask-extension/tree/develop/ui/ducks/send): this duck is important state machine for send flow and we should continue to maintain. -- [gasFeeContext](https://github.com/MetaMask/metamask-extension/blob/develop/ui/contexts/gasFee.js) is huge context written on top of [gasFeeInput](https://github.com/MetaMask/metamask-extension/tree/develop/ui/hooks/gasFeeInput) hook. The context / hook provides about 20 different values used in different places in confirmation pages. We need to break this down: +- There are confirmations related ducks [here](https://github.com/MetaMask/metamask-extension/tree/main/ui/ducks): + - [confirm-transaction](https://github.com/MetaMask/metamask-extension/tree/main/ui/ducks/confirm-transaction): this is redundant and we should be able to get rid of it. + - [gas](https://github.com/MetaMask/metamask-extension/tree/main/ui/ducks/gas): this is not used anywhere and can be removed. + - [send](https://github.com/MetaMask/metamask-extension/tree/main/ui/ducks/send): this duck is important state machine for send flow and we should continue to maintain. +- [gasFeeContext](https://github.com/MetaMask/metamask-extension/blob/main/ui/contexts/gasFee.js) is huge context written on top of [gasFeeInput](https://github.com/MetaMask/metamask-extension/tree/main/ui/hooks/gasFeeInput) hook. The context / hook provides about 20 different values used in different places in confirmation pages. We need to break this down: - Context is required only to provide temporary UI state for confirmation pages which includes: @@ -38,10 +38,10 @@ Refactorings: - `gasFeeEstimates` - `isNetworkBusy` - `minimumGasLimitDec` is a constant value 21000 should be removed from the context, this can be moved to constants file. - - Create separate hook for transaction functions [here](https://github.com/MetaMask/metamask-extension/blob/develop/ui/hooks/gasFeeInput/useTransactionFunctions.js), this hook can consume GasFeeContext. - - Setters and manual update functions are only used by legacy gas component [edit-gas-fee-popover](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/edit-gas-popover). This component uses useGasFeeInputs hook. We need to create a smaller hook just for this component using the above context and hooks. + - Create separate hook for transaction functions [here](https://github.com/MetaMask/metamask-extension/blob/main/ui/hooks/gasFeeInput/useTransactionFunctions.js), this hook can consume GasFeeContext. + - Setters and manual update functions are only used by legacy gas component [edit-gas-fee-popover](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/edit-gas-popover). This component uses useGasFeeInputs hook. We need to create a smaller hook just for this component using the above context and hooks. -* [confirm-transaction-base.container.js](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js) and [confirm-transaction-base.component.js](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js) has a lot of code to derive values from state and selected transactions. This can be simplified by using hooks that will he created. +* [confirm-transaction-base.container.js](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js) and [confirm-transaction-base.component.js](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js) has a lot of code to derive values from state and selected transactions. This can be simplified by using hooks that will he created. * We will have a lot of hooks for transaction related fields, these can be grouped into same file / folder. As we work on the components we will be able to identify more areas of improvement. diff --git a/docs/confirmation-refactoring/signature-request/README.md b/docs/confirmation-refactoring/signature-request/README.md index a80cc046ca6c..3585ef4a9c4e 100644 --- a/docs/confirmation-refactoring/signature-request/README.md +++ b/docs/confirmation-refactoring/signature-request/README.md @@ -6,35 +6,35 @@ This document details the plan to refactor and cleanup Signature Request pages i 1. Simple ETH Signature - + 1. Personal Signature - + 1. Typed Data - V1 - + 1. Typed Data - V3 - + 1. Typed Data - V4 - + 1. SIWE Signature - + ## The current flow of control for Signature Request looks like: -![Signature Request Flow - Current](https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/docs/confirmation-refactoring/signature-request/signature_request_old.png) +![Signature Request Flow - Current](https://raw.githubusercontent.com/MetaMask/metamask-extension/main/docs/confirmation-refactoring/signature-request/signature_request_old.png) ## The proposed flow of control: -![Signature Request Flow - Proposed](https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/docs/confirmation-refactoring/signature-request/signature_request_proposed.png) +![Signature Request Flow - Proposed](https://raw.githubusercontent.com/MetaMask/metamask-extension/main/docs/confirmation-refactoring/signature-request/signature_request_proposed.png) ## Proposed Refactoring: @@ -44,9 +44,9 @@ There are many areas in above flow where the code can be improved upon to cleanu Currently we have 3 different message managers: - - [MessageManager](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/message-manager.js) - - [PersonalMessageManager](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/personal-message-manager.js) - - [TypedMessageManager](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/typed-message-manager.js) + - [MessageManager](https://github.com/MetaMask/metamask-extension/blob/main/app/scripts/lib/message-manager.js) + - [PersonalMessageManager](https://github.com/MetaMask/metamask-extension/blob/main/app/scripts/lib/personal-message-manager.js) + - [TypedMessageManager](https://github.com/MetaMask/metamask-extension/blob/main/app/scripts/lib/typed-message-manager.js) Above message managers handle different types of message requests sent by DAPP. There is a lot of code duplication between the 3 classes. @@ -56,14 +56,14 @@ There are many areas in above flow where the code can be improved upon to cleanu Current navigation to Signature Request pages is un-necessarily complicated. It can be simplified to great extent. - - To the navigation code in [Home](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/home/home.component.js#L181) component add condition to check if there are unapproved messages and route to path `/singature-request`. - - In [Routes](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/routes/routes.component.js) component render pages/confirm-signature-request for path `/singature-request`. - - Refactor out [conf-tx.js](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction/conf-tx.js) into pages/confirm-signature-request component. [#17240](https://github.com/MetaMask/metamask-extension/issues/17240) + - To the navigation code in [Home](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/home/home.component.js#L181) component add condition to check if there are unapproved messages and route to path `/singature-request`. + - In [Routes](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/routes/routes.component.js) component render pages/confirm-signature-request for path `/singature-request`. + - Refactor out [conf-tx.js](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction/conf-tx.js) into pages/confirm-signature-request component. [#17240](https://github.com/MetaMask/metamask-extension/issues/17240) -3. ### Refactoring in [conf-tx.js](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction/conf-tx.js) +3. ### Refactoring in [conf-tx.js](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction/conf-tx.js) - - [conf-tx.js](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction/conf-tx.js) to be renamed to `pages/confirm-signature-request component` - - Get rid of [confirm-transaction](https://github.com/MetaMask/metamask-extension/blob/develop/ui/pages/confirm-transaction/confirm-transaction.component.js) component from signature request routing. Thus, we need to ensure that any required logic from the component is extracted into a reusable hook and included in pages/confirm-signature-request. + - [conf-tx.js](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction/conf-tx.js) to be renamed to `pages/confirm-signature-request component` + - Get rid of [confirm-transaction](https://github.com/MetaMask/metamask-extension/blob/main/ui/pages/confirm-transaction/confirm-transaction.component.js) component from signature request routing. Thus, we need to ensure that any required logic from the component is extracted into a reusable hook and included in pages/confirm-signature-request. - Convert to functional react component and use selectors to get state and get rid of `mapStateToProps`. [#17239](https://github.com/MetaMask/metamask-extension/issues/17239) - Various callbacks to `sign message`, `cancel request`, etc for different types of messages can be moved to respective child components. - On component `mount/update` if there are no unapproved messages redirect to `mostRecentlyOverviewedPage` as [here](https://github.com/MetaMask/metamask-extension/blob/76a2a9bb8b6ea04025328d36404ac3b59121dfc8/ui/app/pages/confirm-transaction/conf-tx.js#L187). @@ -74,12 +74,12 @@ There are many areas in above flow where the code can be improved upon to cleanu There are 3 different signature request components responsible to render different signature request pages: - 1. [signature-request-original](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/signature-request-original) - ETH sign, personal sign, sign typed data V1 - 2. [signature-request](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/signature-request) - Sign typed data V3, V4 - 3. [signature-request-siwe](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/signature-request-siwe) - SignatureRequestSIWE (Sign-In with Ethereum) + 1. [signature-request-original](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/signature-request-original) - ETH sign, personal sign, sign typed data V1 + 2. [signature-request](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/signature-request) - Sign typed data V3, V4 + 3. [signature-request-siwe](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/signature-request-siwe) - SignatureRequestSIWE (Sign-In with Ethereum) All, the signature request pages (except SIWE) are very similar, the differing part in these pages is the message section. - And there is a lot of code duplication between components - [signature-request-original](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/signature-request-original) and [signature-request](https://github.com/MetaMask/metamask-extension/tree/develop/ui/components/app/signature-request). + And there is a lot of code duplication between components - [signature-request-original](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/signature-request-original) and [signature-request](https://github.com/MetaMask/metamask-extension/tree/main/ui/components/app/signature-request). 5. ### Refactoring in signature-request-original @@ -89,12 +89,12 @@ There are many areas in above flow where the code can be improved upon to cleanu - Move this [metrics event](https://github.com/MetaMask/metamask-extension/blob/71a0bc8b3ff94478e61294c815770e6bc12a72f5/ui/app/components/app/signature-request-original/signature-request-original.component.js#L50) to pages/confirm-signature-request as it is applicable to all the signature requests types. - Header or we can say upper half of the page of all signature request pages (except SIWE) are very similar, this can be extracted into a reusable component used across both signature-request-original and signature-request: - + - [LedgerInstructions](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/components/app/signature-request-original/signature-request-original.component.js#L312) can also be moved to the header. - Create a reuable footer component and use it across all confirmation pages. [#17237](https://github.com/MetaMask/metamask-extension/issues/17237) - + - Create a reusable component for Cancel All requests for use across signature request pages [Code](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/components/app/signature-request-original/signature-request-original.component.js#L326). - Extract [getNetrowkName](https://github.com/MetaMask/metamask-extension/blob/e07ec9dcf3d3f341f83e6b29a29d30edaf7f5b5b/ui/components/app/signature-request-original/signature-request-original.component.js#L60) into a reusable hook / utility method. diff --git a/docs/lavamoat-policy-review-process.md b/docs/lavamoat-policy-review-process.md new file mode 100644 index 000000000000..097154e6c268 --- /dev/null +++ b/docs/lavamoat-policy-review-process.md @@ -0,0 +1,29 @@ +# LavaMoat Policy Review process in metamask-extension + +When there's a need to change policy (because of new or updated packages that require a different set of capabilities), please follow these steps: + +> In the initial soft-launch of the process the approval from policy reviewers is not mandatory, but it will be in the near future. + +### Engineer on the dev team: + 1. Notice the `Validate lavamoat policy*` PR status check fail because dependency updates or changes need a policy update. + 2. (optional) Generate an updated policy and give it a cursory look in local development whenever you’re testing the change. + - If you're confident your update is complete, you can push it to the PR branch. + 3. To generate a complete set of new policies, call `metamaskbot` for help: + - put `@metamaskbot update-policies` in a comment on the PR. When it produces changes, they need to be reviewed. The following steps assume update-policies produced changes. + - *Note the response from the bot points to instructions on how to review the policy for your convenience. https://lavamoat.github.io/guides/policy-diff/* + 4. Analyze the diff of policy.json and use the understanding of the codebase and change being made to decide whether the capabilities added make sense in that context. Leave a comment listing any doubts you have with brief explanations or if everything is in order \- saying so and explaining why the most powerful capabilities are there (like access to child_process.exec in node or network access in the browser) + *Remember the Security Reviewer comes with more security expertise but less intimate knowledge of the codebase and the change you’ve built, so you are the most qualified to know whether the dependency needs the powers detected by LavaMoat or not.* + - You can use these questions to guide your analysis: + 1. What new powers (globals and builtins) do you see? Why should the package be allowed to use these new powers? Explain if possible + 2. What new packages do you see? Did you intend to introduce them? If you didn’t, which package did? (can you see them in `packages` field in policy of any other package that you updated or introduced?) + - The comment is mandatory even if you don’t understand the policy change, in which case you’re expected to state so (it’s ok to not understand) + - Note: this could be enforced by a job that is only passing if the comment was made + - Note: we’d introduce more tooling to summarize and analyze policy and post that as a comment on the PR + 5. Mention `policy-reviewers` group in your comment. + policy-reviewers group includes security liaisons and their involvement is not limited to (but likely focused more around) their respective teams’ PRs. + +### L1 Security Reviewer: + 1. Look at the policy and the comment from the Developer. Approve the PR if they match and the policy change seems safe. Address questions the Developer had and discuss if the policy change doesn’t seem right. + 2. If changes are hard to explain or seem dangerous, escalate to a review of the dependency and its powers by mentioning `supply-chain` +### (optional) L2 Security Reviewer: + 1. Review the dependency in question and inform the PR reviewers whether it’s deemed malicious or safe. diff --git a/docs/publishing.md b/docs/publishing.md index fdf1ab1850a7..5915f1873a88 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -30,9 +30,9 @@ In the case that a new release has sensitive changes that cannot be fully verifi ## Building -While we develop on the main `develop` branch, our production version is maintained on the `master` branch. +While we develop on the `main` branch, our production version is maintained on the `master` branch. -With each pull request, the @MetaMaskBot will comment with a build of that new pull request, so after bumping the version on `develop`, open a pull request against `master`, and once the pull request is reviewed and merged, you can download those builds for publication. +With each pull request, the @MetaMaskBot will comment with a build of that new pull request, so after bumping the version on `main`, open a pull request against `master`, and once the pull request is reviewed and merged, you can download those builds for publication. ## Publishing @@ -45,7 +45,7 @@ With each pull request, the @MetaMaskBot will comment with a build of that new p ## Hotfix Differences -Our `develop` branch is usually not yet fully tested for quality assurance, and so should be treated as if it is in an unstable state. +Our `main` branch is usually not yet fully tested for quality assurance, and so should be treated as if it is in an unstable state. For this reason, when an urgent change is needed in production, its pull request should: @@ -53,4 +53,4 @@ For this reason, when an urgent change is needed in production, its pull request - Use a hotfix tag. - Should be proposed against the `master` branch. -The version and changelog bump should then be made off the `master` branch, and then merged to `develop` to bring the two branches back into sync. Further time can be saved by incorporating the version/changelog bump into the PR against `master`, since we rely on @MetaMaskBot to run tests before merging. +The version and changelog bump should then be made off the `master` branch, and then merged to `main` to bring the two branches back into sync. Further time can be saved by incorporating the version/changelog bump into the PR against `master`, since we rely on @MetaMaskBot to run tests before merging. diff --git a/docs/testing.md b/docs/testing.md index 76c91ecfb368..5cc23dfb7c42 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -74,7 +74,6 @@ const generateGasEstimatorProps = (overrides) => ({ const generateAppState = (overrides) => ({ networkDropdownOpen: false, - gasIsLoading: false, isLoading: false, modal: { open: false, diff --git a/jest.integration.config.js b/jest.integration.config.js index 685080330fb3..5a110d7a5632 100644 --- a/jest.integration.config.js +++ b/jest.integration.config.js @@ -25,8 +25,7 @@ module.exports = { setupFilesAfterEnv: ['/test/integration/config/setupAfter.js'], testMatch: ['/test/integration/**/*.test.(js|ts|tsx)'], testPathIgnorePatterns: ['/test/integration/config/*'], - // This was increased from 5500 to 10000 to when lazy loading was introduced - testTimeout: 10000, + testTimeout: 15000, // We have to specify the environment we are running in, which is jsdom. The // default is 'node'. This can be modified *per file* using a comment at the // head of the file. So it may be worthwhile to switch to 'node' in any diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 30456a0bd61d..e029e41783d0 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -5,144 +5,124 @@ "regeneratorRuntime": "write" } }, - "@ensdomains/content-hash": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { "globals": { - "console.warn": true + "WeakRef": true }, "packages": { - "@ensdomains/content-hash>cids": true, - "@ensdomains/content-hash>js-base64": true, - "@ensdomains/content-hash>multicodec": true, - "@ensdomains/content-hash>multihashes": true, - "browserify>buffer": true + "browserify": true } }, - "@ensdomains/content-hash>cids": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec": true + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true } }, - "@ensdomains/content-hash>cids>multibase": { + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "SuppressedError": true + } + }, + "@ensdomains/content-hash": { + "globals": { + "console.warn": true }, "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true + "browserify>buffer": true, + "@ensdomains/content-hash>cids": true, + "@ensdomains/content-hash>js-base64": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>multihashes": true } }, - "@ensdomains/content-hash>cids>multihashes": { + "@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>cids>uint8arrays": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true } }, - "@ensdomains/content-hash>js-base64": { - "globals": { - "Base64": "write", - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true, - "define": true - }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays": true, - "sass-embedded>varint": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays": { + "@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "Buffer": true, - "TextDecoder": true, "TextEncoder": true - }, - "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "TextDecoder": true, - "TextEncoder": true, - "console.warn": true, - "crypto.subtle.digest": true - } - }, - "@ensdomains/content-hash>multihashes": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase": true, - "@ensdomains/content-hash>multihashes>varint": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase>base-x": { + "@ethereumjs/tx": { "packages": { - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, - "@ensdomains/content-hash>multihashes>web-encoding": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { - "browserify>util": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@ethereumjs/tx": { + "eth-lattice-keyring>@ethereumjs/tx": { "packages": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethersproject/providers": true, "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true } }, - "@ethereumjs/tx>@ethereumjs/common": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, "browserify>buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/common>crc-32": { - "globals": { - "DO_NOT_EXPORT_CRC": true, - "define": true - } - }, - "@ethereumjs/tx>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -150,88 +130,84 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true, "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { - "Headers": true, - "TextDecoder": true, - "URL": true, - "btoa": true, + "console.warn": true, "fetch": true }, "packages": { - "browserify>browserify-zlib": true, - "browserify>buffer": true, - "browserify>url": true, - "browserify>util": true, - "https-browserify": true, - "process": true, - "stream-http": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true } }, - "@ethereumjs/tx>ethereum-cryptography": { + "@ethersproject/abi": { "globals": { - "TextDecoder": true, - "crypto": true + "console.log": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { + "ethers>@ethersproject/abstract-signer": { "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/address": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/rlp": true } }, - "@ethersproject/abi": { + "ethers>@ethersproject/base64": { "globals": { - "console.log": true + "atob": true, + "btoa": true }, "packages": { - "@ethersproject/bignumber": true, + "@ethersproject/bytes": true + } + }, + "ethers>@ethersproject/basex": { + "packages": { "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "ethers>@ethersproject/properties": true } }, "@ethersproject/bignumber": { "packages": { "@ethersproject/bytes": true, - "bn.js": true, - "ethers>@ethersproject/logger": true + "ethers>@ethersproject/logger": true, + "bn.js": true } }, "@ethersproject/bytes": { @@ -239,17 +215,22 @@ "ethers>@ethersproject/logger": true } }, + "ethers>@ethersproject/constants": { + "packages": { + "@ethersproject/bignumber": true + } + }, "@ethersproject/contracts": { "globals": { "setTimeout": true }, "packages": { "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/abstract-provider": true, "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/transactions": true @@ -257,10 +238,10 @@ }, "@ethersproject/hash": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/address": true, "ethers>@ethersproject/base64": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, @@ -269,9 +250,9 @@ }, "@ethersproject/hdnode": { "packages": { + "ethers>@ethersproject/basex": true, "@ethersproject/bignumber": true, "@ethersproject/bytes": true, - "ethers>@ethersproject/basex": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, @@ -282,1147 +263,1033 @@ "ethers>@ethersproject/wordlists": true } }, - "@ethersproject/providers": { - "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "ethers>@ethersproject/json-wallets": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/networks": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true - } - }, - "@ethersproject/providers>@ethersproject/random": { - "globals": { - "crypto.getRandomValues": true + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/json-wallets>aes-js": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true } }, - "@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "ethers>@ethersproject/keccak256": { "packages": { "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "eth-ens-namehash>js-sha3": true } }, - "@ethersproject/wallet": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/transactions": true + "ethers>@ethersproject/logger": { + "globals": { + "console": true } }, - "@keystonehq/bc-ur-registry-eth": { + "ethers>@ethersproject/providers>@ethersproject/networks": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { - "globals": { - "define": true - }, + "@metamask/test-bundler>@ethersproject/networks": { "packages": { - "@ngraveio/bc-ur": true, - "@swc/helpers>tslib": true, - "browserify>buffer": true, - "buffer": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/metamask-airgapped-keyring": { + "ethers>@ethersproject/pbkdf2": { "packages": { - "@ethereumjs/tx": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, - "@metamask/obs-store": true, - "browserify>buffer": true, - "ethereumjs-util>rlp": true, - "uuid": true, - "webpack>events": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/sha2": true } }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { + "ethers>@ethersproject/properties": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { - "globals": { - "TextEncoder": true + "ethers>@ethersproject/logger": true } }, - "@lavamoat/lavadome-react": { + "@ethersproject/providers": { "globals": { - "Document.prototype": true, - "DocumentFragment.prototype": true, - "Element.prototype": true, - "Node.prototype": true, + "WebSocket": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, "console.warn": true, - "document": true + "setInterval": true, + "setTimeout": true }, "packages": { - "react": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "@metamask/test-bundler>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/providers>bech32": true } }, - "@material-ui/core": { + "ethers>@ethersproject/providers": { "globals": { - "Image": true, - "_formatMuiErrorMessage": true, - "addEventListener": true, + "WebSocket": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "getComputedStyle": true, - "getSelection": true, - "innerHeight": true, - "innerWidth": true, - "matchMedia": true, - "navigator": true, - "performance.now": true, - "removeEventListener": true, - "requestAnimationFrame": true, "setInterval": true, "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles": true, - "@material-ui/core>@material-ui/system": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "@material-ui/core>popper.js": true, - "@material-ui/core>react-transition-group": true, - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/providers>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/providers>bech32": true } }, - "@material-ui/core>@material-ui/styles": { + "@ethersproject/providers>@ethersproject/random": { "globals": { - "console.error": true, - "console.warn": true, - "document.createComment": true, - "document.head": true - }, - "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, - "@material-ui/core>@material-ui/styles>jss-plugin-global": true, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, - "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "prop-types": true, - "react": true, - "react-redux>hoist-non-react-statics": true + "crypto.getRandomValues": true } }, - "@material-ui/core>@material-ui/styles>jss": { - "globals": { - "CSS": true, - "document.createElement": true, - "document.querySelector": true - }, + "ethers>@ethersproject/random": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { + "ethers>@ethersproject/rlp": { "packages": { - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { - "globals": { - "CSS": true - }, + "ethers>@ethersproject/sha2": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2>hash.js": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-global": { + "ethers>@ethersproject/signing-key": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/signing-key>elliptic": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": { + "ethers>@ethersproject/solidity": { "packages": { - "@babel/runtime": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { + "ethers>@ethersproject/strings": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { + "ethers>@ethersproject/transactions": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/signing-key": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { - "globals": { - "document.createElement": true, - "document.documentElement": true, - "getComputedStyle": true - }, + "ethers>@ethersproject/units": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true + "@ethersproject/bignumber": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": { - "globals": { - "document": true + "@ethersproject/wallet": { + "packages": { + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/transactions": true } }, - "@material-ui/core>@material-ui/system": { + "@ethersproject/providers>@ethersproject/web": { "globals": { - "console.error": true + "clearTimeout": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/utils": true, - "prop-types": true - } - }, - "@material-ui/core>@material-ui/utils": { - "packages": { - "@babel/runtime": true, - "prop-types": true, - "prop-types>react-is": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>popper.js": { + "ethers>@ethersproject/providers>@ethersproject/web": { "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator": true, - "requestAnimationFrame": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>react-transition-group": { + "ethers>@ethersproject/web": { "globals": { - "Element": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true }, "packages": { - "@material-ui/core>react-transition-group>dom-helpers": true, - "prop-types": true, - "react": true, - "react-dom": true - } - }, - "@material-ui/core>react-transition-group>dom-helpers": { - "packages": { - "@babel/runtime": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask/abi-utils": { + "ethers>@ethersproject/wordlists": { "packages": { - "@metamask/abi-utils>@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask/abi-utils>@metamask/utils": { + "@metamask/notification-services-controller>firebase>@firebase/app": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "FinalizationRegistry": true, + "console.warn": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/accounts-controller": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/eth-snap-keyring": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/utils": true, - "uuid": true - } - }, - "@metamask/address-book-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask/announcement-controller": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { "packages": { - "@metamask/announcement-controller>@metamask/base-controller": true + "@metamask/notification-services-controller>firebase>@firebase/util": true } }, - "@metamask/announcement-controller>@metamask/base-controller": { + "@metamask/notification-services-controller>firebase>@firebase/installations": { "globals": { + "BroadcastChannel": true, + "Headers": true, + "btoa": true, + "console.error": true, + "crypto": true, + "fetch": true, + "msCrypto": true, + "navigator.onLine": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask/approval-controller": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { "globals": { - "console.info": true + "console": true }, "packages": { - "@metamask/approval-controller>nanoid": true, - "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "tslib": true } }, - "@metamask/approval-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/assets-controllers": { + "@metamask/notification-services-controller>firebase>@firebase/messaging": { "globals": { - "AbortController": true, "Headers": true, + "Notification.maxActions": true, + "Notification.permission": true, + "Notification.requestPermission": true, + "PushSubscription.prototype.hasOwnProperty": true, + "ServiceWorkerRegistration": true, "URL": true, - "URLSearchParams": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setInterval": true, + "addEventListener": true, + "atob": true, + "btoa": true, + "clients.matchAll": true, + "clients.openWindow": true, + "console.warn": true, + "document": true, + "fetch": true, + "indexedDB": true, + "location.href": true, + "location.origin": true, + "navigator": true, + "origin.replace": true, + "registration.showNotification": true, "setTimeout": true }, "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, - "@metamask/assets-controllers>@metamask/polling-controller": true, - "@metamask/base-controller": true, - "@metamask/contract-metadata": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "bn.js": true, - "cockatiel": true, - "ethers>@ethersproject/address": true, - "lodash": true, - "single-call-balance-checker-abi": true, - "uuid": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/installations": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, + "tslib": true } }, - "@metamask/assets-controllers>@metamask/polling-controller": { + "@metamask/notification-services-controller>firebase>@firebase/util": { "globals": { - "clearTimeout": true, - "console.error": true, + "atob": true, + "browser": true, + "btoa": true, + "chrome": true, + "console": true, + "document": true, + "indexedDB": true, + "navigator": true, + "process": true, + "self": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "process": true } }, - "@metamask/base-controller": { - "globals": { - "setTimeout": true - }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { - "immer": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "eth-lattice-keyring>rlp": true, + "uuid": true } }, - "@metamask/browser-passworder": { - "globals": { - "CryptoKey": true, - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, + "@keystonehq/bc-ur-registry-eth": { "packages": { - "@metamask/browser-passworder>@metamask/utils": true, - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "uuid": true } }, - "@metamask/browser-passworder>@metamask/utils": { + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "define": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ngraveio/bc-ur": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "buffer": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "tslib": true } }, - "@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "@keystonehq/metamask-airgapped-keyring": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, + "@ethereumjs/tx": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, + "@keystonehq/bc-ur-registry-eth": true, + "@metamask/obs-store": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true + "webpack>events": true, + "@keystonehq/metamask-airgapped-keyring>rlp": true, + "uuid": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser": { + "chart.js>@kurkle/color": { "globals": { - "console.error": true, - "console.log": true + "define": true + } + }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "DocumentFragment.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "react": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { "packages": { - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true } }, - "@metamask/controllers>web3": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { "globals": { - "XMLHttpRequest": true + "console.warn": true } }, - "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { - "globals": { - "fetch": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true } }, - "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { "globals": { - "fetch": true + "console.warn": true + }, + "packages": { + "@ethersproject/abi": true, + "ethers>@ethersproject/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, + "browserify>buffer": true, + "semver": true } }, - "@metamask/ens-controller": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { + "globals": { + "console.warn": true + }, "packages": { - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/ens-controller>@metamask/base-controller": true, - "@metamask/ens-controller>@metamask/utils": true, - "punycode": true + "wait-on>rxjs": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { + "globals": { + "__ledgerLogsListen": "write", + "console.error": true } }, - "@metamask/ens-controller>@metamask/base-controller": { + "@material-ui/core": { "globals": { + "Image": true, + "_formatMuiErrorMessage": true, + "addEventListener": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "getSelection": true, + "innerHeight": true, + "innerWidth": true, + "matchMedia": true, + "navigator": true, + "performance.now": true, + "removeEventListener": true, + "requestAnimationFrame": true, + "setInterval": true, "setTimeout": true }, "packages": { - "immer": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles": true, + "@material-ui/core>@material-ui/system": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>popper.js": true, + "prop-types": true, + "react": true, + "react-dom": true, + "prop-types>react-is": true, + "@material-ui/core>react-transition-group": true } }, - "@metamask/ens-controller>@metamask/utils": { + "@material-ui/core>@material-ui/styles": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "console.error": true, + "console.warn": true, + "document.createComment": true, + "document.head": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, + "@material-ui/core>@material-ui/styles>jss-plugin-global": true, + "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, + "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, + "@material-ui/core>@material-ui/styles>jss": true, + "prop-types": true, + "react": true } }, - "@metamask/eth-json-rpc-filters": { + "@material-ui/core>@material-ui/system": { "globals": { "console.error": true }, "packages": { - "@metamask/eth-query": true, - "@metamask/json-rpc-engine": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "prop-types": true } }, - "@metamask/eth-json-rpc-middleware": { - "globals": { - "URL": true, - "console.error": true, - "setTimeout": true - }, + "@material-ui/core>@material-ui/utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true + "@babel/runtime": true, + "prop-types": true, + "prop-types>react-is": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "@metamask/abi-utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-json-rpc-provider": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring": { - "globals": { - "addEventListener": true, - "console.error": true, - "document.createElement": true, - "document.head.appendChild": true, - "fetch": true, - "removeEventListener": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, - "@metamask/eth-sig-util": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { - "globals": { - "console.warn": true - }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, - "browserify>buffer": true, - "ethers>@ethersproject/rlp": true, - "semver": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { + "@metamask/accounts-controller": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/base-controller": true, + "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/accounts-controller>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "uuid": true + } + }, + "@metamask/address-book-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true + } + }, + "@metamask/announcement-controller": { "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true + "@metamask/announcement-controller>@metamask/base-controller": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { + "@metamask/approval-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { - "globals": { - "console.warn": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { - "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true, - "@metamask/ppom-validator>crypto-js": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { - "globals": { - "console.warn": true + "console.info": true }, "packages": { - "wait-on>rxjs": true + "@metamask/base-controller": true, + "@metamask/rpc-errors": true, + "nanoid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { + "@metamask/assets-controllers": { "globals": { - "Blob": true, - "FormData": true, + "AbortController": true, + "Headers": true, + "URL": true, "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.log": true, + "setInterval": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { - "packages": { - "@ethersproject/abi": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "ethers>@ethersproject/address": true, "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, "@ethersproject/providers": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/wordlists": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { - "globals": { - "__ledgerLogsListen": "write", - "console.error": true + "@metamask/abi-utils": true, + "@metamask/base-controller": true, + "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/metamask-eth-abis": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "cockatiel": true, + "lodash": true, + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, + "single-call-balance-checker-abi": true, + "uuid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { + "@metamask/base-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true + "immer": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "@metamask/announcement-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "define": true - } - }, - "@metamask/eth-query": { - "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "watchify>xtend": true - } - }, - "@metamask/eth-sig-util": { + "setTimeout": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "immer": true } }, - "@metamask/eth-sig-util>@metamask/utils": { + "@metamask/name-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, - "@metamask/eth-sig-util>tweetnacl": { + "@metamask/rate-limit-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "msCrypto": true, - "nacl": "write" + "setTimeout": true }, "packages": { - "browserify>browser-resolve": true + "immer": true } }, - "@metamask/eth-snap-keyring": { + "@metamask/browser-passworder": { "globals": { - "URL": true, - "console.error": true + "CryptoKey": true, + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true }, "packages": { - "@ethereumjs/tx": true, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/eth-snap-keyring>uuid": true, - "@metamask/keyring-api": true, - "@metamask/utils>@metamask/superstruct": true, - "webpack>events": true + "@metamask/browser-passworder>@metamask/utils": true, + "browserify>buffer": true } }, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "eth-keyring-controller>@metamask/browser-passworder": { + "globals": { + "crypto": true } }, - "@metamask/eth-snap-keyring>@metamask/utils": { + "@metamask/controller-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/controller-utils>@metamask/ethjs-unit": true, + "@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "bn.js": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "eth-ens-namehash": true, + "eslint>fast-deep-equal": true } }, - "@metamask/eth-snap-keyring>uuid": { - "globals": { - "crypto": true + "@metamask/ens-controller": { + "packages": { + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/utils": true, + "punycode": true } }, - "@metamask/eth-token-tracker": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { "globals": { - "console.warn": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, - "@metamask/eth-token-tracker>deep-equal": true, - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, "@metamask/safe-event-emitter": true, - "bn.js": true, - "human-standard-token-abi": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true, + "pify": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { + "@metamask/network-controller>@metamask/eth-block-tracker": { "globals": { "clearTimeout": true, "console.error": true, "setTimeout": true }, "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, "@metamask/safe-event-emitter": true, - "pify": true + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/scure-bip39": true, + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@metamask/eth-token-tracker>deep-equal": { - "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, - "@metamask/eth-token-tracker>deep-equal>is-date-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, - "@metamask/eth-token-tracker>deep-equal>which-collection": true, - "@ngraveio/bc-ur>assert>object-is": true, - "browserify>util>is-arguments": true, - "browserify>util>which-typed-array": true, - "gulp>vinyl-fs>object.assign": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true, - "string.prototype.matchall>es-abstract>is-regex": true, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>regexp.prototype.flags": true, - "string.prototype.matchall>side-channel": true + "@metamask/eth-json-rpc-filters": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/safe-event-emitter": true, + "@metamask/name-controller>async-mutex": true, + "pify": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "fetch": true, + "setTimeout": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true, - "browserify>util>is-arguments": true, - "eslint-plugin-react>array-includes>is-string": true, - "process": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>has-symbols": true + "@metamask/eth-json-rpc-provider": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { + "@metamask/eth-json-rpc-middleware": { "globals": { - "StopIteration": true + "URL": true, + "console.error": true, + "setTimeout": true }, "packages": { - "string.prototype.matchall>internal-slot": true + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/eth-json-rpc-middleware>klona": true, + "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true } }, - "@metamask/eth-token-tracker>deep-equal>is-date-object": { + "@metamask/eth-json-rpc-provider": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "uuid": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "@metamask/eth-ledger-bridge-keyring": { + "globals": { + "addEventListener": true, + "console.error": true, + "document.createElement": true, + "document.head.appendChild": true, + "fetch": true, + "removeEventListener": true + }, "packages": { - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, - "eslint-plugin-react>array-includes>is-string": true, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { + "@metamask/controller-utils>@metamask/eth-query": { "packages": { - "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true + "@metamask/ppom-validator>json-rpc-random-id": true, + "watchify>xtend": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/abi-utils": true, + "@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring": { - "globals": { - "setTimeout": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { + "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-sig-util": true, - "@swc/helpers>tslib": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>hdkey": { + "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { - "browserify>assert": true, - "crypto-browserify": true, - "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/etherscan-link": { - "globals": { - "URL": true + "@metamask/keyring-controller>@metamask/eth-simple-keyring": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "crypto-browserify>randombytes": true } }, - "@metamask/ethjs": { + "@metamask/eth-snap-keyring": { "globals": { - "clearInterval": true, - "setInterval": true + "URL": true, + "console.error": true, + "console.info": true }, "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-provider-http": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/number-to-bn": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "@ethereumjs/tx": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, + "@metamask/keyring-api": true, + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "webpack>events": true, + "@metamask/eth-snap-keyring>uuid": true } }, - "@metamask/ethjs-contract": { + "@metamask/eth-token-tracker": { + "globals": { + "console.warn": true + }, "packages": { "@babel/runtime": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "promise-to-callback": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true, + "@metamask/safe-event-emitter": true, + "bn.js": true, + "@metamask/eth-token-tracker>deep-equal": true, + "human-standard-token-abi": true } }, - "@metamask/ethjs-query": { + "@metamask/eth-trezor-keyring": { "globals": { - "console": true + "setTimeout": true }, "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format": true, - "@metamask/ethjs-query>@metamask/ethjs-rpc": true, - "promise-to-callback": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@trezor/connect-web": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/ethjs-query>@metamask/ethjs-format": { - "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "@metamask/ethjs>@metamask/number-to-bn": true + "@metamask/etherscan-link": { + "globals": { + "URL": true } }, - "@metamask/ethjs-query>@metamask/ethjs-rpc": { + "eth-method-registry>@metamask/ethjs-contract": { "packages": { + "@babel/runtime": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": true, + "eth-ens-namehash>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs>@metamask/ethjs-filter": { + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { "globals": { "clearInterval": true, "setInterval": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { "packages": { - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": true + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": { + "eth-method-registry>@metamask/ethjs-query": { "globals": { - "XMLHttpRequest": true - } - }, - "@metamask/ethjs>@metamask/ethjs-unit": { - "packages": { - "@metamask/ethjs>@metamask/number-to-bn": true, - "bn.js": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "browserify>buffer": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, - "@metamask/ethjs>@metamask/number-to-bn": { + "console": true + }, "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "bn.js": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi>number-to-bn": { + "@metamask/controller-utils>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, "bn.js": true } }, - "@metamask/ethjs>js-sha3": { - "globals": { - "define": true - }, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { "packages": { - "process": true + "browserify>buffer": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, "@metamask/gas-fee-controller": { @@ -1433,111 +1300,58 @@ }, "packages": { "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, "bn.js": true, "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/base-controller": { + "@metamask/jazzicon": { "globals": { - "setTimeout": true + "document.createElement": true, + "document.createElementNS": true }, "packages": { - "immer": true + "@metamask/jazzicon>color": true, + "@metamask/jazzicon>mersenne-twister": true + } + }, + "@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/json-rpc-engine>@metamask/utils": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/json-rpc-middleware-stream": { "globals": { - "clearTimeout": true, - "console.error": true, + "console.warn": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/safe-event-emitter": true, + "@metamask/json-rpc-middleware-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/jazzicon": { + "@metamask/snaps-sdk>@metamask/key-tree": { "globals": { - "document.createElement": true, - "document.createElementNS": true + "crypto.subtle": true }, "packages": { - "@metamask/jazzicon>color": true, - "@metamask/jazzicon>mersenne-twister": true + "@metamask/scure-bip39": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/jazzicon>color": { - "packages": { - "@metamask/jazzicon>color>clone": true, - "@metamask/jazzicon>color>color-convert": true, - "@metamask/jazzicon>color>color-string": true - } - }, - "@metamask/jazzicon>color>clone": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask/jazzicon>color>color-convert": { - "packages": { - "@metamask/jazzicon>color>color-convert>color-name": true - } - }, - "@metamask/jazzicon>color>color-string": { - "packages": { - "jest-canvas-mock>moo-color>color-name": true - } - }, - "@metamask/json-rpc-engine": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, - "@metamask/json-rpc-middleware-stream": { - "globals": { - "console.warn": true, - "setTimeout": true - }, - "packages": { - "@metamask/safe-event-emitter": true, - "@metamask/utils": true, - "readable-stream": true - } - }, - "@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/keyring-api>@metamask/utils": true, - "@metamask/keyring-api>bech32": true, - "@metamask/keyring-api>uuid": true, - "@metamask/utils>@metamask/superstruct": true - } - }, - "@metamask/keyring-api>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-api": { "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-api>uuid": { - "globals": { - "crypto": true + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>bech32": true } }, "@metamask/keyring-controller": { @@ -1548,126 +1362,32 @@ "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/keyring-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "@metamask/utils": true + "@metamask/keyring-controller>ethereumjs-wallet": true } }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring": { - "globals": { - "TextEncoder": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, - "@metamask/scure-bip39": true, - "browserify>buffer": true - } - }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-sig-util": { + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "@metamask/keyring-snap-client": true } }, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-snap-client": { "packages": { + "@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "@metamask/keyring-snap-client>uuid": true } }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { + "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, - "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, - "browserify>buffer": true, - "crypto-browserify": true, - "crypto-browserify>randombytes": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "uuid": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>assert": true, - "browserify>buffer": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethers>@ethersproject/sha2>hash.js": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>rlp": true + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": true, + "bitcoin-address-validation": true } }, "@metamask/logging-controller": { @@ -1694,53 +1414,20 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-sig-util": true, - "@metamask/message-manager>@metamask/utils": true, - "@metamask/message-manager>jsonschema": true, - "browserify>buffer": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/message-manager>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/message-manager>jsonschema": { - "packages": { - "browserify>url": true - } - }, - "@metamask/message-signing-snap>@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true + "webpack>events": true, + "uuid": true } }, - "@metamask/message-signing-snap>@noble/curves": { - "globals": { - "TextEncoder": true - }, + "@metamask/multichain": { "packages": { - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": true - } - }, - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@metamask/multichain>@metamask/api-specs": true, + "@metamask/controller-utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "lodash": true } }, "@metamask/name-controller": { @@ -1748,44 +1435,12 @@ "fetch": true }, "packages": { - "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true } }, - "@metamask/name-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/name-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/name-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true - } - }, "@metamask/network-controller": { "globals": { "btoa": true, @@ -1795,721 +1450,853 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-json-rpc-provider": true, - "@metamask/eth-query": true, "@metamask/network-controller>@metamask/eth-block-tracker": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-provider": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/network-controller>reselect": true, - "browserify>assert": true, - "browserify>util": true, + "@metamask/utils": true, + "eslint>fast-deep-equal": true, + "reselect": true, "uri-js": true, "uuid": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, + "@metamask/transaction-controller>@metamask/nonce-tracker": { "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@ethersproject/providers": true, + "browserify>assert": true, + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/notification-services-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Intl.NumberFormat": true, + "addEventListener": true, + "fetch": true, + "registration": true, + "removeEventListener": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/profile-sync-controller": true, + "@metamask/utils": true, + "@metamask/notification-services-controller>bignumber.js": true, + "@metamask/notification-services-controller>firebase": true, + "loglevel": true, + "uuid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": { + "packages": { + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true + } + }, + "@metamask/object-multiplex": { "globals": { - "setTimeout": true + "console.warn": true }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "node-fetch": true + "@metamask/object-multiplex>once": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": { + "@metamask/obs-store": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true + "@metamask/safe-event-emitter": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { + "@metamask/permission-controller": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/permission-controller>@metamask/utils": true, + "deep-freeze-strict": true, + "immer": true, + "nanoid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": { + "@metamask/permission-log-controller": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/permission-log-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": { + "@metamask/phishing-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "console.error": true, + "fetch": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack-cli>fastest-levenshtein": true, + "punycode": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "@metamask/polling-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, + "@metamask/post-message-stream": { + "globals": { + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true + }, + "packages": { + "@metamask/post-message-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": { + "@metamask/ppom-validator": { "globals": { "URL": true, "console.error": true, - "setTimeout": true + "crypto": true }, "packages": { - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, - "bn.js": true, - "pify": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "await-semaphore": true, + "browserify>buffer": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/ppom-validator>elliptic": true, + "@metamask/ppom-validator>json-rpc-random-id": true + } + }, + "@metamask/preferences-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": { + "@metamask/profile-sync-controller": { "globals": { + "Event": true, + "Headers": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/network-controller": true, + "@metamask/profile-sync-controller>@noble/ciphers": true, "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "loglevel": true, + "@metamask/profile-sync-controller>siwe": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/network-controller>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/selected-network-controller": true, + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/rpc-errors": { + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/network-controller>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/rate-limit-controller>@metamask/base-controller": true, + "@metamask/rate-limit-controller>@metamask/rpc-errors": true, + "@metamask/rate-limit-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/remote-feature-flag-controller": { "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "cockatiel": true, + "uuid": true } }, - "@metamask/network-controller>reselect": { - "globals": { - "WeakRef": true, - "console.warn": true, - "unstable_autotrackMemoize": true + "@metamask/rpc-errors": { + "packages": { + "@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-controller": { + "@metamask/rate-limit-controller>@metamask/rpc-errors": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-controller>@metamask/base-controller": { + "@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "immer": true + "webpack>events": true } }, - "@metamask/notification-controller>@metamask/utils": { + "@metamask/scure-bip39": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/scure-bip39>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/notification-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@metamask/selected-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true } }, - "@metamask/notification-services-controller": { + "@metamask/signature-controller": { "globals": { - "Intl.NumberFormat": true, - "addEventListener": true, - "fetch": true, - "registration": true, - "removeEventListener": true + "fetch": true }, "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, - "@metamask/notification-services-controller>bignumber.js": true, - "@metamask/notification-services-controller>firebase": true, - "@metamask/profile-sync-controller": true, + "@metamask/signature-controller>@metamask/eth-sig-util": true, + "@metamask/keyring-controller": true, + "@metamask/logging-controller": true, "@metamask/utils": true, - "loglevel": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/message-manager>jsonschema": true, "uuid": true } }, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { + "@metamask/smart-transactions-controller": { "globals": { - "SuppressedError": true + "URLSearchParams": true, + "clearInterval": true, + "console.error": true, + "console.log": true, + "fetch": true, + "setInterval": true + }, + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethersproject/bytes": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, + "@metamask/transaction-controller": true, + "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, + "fast-json-patch": true, + "lodash": true } }, - "@metamask/notification-services-controller>bignumber.js": { + "@metamask/snaps-controllers": { "globals": { - "crypto": true, - "define": true - } - }, - "@metamask/notification-services-controller>firebase": { - "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/messaging": true + "DecompressionStream": true, + "URL": true, + "clearTimeout": true, + "document.getElementById": true, + "fetch.bind": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/json-rpc-middleware-stream": true, + "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, + "@metamask/post-message-stream": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/snaps-registry": true, + "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/utils": true, + "@metamask/snaps-controllers>@xstate/fsm": true, + "@metamask/name-controller>async-mutex": true, + "browserify>browserify-zlib": true, + "@metamask/snaps-controllers>concat-stream": true, + "eslint>fast-deep-equal": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, + "immer": true, + "luxon": true, + "nanoid": true, + "readable-stream": true, + "@metamask/snaps-controllers>readable-web-to-node-stream": true, + "semver": true, + "@metamask/snaps-controllers>tar-stream": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app": { + "@metamask/snaps-execution-environments": { "globals": { - "FinalizationRegistry": true, - "console.warn": true + "document.getElementById": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/post-message-stream": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true, + "@metamask/snaps-execution-environments>@metamask/utils": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { + "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { - "globals": { - "console": true - }, + "@metamask/snaps-rpc-methods": { "packages": { - "@swc/helpers>tslib": true - } - }, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": { - "globals": { - "DOMException": true, - "IDBCursor": true, - "IDBDatabase": true, - "IDBIndex": true, - "IDBObjectStore": true, - "IDBRequest": true, - "IDBTransaction": true, - "indexedDB.deleteDatabase": true, - "indexedDB.open": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@noble/hashes": true, + "luxon": true } }, - "@metamask/notification-services-controller>firebase>@firebase/installations": { + "@metamask/snaps-sdk": { "globals": { - "BroadcastChannel": true, - "Headers": true, - "btoa": true, - "console.error": true, - "crypto": true, - "fetch": true, - "msCrypto": true, - "navigator.onLine": true, - "setTimeout": true + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-sdk>@metamask/utils": true } }, - "@metamask/notification-services-controller>firebase>@firebase/messaging": { + "@metamask/snaps-utils": { "globals": { - "Headers": true, - "Notification.maxActions": true, - "Notification.permission": true, - "Notification.requestPermission": true, - "PushSubscription.prototype.hasOwnProperty": true, - "ServiceWorkerRegistration": true, + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, "URL": true, - "addEventListener": true, - "atob": true, - "btoa": true, - "clients.matchAll": true, - "clients.openWindow": true, + "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "fetch": true, - "indexedDB": true, - "location.href": true, - "location.origin": true, - "navigator": true, - "origin.replace": true, - "registration.showNotification": true, - "setTimeout": true + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/installations": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true, - "@swc/helpers>tslib": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true } }, - "@metamask/notification-services-controller>firebase>@firebase/util": { + "@metamask/transaction-controller": { "globals": { - "atob": true, - "browser": true, - "btoa": true, - "chrome": true, - "console": true, - "document": true, - "indexedDB": true, - "navigator": true, - "process": true, - "self": true, + "clearTimeout": true, + "console.error": true, + "fetch": true, "setTimeout": true }, "packages": { - "process": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/abi": true, + "@ethersproject/contracts": true, + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/network-controller": true, + "@metamask/transaction-controller>@metamask/nonce-tracker": true, + "@metamask/rpc-errors": true, + "@metamask/transaction-controller>@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "browserify>buffer": true, + "eth-method-registry": true, + "webpack>events": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true } }, - "@metamask/object-multiplex": { + "@metamask/user-operation-controller": { "globals": { - "console.warn": true + "fetch": true }, "packages": { - "@metamask/object-multiplex>once": true, - "readable-stream": true - } - }, - "@metamask/object-multiplex>once": { - "packages": { - "@metamask/object-multiplex>once>wrappy": true - } - }, - "@metamask/obs-store": { - "packages": { - "@metamask/safe-event-emitter": true, - "readable-stream": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/transaction-controller": true, + "@metamask/user-operation-controller>@metamask/utils": true, + "bn.js": true, + "webpack>events": true, + "lodash": true, + "uuid": true } }, - "@metamask/permission-controller": { + "@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/permission-controller>@metamask/utils": true, - "@metamask/permission-controller>nanoid": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-controller>@metamask/base-controller": { + "@metamask/abi-utils>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/accounts-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/utils": { + "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>nanoid": { + "@metamask/controller-utils>@metamask/utils": { "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/permission-log-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/permission-log-controller>@metamask/base-controller": true, - "@metamask/permission-log-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/base-controller": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/utils": { + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/phishing-controller": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { "globals": { - "TextEncoder": true, - "URL": true, - "console.error": true, - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, - "punycode": true, - "webpack-cli>fastest-levenshtein": true + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/polling-controller": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream": { + "@metamask/eth-json-rpc-middleware>@metamask/utils": { "globals": { - "MessageEvent.prototype": true, - "WorkerGlobalScope": true, - "addEventListener": true, - "browser": true, - "chrome": true, - "location.origin": true, - "postMessage": true, - "removeEventListener": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "readable-stream": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream>@metamask/utils": { + "@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/ppom-validator": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "URL": true, - "console.error": true, - "crypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/ppom-validator>crypto-js": true, - "@metamask/ppom-validator>elliptic": true, - "await-semaphore": true, - "browserify>buffer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>crypto-js": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "crypto": true, - "define": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "@metamask/ppom-validator>elliptic>hmac-drbg": true, - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "bn.js": true, - "ethers>@ethersproject/sha2>hash.js": true, - "pumpify>inherits": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>brorand": { + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>hmac-drbg": { + "@metamask/eth-snap-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "ethers>@ethersproject/sha2>hash.js": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/preferences-controller": { + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller": { + "@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "Event": true, - "Headers": true, "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "URLSearchParams": true, - "addEventListener": true, - "console.error": true, - "dispatchEvent": true, - "fetch": true, - "removeEventListener": true, - "setTimeout": true + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/message-signing-snap>@noble/ciphers": true, - "@metamask/profile-sync-controller>siwe": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "loglevel": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe": { + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { "globals": { - "console.error": true, - "console.warn": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random": true, - "ethers": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "@metamask/keyring-api>@metamask/utils": { "globals": { - "console.error": true, - "console.log": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "@metamask/keyring-controller>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true - } - }, - "@metamask/queued-request-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller": { + "@metamask/name-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/rate-limit-controller>@metamask/base-controller": true, - "@metamask/rate-limit-controller>@metamask/rpc-errors": true, - "@metamask/rate-limit-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/base-controller": { + "@metamask/permission-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors": { + "@metamask/permission-log-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/post-message-stream>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, @@ -2520,1700 +2307,1799 @@ }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/rpc-errors": { + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods>nanoid": { + "@metamask/snaps-controllers>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/safe-event-emitter": { + "@metamask/snaps-execution-environments>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39": { + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { "globals": { + "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/scure-bip39>@noble/hashes": true, - "@metamask/utils>@scure/base": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39>@noble/hashes": { + "@metamask/snaps-rpc-methods>@metamask/utils": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@metamask/selected-network-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/network-controller>@metamask/swappable-obj-proxy": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller": { + "@metamask/snaps-sdk>@metamask/utils": { "globals": { - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/keyring-controller": true, - "@metamask/logging-controller": true, - "@metamask/message-manager>jsonschema": true, - "@metamask/signature-controller>@metamask/eth-sig-util": true, - "@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "uuid": true, - "webpack>events": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util": { + "@metamask/snaps-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "@metamask/transaction-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/smart-transactions-controller": { + "@metamask/user-operation-controller>@metamask/utils": { "globals": { - "URLSearchParams": true, - "clearInterval": true, - "console.error": true, - "console.log": true, - "fetch": true, - "setInterval": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bytes": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/polling-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, - "@metamask/smart-transactions-controller>bignumber.js": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "fast-json-patch": true, - "lodash": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "@ngraveio/bc-ur": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true + "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, + "browserify>assert": true, + "@ngraveio/bc-ur>bignumber.js": true, + "browserify>buffer": true, + "@ngraveio/bc-ur>cbor-sync": true, + "@ngraveio/bc-ur>crc": true, + "@ngraveio/bc-ur>jsbi": true, + "addons-linter>sha.js": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { - "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "webpack>events": true + "@metamask/profile-sync-controller>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/util": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "console.warn": true, - "fetch": true + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@noble/hashes": { "globals": { - "setTimeout": true - }, - "packages": { - "immer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "@metamask/scure-bip39>@noble/hashes": { "globals": { - "crypto.getRandomValues": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { "globals": { - "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "TextEncoder": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": { - "packages": { - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": true + "@popperjs/core": { + "globals": { + "Element": true, + "HTMLElement": true, + "ShadowRoot": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator.userAgent": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "console.log": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "XMLHttpRequest": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/smart-transactions-controller>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, - "@metamask/snaps-controllers": { + "@reduxjs/toolkit": { "globals": { - "DecompressionStream": true, - "URL": true, - "clearTimeout": true, - "document.getElementById": true, - "fetch.bind": true, + "AbortController": true, + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, + "__REDUX_DEVTOOLS_EXTENSION__": true, + "console": true, + "queueMicrotask": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/json-rpc-middleware-stream": true, - "@metamask/object-multiplex": true, - "@metamask/post-message-stream": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, - "@metamask/snaps-controllers>@xstate/fsm": true, - "@metamask/snaps-controllers>concat-stream": true, - "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/snaps-controllers>readable-web-to-node-stream": true, - "@metamask/snaps-controllers>tar-stream": true, - "@metamask/snaps-rpc-methods": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, - "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/utils": true, - "browserify>browserify-zlib": true, - "eslint>fast-deep-equal": true, "immer": true, - "readable-stream": true, - "semver": true - } - }, - "@metamask/snaps-controllers-flask>nanoid": { - "globals": { - "crypto.getRandomValues": true + "process": true, + "redux": true, + "redux-thunk": true, + "@reduxjs/toolkit>reselect": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "react-router-dom-v5-compat>@remix-run/router": { "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-controllers>concat-stream": { - "packages": { - "browserify>buffer": true, - "browserify>concat-stream>typedarray": true, - "pumpify>inherits": true, - "readable-stream": true, - "terser>source-map-support>buffer-from": true + "AbortController": true, + "DOMException": true, + "FormData": true, + "Headers": true, + "Request": true, + "Response": true, + "URL": true, + "URLSearchParams": true, + "console": true, + "document.defaultView": true } }, - "@metamask/snaps-controllers>nanoid": { + "@metamask/utils>@scure/base": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>readable-web-to-node-stream": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { "packages": { - "readable-stream": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/snaps-controllers>tar-stream": { + "@segment/loosely-validate-event": { "packages": { - "@metamask/snaps-controllers>tar-stream>b4a": true, - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx": true, - "browserify>browser-resolve": true + "browserify>assert": true, + "browserify>buffer": true, + "@segment/loosely-validate-event>component-type": true, + "@segment/loosely-validate-event>join-component": true } }, - "@metamask/snaps-controllers>tar-stream>b4a": { + "@sentry/browser>@sentry-internal/browser-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true - } - }, - "@metamask/snaps-controllers>tar-stream>streamx": { + "PerformanceEventTiming.prototype": true, + "PerformanceObserver": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "clearTimeout": true, + "performance": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, - "webpack>events": true - } - }, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { - "globals": { - "queueMicrotask": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-execution-environments": { + "@sentry/browser>@sentry-internal/feedback": { "globals": { - "document.getElementById": true + "FormData": true, + "HTMLFormElement": true, + "__SENTRY_DEBUG__": true, + "cancelAnimationFrame": true, + "clearTimeout": true, + "document.createElement": true, + "document.createElementNS": true, + "document.createTextNode": true, + "isSecureContext": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods": { + "@sentry/browser>@sentry-internal/replay-canvas": { + "globals": { + "Blob": true, + "HTMLCanvasElement": true, + "HTMLImageElement": true, + "ImageData": true, + "URL.createObjectURL": true, + "WeakRef": true, + "Worker": true, + "cancelAnimationFrame": true, + "console.error": true, + "createImageBitmap": true, + "document": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { + "@sentry/browser>@sentry-internal/replay": { "globals": { - "console.error": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSRule": true, + "CSSSupportsRule": true, + "Document": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLElement": true, + "HTMLFormElement": true, + "Headers": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.prototype.contains": true, + "PointerEvent": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "__RRWEB_EXCLUDE_IFRAME__": true, + "__RRWEB_EXCLUDE_SHADOW_DOM__": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, + "__rrMutationObserver": true, + "addEventListener": true, + "clearTimeout": true, + "console.debug": true, + "console.error": true, + "console.warn": true, + "customElements.get": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "location.origin": true, + "parent": true, + "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk": { + "@sentry/browser": { "globals": { - "fetch": true + "PerformanceObserver.supportedEntryTypes": true, + "Request": true, + "URL": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "addEventListener": true, + "console.error": true, + "indexedDB.open": true, + "performance.timeOrigin": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true - } - }, - "@metamask/snaps-sdk>@metamask/key-tree": { - "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/scure-bip39": true, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry-internal/feedback": true, + "@sentry/browser>@sentry-internal/replay-canvas": true, + "@sentry/browser>@sentry-internal/replay": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "@sentry/browser>@sentry/core": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Headers": true, + "Request": true, + "URL": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@sentry/utils": true } }, - "@metamask/snaps-utils": { + "@sentry/utils": { "globals": { - "File": true, - "FileReader": true, + "CustomEvent": true, + "DOMError": true, + "DOMException": true, + "EdgeRuntime": true, + "Element": true, + "ErrorEvent": true, + "Event": true, + "HTMLElement": true, + "Headers": true, + "Request": true, + "Response": true, "TextDecoder": true, "TextEncoder": true, "URL": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, + "clearTimeout": true, "console.error": true, - "console.log": true, - "console.warn": true, - "crypto": true, - "document.body.appendChild": true, - "document.createElement": true, - "fetch": true + "document": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, - "@metamask/snaps-utils>cron-parser": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>fast-xml-parser": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, - "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true, - "chalk": true, - "semver": true + "process": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { + "@solana/addresses": { "globals": { - "console.error": true + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@solana/addresses>@solana/assertions": true, + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/codecs-strings": true, + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { + "@solana/addresses>@solana/assertions": { "globals": { - "crypto.getRandomValues": true + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>@metamask/snaps-registry": { + "@solana/addresses>@solana/codecs-core": { "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>cron-parser": { + "@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, "packages": { - "browserify>browser-resolve": true, - "luxon": true + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>fast-xml-parser": { + "@solana/addresses>@solana/errors": { "globals": { - "entityName": true, - "val": true + "btoa": true + } + }, + "@metamask/controller-utils>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true }, "packages": { - "@metamask/snaps-utils>fast-xml-parser>strnum": true + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true } }, - "@metamask/snaps-utils>marked": { + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { "globals": { "console.error": true, - "console.warn": true, - "define": true + "console.log": true + }, + "packages": { + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true } }, - "@metamask/snaps-utils>rfdc": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { "packages": { - "browserify>buffer": true + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/snaps-utils>validate-npm-package-name": { + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, "packages": { - "@metamask/snaps-utils>validate-npm-package-name>builtins": true + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true } }, - "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, "packages": { - "process": true, - "semver": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true } }, - "@metamask/test-bundler>@ethersproject/networks": { + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { - "ethers>@ethersproject/logger": true + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "tslib": true } }, - "@metamask/transaction-controller": { + "@trezor/connect-web": { "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/rpc-errors": true, - "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/utils": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker": { + "@trezor/connect-web>@trezor/connect": { "packages": { - "@ethersproject/providers": true, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>@trezor/connect>@trezor/transport": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { "globals": { - "clearTimeout": true, - "setTimeout": true + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true }, "packages": { - "@swc/helpers>tslib": true + "process": true, + "tslib": true, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true } }, - "@metamask/user-operation-controller": { - "globals": { - "fetch": true - }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/transaction-controller": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "@metamask/user-operation-controller>@metamask/polling-controller": true, - "@metamask/user-operation-controller>@metamask/rpc-errors": true, - "@metamask/user-operation-controller>@metamask/utils": true, - "bn.js": true, - "lodash": true, - "superstruct": true, - "uuid": true, - "webpack>events": true + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "browserify>buffer": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/base-controller": { + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { "globals": { - "setTimeout": true + "console.warn": true }, "packages": { - "immer": true + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, + "browserify>buffer": true, + "ts-mixer": true } }, - "@metamask/user-operation-controller>@metamask/polling-controller": { + "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, + "clearInterval": true, "clearTimeout": true, "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, + "setInterval": true, "setTimeout": true }, "packages": { - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "uuid": true - } - }, - "@metamask/user-operation-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": true + "@trezor/connect-web>@trezor/utils>bignumber.js": true, + "browserify>buffer": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": { + "@welldone-software/why-did-you-render": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Element": true, + "console.group": true, + "console.groupCollapsed": true, + "console.groupEnd": true, + "console.log": true, + "console.warn": true, + "define": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "lodash": true, + "react": true } }, - "@metamask/user-operation-controller>@metamask/utils": { + "@zxing/browser": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "HTMLElement": true, + "HTMLImageElement": true, + "HTMLVideoElement": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@zxing/library": true } }, - "@metamask/utils": { + "@zxing/library": { "globals": { + "HTMLImageElement": true, + "HTMLVideoElement": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL.createObjectURL": true, + "btoa": true, + "console.log": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@zxing/library>ts-custom-error": true } }, - "@metamask/utils>@scure/base": { + "@lavamoat/lavapack>readable-stream>abort-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true - } - }, - "@ngraveio/bc-ur": { - "packages": { - "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, - "@ngraveio/bc-ur>bignumber.js": true, - "@ngraveio/bc-ur>cbor-sync": true, - "@ngraveio/bc-ur>crc": true, - "@ngraveio/bc-ur>jsbi": true, - "addons-linter>sha.js": true, - "browserify>assert": true, - "browserify>buffer": true + "AbortController": true } }, - "@ngraveio/bc-ur>assert>object-is": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true + "currency-formatter>accounting": { + "globals": { + "define": true } }, - "@ngraveio/bc-ur>bignumber.js": { + "ethers>@ethersproject/json-wallets>aes-js": { "globals": { - "crypto": true, "define": true } }, - "@ngraveio/bc-ur>cbor-sync": { + "eth-lattice-keyring>gridplus-sdk>aes-js": { "globals": { "define": true - }, + } + }, + "chalk>ansi-styles": { "packages": { - "browserify>buffer": true + "chalk>ansi-styles>color-convert": true } }, - "@ngraveio/bc-ur>crc": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { "packages": { "browserify>buffer": true } }, - "@ngraveio/bc-ur>jsbi": { - "globals": { - "define": true + "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true } }, - "@noble/hashes": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "browserify>vm-browserify": true + } + }, + "browserify>assert": { "globals": { - "TextEncoder": true, - "crypto": true + "Buffer": true + }, + "packages": { + "react>object-assign": true, + "browserify>assert>util": true } }, - "@popperjs/core": { + "@metamask/name-controller>async-mutex": { "globals": { - "Element": true, - "HTMLElement": true, - "ShadowRoot": true, - "console.error": true, - "console.warn": true, - "document": true, - "navigator.userAgent": true + "clearTimeout": true, + "setTimeout": true + }, + "packages": { + "tslib": true } }, - "@reduxjs/toolkit": { + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { "globals": { - "AbortController": true, - "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, - "__REDUX_DEVTOOLS_EXTENSION__": true, - "console": true, - "queueMicrotask": true, - "requestAnimationFrame": true, + "clearTimeout": true, "setTimeout": true }, "packages": { - "@reduxjs/toolkit>reselect": true, - "immer": true, - "process": true, - "redux": true, - "redux-thunk": true + "tslib": true } }, - "@segment/loosely-validate-event": { + "string.prototype.matchall>es-abstract>available-typed-arrays": { "packages": { - "@segment/loosely-validate-event>component-type": true, - "@segment/loosely-validate-event>join-component": true, - "browserify>assert": true, - "browserify>buffer": true + "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true } }, - "@sentry/browser": { + "await-semaphore": { + "packages": { + "process": true, + "browserify>timers-browserify": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { "globals": { - "PerformanceObserver.supportedEntryTypes": true, - "Request": true, - "URL": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_RELEASE__": true, - "addEventListener": true, - "console.error": true, - "indexedDB.open": true, - "performance.timeOrigin": true, - "setTimeout": true - }, - "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry-internal/feedback": true, - "@sentry/browser>@sentry-internal/replay": true, - "@sentry/browser>@sentry-internal/replay-canvas": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry-internal/browser-utils": { - "globals": { - "PerformanceEventTiming.prototype": true, - "PerformanceObserver": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "addEventListener": true, - "clearTimeout": true, - "performance": true, - "removeEventListener": true, + "Blob": true, + "FormData": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/feedback": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { "globals": { + "Blob": true, "FormData": true, - "HTMLFormElement": true, - "__SENTRY_DEBUG__": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "document.createElement": true, - "document.createElementNS": true, - "document.createTextNode": true, - "isSecureContext": true, - "requestAnimationFrame": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/replay": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { "globals": { "Blob": true, - "CSSConditionRule": true, - "CSSGroupingRule": true, - "CSSMediaRule": true, - "CSSRule": true, - "CSSSupportsRule": true, - "Document": true, - "DragEvent": true, - "Element": true, "FormData": true, - "HTMLElement": true, - "HTMLFormElement": true, - "Headers": true, - "MouseEvent": true, - "MutationObserver": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.prototype.contains": true, - "PointerEvent": true, - "TextEncoder": true, - "URL": true, "URLSearchParams": true, - "Worker": true, - "__RRWEB_EXCLUDE_IFRAME__": true, - "__RRWEB_EXCLUDE_SHADOW_DOM__": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, - "__rrMutationObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.debug": true, - "console.error": true, + "XMLHttpRequest": true, + "btoa": true, "console.warn": true, - "customElements.get": true, "document": true, - "innerHeight": true, - "innerWidth": true, "location.href": true, - "location.origin": true, - "parent": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/replay-canvas": { + "@metamask/snaps-controllers>tar-stream>b4a": { "globals": { - "Blob": true, - "HTMLCanvasElement": true, - "HTMLImageElement": true, - "ImageData": true, - "URL.createObjectURL": true, - "WeakRef": true, - "Worker": true, - "cancelAnimationFrame": true, - "console.error": true, - "createImageBitmap": true, - "document": true + "TextDecoder": true, + "TextEncoder": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true + } + }, + "base32-encode": { + "packages": { + "base32-encode>to-data-view": true + } + }, + "bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/notification-services-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/smart-transactions-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@ngraveio/bc-ur>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@trezor/connect-web>@trezor/utils>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bitwise": { + "packages": { + "browserify>buffer": true + } + }, + "blo": { + "globals": { + "btoa": true + } + }, + "bn.js": { + "globals": { + "Buffer": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>browser-resolve": true } }, - "@sentry/browser>@sentry/core": { + "eth-lattice-keyring>gridplus-sdk>borc": { "globals": { - "Headers": true, - "Request": true, - "URL": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_TRACING__": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true + "console": true }, "packages": { - "@sentry/utils": true + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, + "browserify>buffer": true, + "buffer>ieee754": true, + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true } }, - "@sentry/utils": { + "bowser": { "globals": { - "CustomEvent": true, - "DOMError": true, - "DOMException": true, - "EdgeRuntime": true, - "Element": true, - "ErrorEvent": true, - "Event": true, - "HTMLElement": true, - "Headers": true, - "Request": true, - "Response": true, - "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "__SENTRY_BROWSER_BUNDLE__": true, - "__SENTRY_DEBUG__": true, - "clearTimeout": true, - "console.error": true, - "document": true, - "setInterval": true, - "setTimeout": true + "define": true + } + }, + "@metamask/ppom-validator>elliptic>brorand": { + "globals": { + "crypto": true, + "msCrypto": true }, "packages": { - "process": true + "browserify>browser-resolve": true + } + }, + "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true + } + }, + "crypto-browserify>browserify-cipher": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "crypto-browserify>browserify-cipher>browserify-des": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true + } + }, + "crypto-browserify>browserify-cipher>browserify-des": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>browserify-des>des.js": true, + "pumpify>inherits": true + } + }, + "crypto-browserify>public-encrypt>browserify-rsa": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>randombytes": true + } + }, + "crypto-browserify>browserify-sign": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "@metamask/ppom-validator>elliptic": true, + "pumpify>inherits": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "stream-browserify": true + } + }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>buffer": true, + "browserify>browserify-zlib>pako": true, + "process": true, + "stream-browserify": true, + "browserify>util": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, - "@solana/addresses": { - "globals": { - "Intl.Collator": true, - "TextEncoder": true, - "crypto.subtle.digest": true, - "crypto.subtle.exportKey": true - }, + "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { - "@solana/addresses>@solana/assertions": true, - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/codecs-strings": true, - "@solana/addresses>@solana/errors": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "ethereumjs-util>create-hash": true, + "koa>content-disposition>safe-buffer": true } }, - "@solana/addresses>@solana/assertions": { + "buffer": { "globals": { - "crypto": true, - "isSecureContext": true + "console": true }, "packages": { - "@solana/addresses>@solana/errors": true + "base64-js": true, + "buffer>ieee754": true } }, - "@solana/addresses>@solana/codecs-core": { + "terser>source-map-support>buffer-from": { "packages": { - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/codecs-strings": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true - }, + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { "packages": { - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/errors": { + "browserify>buffer": { "globals": { - "btoa": true + "console": true + }, + "packages": { + "base64-js": true, + "buffer>ieee754": true } }, - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": { "packages": { - "react-markdown>unist-util-visit": true + "process": true, + "semver": true } }, - "@storybook/addon-knobs>qs": { + "string.prototype.matchall>call-bind": { "packages": { - "string.prototype.matchall>side-channel": true + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>call-bind>set-function-length": true } }, - "@swc/helpers>tslib": { + "@ngraveio/bc-ur>cbor-sync": { "globals": { - "SuppressedError": true, "define": true + }, + "packages": { + "browserify>buffer": true } }, - "@trezor/connect-web": { + "chalk": { + "packages": { + "chalk>ansi-styles": true, + "chalk>supports-color": true + } + }, + "chart.js": { "globals": { - "URLSearchParams": true, - "__TREZOR_CONNECT_SRC": true, + "Intl.NumberFormat": true, + "MutationObserver": true, + "OffscreenCanvas": true, + "Path2D": true, + "ResizeObserver": true, "addEventListener": true, - "btoa": true, - "chrome": true, - "clearInterval": true, "clearTimeout": true, + "console.error": true, "console.warn": true, - "document.body": true, - "document.createElement": true, - "document.createTextNode": true, - "document.getElementById": true, - "document.querySelectorAll": true, - "location": true, - "navigator": true, - "open": true, - "origin": true, + "devicePixelRatio": true, + "document": true, "removeEventListener": true, - "setInterval": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect": true, - "@trezor/connect-web>@trezor/connect-common": true, - "@trezor/connect-web>@trezor/utils": true, - "webpack>events": true - } - }, - "@trezor/connect-web>@trezor/connect": { - "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, - "@trezor/connect-web>@trezor/connect>@trezor/transport": true, - "@trezor/connect-web>@trezor/utils": true + "chart.js>@kurkle/color": true } }, - "@trezor/connect-web>@trezor/connect-common": { - "globals": { - "console.warn": true, - "localStorage.getItem": true, - "localStorage.setItem": true, - "navigator": true, - "setTimeout": true, - "window": true - }, + "@ensdomains/content-hash>cids": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, - "@trezor/connect-web>@trezor/utils": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>cids>multihashes": true, + "@ensdomains/content-hash>cids>uint8arrays": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { - "globals": { - "innerHeight": true, - "innerWidth": true, - "location.hostname": true, - "location.origin": true, - "navigator.languages": true, - "navigator.platform": true, - "navigator.userAgent": true, - "screen.height": true, - "screen.width": true - }, + "ethereumjs-util>create-hash>cipher-base": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, - "process": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true, + "stream-browserify": true, + "browserify>string_decoder": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "classnames": { "globals": { + "classNames": "write", "define": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { + "@metamask/jazzicon>color>clone": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, "browserify>buffer": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "cockatiel": { "globals": { - "process": true, + "AbortController": true, + "AbortSignal": true, + "WeakRef": true, + "clearTimeout": true, + "performance": true, "setTimeout": true }, "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true - } - }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { - "globals": { - "console.log": true + "process": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { - "globals": { - "XMLHttpRequest": true - }, + "chalk>ansi-styles>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { - "globals": { - "console.warn": true - }, + "@metamask/jazzicon>color>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "browserify>buffer": true, - "ts-mixer": true + "@metamask/jazzicon>color>color-convert>color-name": true } }, - "@trezor/connect-web>@trezor/utils": { - "globals": { - "AbortController": true, - "Intl.NumberFormat": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.info": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "@metamask/jazzicon>color>color-string": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/utils>bignumber.js": true, - "browserify>buffer": true, - "webpack>events": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/utils>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/jazzicon>color": { + "packages": { + "@metamask/jazzicon>color>clone": true, + "@metamask/jazzicon>color>color-convert": true, + "@metamask/jazzicon>color>color-string": true } }, - "@welldone-software/why-did-you-render": { - "globals": { - "Element": true, - "console.group": true, - "console.groupCollapsed": true, - "console.groupEnd": true, - "console.log": true, - "console.warn": true, - "define": true, - "setTimeout": true - }, + "@metamask/snaps-controllers>concat-stream": { "packages": { - "lodash": true, - "react": true + "terser>source-map-support>buffer-from": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "readable-stream": true, + "browserify>concat-stream>typedarray": true } }, - "@zxing/browser": { + "copy-to-clipboard": { "globals": { - "HTMLElement": true, - "HTMLImageElement": true, - "HTMLVideoElement": true, - "clearTimeout": true, + "clipboardData": true, "console.error": true, "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true, + "document.createRange": true, + "document.execCommand": true, + "document.getSelection": true, + "navigator.userAgent": true, + "prompt": true }, "packages": { - "@zxing/library": true + "copy-to-clipboard>toggle-selection": true } }, - "@zxing/library": { + "@ethereumjs/tx>@ethereumjs/common>crc-32": { "globals": { - "HTMLImageElement": true, - "HTMLVideoElement": true, - "TextDecoder": true, - "TextEncoder": true, - "URL.createObjectURL": true, - "btoa": true, - "console.log": true, - "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "@zxing/library>ts-custom-error": true + "DO_NOT_EXPORT_CRC": true, + "define": true } }, - "addons-linter>sha.js": { + "@ngraveio/bc-ur>crc": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "await-semaphore": { + "crypto-browserify>create-ecdh": { "packages": { - "browserify>timers-browserify": true, - "process": true + "bn.js": true, + "browserify>buffer": true, + "@metamask/ppom-validator>elliptic": true } }, - "axios>form-data": { - "globals": { - "FormData": true + "ethereumjs-util>create-hash": { + "packages": { + "ethereumjs-util>create-hash>cipher-base": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>md5.js": true, + "ethereumjs-util>create-hash>ripemd160": true, + "addons-linter>sha.js": true } }, - "base32-encode": { + "crypto-browserify>create-hmac": { "packages": { - "base32-encode>to-data-view": true + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>create-hash": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true } }, - "blo": { - "globals": { - "btoa": true + "crypto-browserify": { + "packages": { + "crypto-browserify>browserify-cipher": true, + "crypto-browserify>browserify-sign": true, + "crypto-browserify>create-ecdh": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "crypto-browserify>diffie-hellman": true, + "crypto-browserify>pbkdf2": true, + "crypto-browserify>public-encrypt": true, + "crypto-browserify>randombytes": true, + "crypto-browserify>randomfill": true } }, - "bn.js": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "Buffer": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { "browserify>browser-resolve": true } }, - "bowser": { - "globals": { - "define": true - } - }, - "browserify>assert": { + "react-beautiful-dnd>css-box-model": { "globals": { - "Buffer": true + "getComputedStyle": true, + "pageXOffset": true, + "pageYOffset": true }, "packages": { - "browserify>assert>util": true, - "react>object-assign": true + "react-router-dom>tiny-invariant": true } }, - "browserify>assert>util": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true, - "process": true + "document.createElement": true, + "document.documentElement": true, + "getComputedStyle": true }, "packages": { - "browserify>assert>util>inherits": true, - "process": true - } - }, - "browserify>browserify-zlib": { - "packages": { - "browserify>assert": true, - "browserify>browserify-zlib>pako": true, - "browserify>buffer": true, - "browserify>util": true, - "process": true, - "stream-browserify": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true } }, - "browserify>buffer": { - "globals": { - "console": true - }, + "currency-formatter": { "packages": { - "base64-js": true, - "buffer>ieee754": true - } - }, - "browserify>punycode": { - "globals": { - "define": true + "currency-formatter>accounting": true, + "currency-formatter>locale-currency": true, + "react>object-assign": true } }, - "browserify>string_decoder": { + "debounce-stream": { "packages": { - "koa>content-disposition>safe-buffer": true + "debounce-stream>debounce": true, + "debounce-stream>duplexer": true, + "debounce-stream>through": true } }, - "browserify>timers-browserify": { + "debounce-stream>debounce": { "globals": { - "clearInterval": true, "clearTimeout": true, - "setInterval": true, "setTimeout": true - }, - "packages": { - "process": true - } - }, - "browserify>url": { - "packages": { - "@storybook/addon-knobs>qs": true, - "browserify>punycode": true } }, - "browserify>util": { + "nock>debug": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "browserify>util>is-arguments": true, - "browserify>util>is-typed-array": true, - "browserify>util>which-typed-array": true, - "koa>is-generator-function": true, - "process": true, - "pumpify>inherits": true - } - }, - "browserify>util>is-arguments": { - "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "nock>debug>ms": true, + "process": true } }, - "browserify>util>is-typed-array": { + "@metamask/eth-token-tracker>deep-equal": { "packages": { + "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, + "string.prototype.matchall>call-bind": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, + "string.prototype.matchall>get-intrinsic": true, + "browserify>util>is-arguments": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true, + "@metamask/eth-token-tracker>deep-equal>is-date-object": true, + "string.prototype.matchall>es-abstract>is-regex": true, + "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "@ngraveio/bc-ur>assert>object-is": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true, + "gulp>vinyl-fs>object.assign": true, + "string.prototype.matchall>regexp.prototype.flags": true, + "string.prototype.matchall>side-channel": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, + "@metamask/eth-token-tracker>deep-equal>which-collection": true, "browserify>util>which-typed-array": true } }, - "browserify>util>which-typed-array": { + "string.prototype.matchall>define-properties>define-data-property": { "packages": { - "browserify>util>which-typed-array>for-each": true, - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>es-abstract>gopd": true } }, - "browserify>util>which-typed-array>for-each": { + "string.prototype.matchall>define-properties": { "packages": { - "string.prototype.matchall>es-abstract>is-callable": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "browserify>vm-browserify": { - "globals": { - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true + "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, - "buffer": { - "globals": { - "console": true - }, + "crypto-browserify>diffie-hellman": { "packages": { - "base64-js": true, - "buffer>ieee754": true + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>diffie-hellman>miller-rabin": true, + "crypto-browserify>randombytes": true } }, - "chalk": { + "@material-ui/core>react-transition-group>dom-helpers": { "packages": { - "chalk>ansi-styles": true, - "chalk>supports-color": true + "@babel/runtime": true } }, - "chalk>ansi-styles": { + "debounce-stream>duplexer": { "packages": { - "chalk>ansi-styles>color-convert": true + "stream-browserify": true } }, - "chalk>ansi-styles>color-convert": { + "ethers>@ethersproject/signing-key>elliptic": { "packages": { - "jest-canvas-mock>moo-color>color-name": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chart.js": { - "globals": { - "Intl.NumberFormat": true, - "MutationObserver": true, - "OffscreenCanvas": true, - "Path2D": true, - "ResizeObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.error": true, - "console.warn": true, - "devicePixelRatio": true, - "document": true, - "removeEventListener": true, - "requestAnimationFrame": true, - "setTimeout": true - }, + "@metamask/ppom-validator>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, + "eth-lattice-keyring>gridplus-sdk>elliptic": { "packages": { - "chart.js>@kurkle/color": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chart.js>@kurkle/color": { - "globals": { - "define": true + "string.prototype.matchall>call-bind>es-define-property": { + "packages": { + "string.prototype.matchall>get-intrinsic": true } }, - "classnames": { - "globals": { - "classNames": "write", - "define": true + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>has-symbols": true, + "browserify>util>is-arguments": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "eslint-plugin-react>array-includes>is-string": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "process": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "cockatiel": { + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { "globals": { - "AbortController": true, - "AbortSignal": true, - "WeakRef": true, - "clearTimeout": true, - "performance": true, - "setTimeout": true + "intToBuffer": true }, "packages": { - "process": true + "bn.js": true, + "buffer": true, + "eth-ens-namehash>js-sha3": true } }, - "copy-to-clipboard": { + "eth-ens-namehash": { "globals": { - "clipboardData": true, - "console.error": true, - "console.warn": true, - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true, - "document.createRange": true, - "document.execCommand": true, - "document.getSelection": true, - "navigator.userAgent": true, - "prompt": true + "name": "write" }, "packages": { - "copy-to-clipboard>toggle-selection": true + "browserify>buffer": true, + "eth-ens-namehash>idna-uts46-hx": true, + "eth-ens-namehash>js-sha3": true } }, - "copy-to-clipboard>toggle-selection": { + "eth-lattice-keyring": { "globals": { - "document.activeElement": true, - "document.getSelection": true + "addEventListener": true, + "browser": true, + "clearInterval": true, + "fetch": true, + "open": true, + "setInterval": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify": true, + "webpack>events": true, + "eth-lattice-keyring>gridplus-sdk": true, + "eth-lattice-keyring>rlp": true } }, - "crypto-browserify": { + "eth-method-registry": { "packages": { - "crypto-browserify>browserify-cipher": true, - "crypto-browserify>browserify-sign": true, - "crypto-browserify>create-ecdh": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>diffie-hellman": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt": true, - "crypto-browserify>randombytes": true, - "crypto-browserify>randomfill": true, - "ethereumjs-util>create-hash": true + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true } }, - "crypto-browserify>browserify-cipher": { + "@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "crypto-browserify>browserify-cipher>browserify-des": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true } }, - "crypto-browserify>browserify-cipher>browserify-des": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>browserify-des>des.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "pumpify>inherits": true + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>evp_bytestokey": { + "ethereumjs-util>ethereum-cryptography": { "packages": { - "ethereumjs-util>create-hash>md5.js": true, - "koa>content-disposition>safe-buffer": true + "browserify>buffer": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "ganache>secp256k1": true } }, - "crypto-browserify>browserify-sign": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "browserify>buffer": true, "crypto-browserify>create-hmac": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "ethereumjs-util>create-hash": true, - "pumpify>inherits": true, - "stream-browserify": true + "ethers>@ethersproject/sha2>hash.js": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true } }, - "crypto-browserify>create-ecdh": { + "ethereumjs-util": { "packages": { - "@metamask/ppom-validator>elliptic": true, + "browserify>assert": true, "bn.js": true, - "browserify>buffer": true + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>rlp": true } }, - "crypto-browserify>create-hmac": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { - "addons-linter>sha.js": true, + "browserify>assert": true, + "bn.js": true, + "browserify>buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true } }, - "crypto-browserify>diffie-hellman": { + "@metamask/keyring-controller>ethereumjs-wallet": { "packages": { - "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "browserify>buffer": true, - "crypto-browserify>diffie-hellman>miller-rabin": true, - "crypto-browserify>randombytes": true + "crypto-browserify": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, + "crypto-browserify>randombytes": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true, + "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, + "uuid": true } }, - "crypto-browserify>diffie-hellman>miller-rabin": { + "ethers": { "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "bn.js": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "ethers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>pbkdf2": { - "globals": { - "crypto": true, - "process": true, - "queueMicrotask": true, - "setImmediate": true, - "setTimeout": true - }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>public-encrypt": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, "browserify>buffer": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>create-hash": true + "eth-ens-namehash>js-sha3": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": true } }, - "crypto-browserify>public-encrypt>browserify-rsa": { - "packages": { - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "webpack>events": { + "globals": { + "console": true } }, - "crypto-browserify>public-encrypt>parse-asn1": { - "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "crypto-browserify>browserify-cipher>evp_bytestokey": { + "packages": { + "ethereumjs-util>create-hash>md5.js": true, + "koa>content-disposition>safe-buffer": true } }, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "extension-port-stream": { "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "bn.js": true, "browserify>buffer": true, - "browserify>vm-browserify": true, - "pumpify>inherits": true + "extension-port-stream>readable-stream": true } }, - "crypto-browserify>randombytes": { + "fast-json-patch": { "globals": { - "crypto": true, - "msCrypto": true + "addEventListener": true, + "clearTimeout": true, + "removeEventListener": true, + "setTimeout": true + } + }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true }, "packages": { - "koa>content-disposition>safe-buffer": true, - "process": true + "@metamask/snaps-utils>fast-xml-parser>strnum": true } }, - "crypto-browserify>randomfill": { + "@metamask/notification-services-controller>firebase": { + "packages": { + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/messaging": true + } + }, + "react-focus-lock>focus-lock": { "globals": { - "crypto": true, - "msCrypto": true + "HTMLIFrameElement": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.DOCUMENT_NODE": true, + "Node.DOCUMENT_POSITION_CONTAINED_BY": true, + "Node.DOCUMENT_POSITION_CONTAINS": true, + "Node.ELEMENT_NODE": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "setTimeout": true }, "packages": { - "crypto-browserify>randombytes": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "tslib": true } }, - "currency-formatter": { + "browserify>util>which-typed-array>for-each": { "packages": { - "currency-formatter>accounting": true, - "currency-formatter>locale-currency": true, - "react>object-assign": true + "string.prototype.matchall>es-abstract>is-callable": true } }, - "currency-formatter>accounting": { + "axios>form-data": { + "globals": { + "FormData": true + } + }, + "fuse.js": { "globals": { + "console": true, "define": true } }, - "currency-formatter>locale-currency": { + "string.prototype.matchall>get-intrinsic": { "globals": { - "countryCode": true + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true, + "depcheck>is-core-module>hasown": true } }, - "debounce-stream": { + "string.prototype.matchall>es-abstract>gopd": { "packages": { - "debounce-stream>debounce": true, - "debounce-stream>duplexer": true, - "debounce-stream>through": true + "string.prototype.matchall>get-intrinsic": true } }, - "debounce-stream>debounce": { + "eth-lattice-keyring>gridplus-sdk": { "globals": { + "AbortController": true, + "Request": true, + "URL": true, + "__values": true, + "caches": true, "clearTimeout": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "@ethersproject/abi": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "@metamask/keyring-api>bech32": true, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, + "eth-lattice-keyring>gridplus-sdk>bitwise": true, + "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>borc": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>elliptic": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "ethers>@ethersproject/sha2>hash.js": true, + "eth-ens-namehash>js-sha3": true, + "lodash": true, + "eth-lattice-keyring>rlp": true, + "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>uuid": true } }, - "debounce-stream>duplexer": { + "string.prototype.matchall>es-abstract>has-property-descriptors": { "packages": { - "stream-browserify": true + "string.prototype.matchall>call-bind>es-define-property": true } }, - "debounce-stream>through": { + "koa>is-generator-function>has-tostringtag": { "packages": { - "process": true, - "stream-browserify": true + "string.prototype.matchall>has-symbols": true } }, - "depcheck>@vue/compiler-sfc>postcss>nanoid": { - "globals": { - "crypto.getRandomValues": true + "ethereumjs-util>create-hash>md5.js>hash-base": { + "packages": { + "pumpify>inherits": true, + "readable-stream": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethers>@ethersproject/sha2>hash.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, "depcheck>is-core-module>hasown": { @@ -4221,276 +4107,257 @@ "browserify>has>function-bind": true } }, - "dependency-tree>precinct>detective-postcss>postcss>nanoid": { + "@metamask/eth-trezor-keyring>hdkey": { + "packages": { + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "crypto-browserify": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true + } + }, + "he": { "globals": { - "crypto.getRandomValues": true + "define": true } }, - "eslint-plugin-react>array-includes>is-string": { - "packages": { - "koa>is-generator-function>has-tostringtag": true + "history": { + "globals": { + "console": true, + "define": true, + "document.defaultView": true, + "document.querySelector": true } }, - "eth-ens-namehash": { + "react-router-dom>history": { "globals": { - "name": "write" + "addEventListener": true, + "confirm": true, + "document": true, + "history": true, + "location": true, + "navigator.userAgent": true, + "removeEventListener": true }, "packages": { - "@metamask/ethjs>js-sha3": true, - "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true + "react-router-dom>history>resolve-pathname": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true, + "react-router-dom>history>value-equal": true } }, - "eth-ens-namehash>idna-uts46-hx": { - "globals": { - "define": true - }, + "@metamask/ppom-validator>elliptic>hmac-drbg": { "packages": { - "browserify>punycode": true + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "eth-keyring-controller>@metamask/browser-passworder": { - "globals": { - "crypto": true + "react-redux>hoist-non-react-statics": { + "packages": { + "prop-types>react-is": true } }, - "eth-lattice-keyring": { - "globals": { - "addEventListener": true, - "browser": true, - "clearInterval": true, - "fetch": true, - "open": true, - "setInterval": true - }, + "https-browserify": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify": true, - "eth-lattice-keyring>gridplus-sdk": true, - "eth-lattice-keyring>rlp": true, - "webpack>events": true + "stream-http": true, + "browserify>url": true } }, - "eth-lattice-keyring>gridplus-sdk": { + "@metamask/notification-services-controller>firebase>@firebase/app>idb": { "globals": { - "AbortController": true, - "Request": true, - "URL": true, - "__values": true, - "caches": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "console.warn": true, - "fetch": true, - "setTimeout": true + "DOMException": true, + "IDBCursor": true, + "IDBDatabase": true, + "IDBIndex": true, + "IDBObjectStore": true, + "IDBRequest": true, + "IDBTransaction": true, + "indexedDB.deleteDatabase": true, + "indexedDB.open": true + } + }, + "eth-ens-namehash>idna-uts46-hx": { + "globals": { + "define": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-sig-util": true, - "@metamask/ethjs>js-sha3": true, - "@metamask/keyring-api>bech32": true, - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc": true, - "eth-lattice-keyring>gridplus-sdk>bs58check": true, - "eth-lattice-keyring>gridplus-sdk>secp256k1": true, - "eth-lattice-keyring>gridplus-sdk>uuid": true, - "ethers>@ethersproject/sha2>hash.js": true, - "lodash": true + "browserify>punycode": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "string.prototype.matchall>internal-slot": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "string.prototype.matchall>call-bind>es-errors": true, + "depcheck>is-core-module>hasown": true, + "string.prototype.matchall>side-channel": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "browserify>util>is-arguments": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "string.prototype.matchall>es-abstract>is-array-buffer": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { - "globals": { - "console.warn": true, - "fetch": true - }, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true } }, - "eth-lattice-keyring>gridplus-sdk>aes-js": { - "globals": { - "define": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "packages": { + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "string.prototype.matchall>es-abstract>is-callable": { "globals": { - "crypto": true, - "define": true + "document": true } }, - "eth-lattice-keyring>gridplus-sdk>borc": { - "globals": { - "console": true - }, + "@metamask/eth-token-tracker>deep-equal>is-date-object": { "packages": { - "browserify>buffer": true, - "buffer>ieee754": true, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "koa>is-generator-function": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "@material-ui/core>@material-ui/styles>jss>is-in-browser": { "globals": { - "URL": true, - "URLSearchParams": true, - "location": true, - "navigator": true + "document": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { "packages": { - "@noble/hashes": true, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "string.prototype.matchall>es-abstract>is-regex": { "packages": { - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "string.prototype.matchall>es-abstract>is-shared-array-buffer": { "packages": { - "@metamask/ppom-validator>elliptic": true + "string.prototype.matchall>call-bind": true } }, - "eth-lattice-keyring>gridplus-sdk>uuid": { - "globals": { - "crypto": true + "eslint-plugin-react>array-includes>is-string": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>rlp": { - "globals": { - "TextEncoder": true + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "packages": { + "string.prototype.matchall>has-symbols": true } }, - "eth-method-registry": { + "browserify>util>is-typed-array": { "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true + "browserify>util>which-typed-array": true } }, - "ethereumjs-util": { + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { "packages": { - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "ethereumjs-util>create-hash": { + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "globals": { + "URL": true, + "URLSearchParams": true, + "location": true + } + }, + "@ensdomains/content-hash>js-base64": { + "globals": { + "Base64": "write", + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true, + "define": true + }, "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-util>create-hash>ripemd160": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "ethereumjs-util>create-hash>cipher-base": { + "eth-ens-namehash>js-sha3": { + "globals": { + "define": true + }, "packages": { - "browserify>string_decoder": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "stream-browserify": true + "process": true } }, - "ethereumjs-util>create-hash>md5.js": { + "@ngraveio/bc-ur>jsbi": { + "globals": { + "define": true + } + }, + "@metamask/message-manager>jsonschema": { "packages": { - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>url": true } }, - "ethereumjs-util>create-hash>md5.js>hash-base": { + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "readable-stream": true + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true } }, - "ethereumjs-util>create-hash>ripemd160": { + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { + "globals": { + "CSS": true + }, "packages": { - "browserify>buffer": true, - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "pumpify>inherits": true + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography": { + "@material-ui/core>@material-ui/styles>jss-plugin-global": { "packages": { - "browserify>buffer": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ganache>secp256k1": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "@material-ui/core>@material-ui/styles>jss-plugin-nested": { "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@babel/runtime": true, + "react-router-dom>tiny-warning": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { "packages": { - "browserify>buffer": true + "@material-ui/core>@material-ui/styles>jss": true, + "react-router-dom>tiny-warning": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, - "koa>content-disposition>safe-buffer": true + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "@material-ui/core>@material-ui/styles>jss": { + "globals": { + "CSS": true, + "document.createElement": true, + "document.querySelector": true + }, "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, + "react-router-dom>tiny-warning": true } }, "ethereumjs-util>ethereum-cryptography>keccak": { @@ -4499,527 +4366,496 @@ "readable-stream": true } }, - "ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, - "ethereumjs-wallet>randombytes": { + "currency-formatter>locale-currency": { "globals": { - "crypto.getRandomValues": true + "countryCode": true } }, - "ethers": { - "packages": { - "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/web": true, - "ethers>@ethersproject/wordlists": true + "localforage": { + "globals": { + "Blob": true, + "BlobBuilder": true, + "FileReader": true, + "IDBKeyRange": true, + "MSBlobBuilder": true, + "MozBlobBuilder": true, + "OIndexedDB": true, + "WebKitBlobBuilder": true, + "atob": true, + "btoa": true, + "console.error": true, + "console.info": true, + "console.warn": true, + "define": true, + "fetch": true, + "indexedDB": true, + "localStorage": true, + "mozIndexedDB": true, + "msIndexedDB": true, + "navigator.platform": true, + "navigator.userAgent": true, + "openDatabase": true, + "setTimeout": true, + "webkitIndexedDB": true } }, - "ethers>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "lodash": { + "globals": { + "clearTimeout": true, + "define": true, + "setTimeout": true } }, - "ethers>@ethersproject/abstract-signer": { - "packages": { - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "loglevel": { + "globals": { + "console": true, + "define": true, + "document.cookie": true, + "localStorage": true, + "log": "write", + "navigator": true } }, - "ethers>@ethersproject/address": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/rlp": true + "lottie-web": { + "globals": { + "Blob": true, + "Howl": true, + "OffscreenCanvas": true, + "URL.createObjectURL": true, + "Worker": true, + "XMLHttpRequest": true, + "bodymovin": "write", + "clearInterval": true, + "console": true, + "define": true, + "document.body": true, + "document.createElement": true, + "document.createElementNS": true, + "document.getElementsByClassName": true, + "document.getElementsByTagName": true, + "document.querySelectorAll": true, + "document.readyState": true, + "location.origin": true, + "location.pathname": true, + "navigator": true, + "requestAnimationFrame": true, + "setInterval": true, + "setTimeout": true } }, - "ethers>@ethersproject/base64": { + "luxon": { "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/bytes": true + "Intl": true } }, - "ethers>@ethersproject/basex": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/properties": true + "@metamask/snaps-utils>marked": { + "globals": { + "console.error": true, + "console.warn": true, + "define": true } }, - "ethers>@ethersproject/constants": { + "ethereumjs-util>create-hash>md5.js": { "packages": { - "@ethersproject/bignumber": true + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "ethers>@ethersproject/json-wallets": { + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets>aes-js": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/pbkdf2": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/json-wallets>aes-js": { - "globals": { - "define": true + "react-markdown>remark-parse>mdast-util-from-markdown": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, + "react-syntax-highlighter>refractor>parse-entities": true, + "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true } }, - "ethers>@ethersproject/json-wallets>scrypt-js": { + "react-markdown>remark-rehype>mdast-util-to-hast": { "globals": { - "define": true, - "setTimeout": true + "console.warn": true }, "packages": { - "browserify>timers-browserify": true - } - }, - "ethers>@ethersproject/keccak256": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ethjs>js-sha3": true + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, + "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/logger": { + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { "globals": { - "console": true + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "https-browserify": true, + "process": true, + "stream-http": true, + "browserify>url": true, + "browserify>util": true } }, - "ethers>@ethersproject/pbkdf2": { + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/sha2": true + "react-syntax-highlighter>refractor>parse-entities": true } }, - "ethers>@ethersproject/properties": { + "crypto-browserify>diffie-hellman>miller-rabin": { "packages": { - "ethers>@ethersproject/logger": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true } }, - "ethers>@ethersproject/providers": { + "@ensdomains/content-hash>cids>multibase": { "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers>@ethersproject/networks": true, - "ethers>@ethersproject/providers>@ethersproject/web": true, - "ethers>@ethersproject/providers>bech32": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "ethers>@ethersproject/providers>@ethersproject/networks": { + "@ensdomains/content-hash>multihashes>multibase": { "packages": { - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "@ensdomains/content-hash>multicodec": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@ensdomains/content-hash>multicodec>uint8arrays": true, + "sass-embedded>varint": true } }, - "ethers>@ethersproject/random": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "console.warn": true, + "crypto.subtle.digest": true } }, - "ethers>@ethersproject/rlp": { + "@ensdomains/content-hash>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>multibase": true, + "@ensdomains/content-hash>multihashes>varint": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/sha2": { + "@ensdomains/content-hash>cids>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2>hash.js": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>cids>multihashes>varint": true } }, - "ethers>@ethersproject/sha2>hash.js": { - "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/signing-key": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ppom-validator>elliptic": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "@metamask/approval-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/solidity": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true + "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/strings": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true + "@metamask/notification-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/transactions": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/signing-key": true + "@metamask/permission-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/rpc-methods>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/units": { - "packages": { - "@ethersproject/bignumber": true, - "ethers>@ethersproject/logger": true + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/web": { + "@metamask/snaps-controllers>nanoid": { "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/wordlists": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@metamask/snaps-controllers-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream": { - "packages": { - "browserify>buffer": true, - "extension-port-stream>readable-stream": true + "depcheck>@vue/compiler-sfc>postcss>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream": { + "dependency-tree>precinct>detective-postcss>postcss>nanoid": { "globals": { - "AbortController": true, - "AggregateError": true, - "Blob": true - }, - "packages": { - "browserify>buffer": true, - "browserify>string_decoder": true, - "extension-port-stream>readable-stream>abort-controller": true, - "process": true, - "webpack>events": true + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream>abort-controller": { + "node-fetch": { "globals": { - "AbortController": true + "Headers": true, + "Request": true, + "Response": true, + "fetch": true } }, - "fast-json-patch": { + "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { "globals": { - "addEventListener": true, - "clearTimeout": true, - "removeEventListener": true, - "setTimeout": true + "fetch": true } }, - "fuse.js": { + "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { "globals": { - "console": true, - "define": true + "fetch": true } }, - "ganache>secp256k1": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": { "packages": { - "@metamask/ppom-validator>elliptic": true + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true + } + }, + "string.prototype.matchall>es-abstract>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@ngraveio/bc-ur>assert>object-is": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true } }, "gulp>vinyl-fs>object.assign": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, "string.prototype.matchall>call-bind": true, "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>has-symbols": true + "string.prototype.matchall>has-symbols": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "he": { - "globals": { - "define": true + "@metamask/object-multiplex>once": { + "packages": { + "@metamask/object-multiplex>once>wrappy": true } }, - "history": { + "crypto-browserify>public-encrypt>parse-asn1": { + "packages": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "browserify>buffer": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "crypto-browserify>pbkdf2": true + } + }, + "react-syntax-highlighter>refractor>parse-entities": { "globals": { - "console": true, - "define": true, - "document.defaultView": true, - "document.querySelector": true + "document.createElement": true } }, - "https-browserify": { + "path-browserify": { "packages": { - "browserify>url": true, - "stream-http": true + "process": true } }, - "koa>content-disposition>safe-buffer": { + "serve-handler>path-to-regexp": { "packages": { - "browserify>buffer": true + "serve-handler>path-to-regexp>isarray": true } }, - "koa>is-generator-function": { + "crypto-browserify>pbkdf2": { + "globals": { + "crypto": true, + "process": true, + "queueMicrotask": true, + "setImmediate": true, + "setTimeout": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true + "ethereumjs-util>create-hash": true, + "process": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "koa>is-generator-function>has-tostringtag": { - "packages": { - "string.prototype.matchall>has-symbols": true + "@material-ui/core>popper.js": { + "globals": { + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator": true, + "requestAnimationFrame": true, + "setTimeout": true } }, - "localforage": { + "react-tippy>popper.js": { "globals": { - "Blob": true, - "BlobBuilder": true, - "FileReader": true, - "IDBKeyRange": true, - "MSBlobBuilder": true, - "MozBlobBuilder": true, - "OIndexedDB": true, - "WebKitBlobBuilder": true, - "atob": true, - "btoa": true, - "console.error": true, - "console.info": true, + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, "console.warn": true, "define": true, - "fetch": true, - "indexedDB": true, - "localStorage": true, - "mozIndexedDB": true, - "msIndexedDB": true, - "navigator.platform": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, "navigator.userAgent": true, - "openDatabase": true, - "setTimeout": true, - "webkitIndexedDB": true + "requestAnimationFrame": true, + "setTimeout": true } }, - "lodash": { + "process": { "globals": { "clearTimeout": true, - "define": true, "setTimeout": true } }, - "loglevel": { + "promise-to-callback": { + "packages": { + "promise-to-callback>is-fn": true, + "promise-to-callback>set-immediate-shim": true + } + }, + "prop-types": { "globals": { - "console": true, - "define": true, - "document.cookie": true, - "localStorage": true, - "log": "write", - "navigator": true + "console": true + }, + "packages": { + "react>object-assign": true, + "prop-types>react-is": true } }, - "lottie-web": { + "react-markdown>property-information": { + "packages": { + "watchify>xtend": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { "globals": { - "Blob": true, - "Howl": true, - "OffscreenCanvas": true, - "URL.createObjectURL": true, - "Worker": true, - "XMLHttpRequest": true, - "bodymovin": "write", - "clearInterval": true, - "console": true, - "define": true, - "document.body": true, - "document.createElement": true, - "document.createElementNS": true, - "document.getElementsByClassName": true, - "document.getElementsByTagName": true, - "document.querySelectorAll": true, - "document.readyState": true, - "location.origin": true, - "location.pathname": true, - "navigator": true, - "requestAnimationFrame": true, - "setInterval": true, + "process": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true } }, - "luxon": { - "globals": { - "Intl": true + "crypto-browserify>public-encrypt": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "crypto-browserify>randombytes": true } }, - "nanoid": { + "browserify>punycode": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "define": true } }, - "nock>debug": { + "qrcode-generator": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "nock>debug>ms": true, - "process": true + "define": true } }, - "node-fetch": { + "qrcode.react": { "globals": { - "Headers": true, - "Request": true, - "Response": true, - "fetch": true + "Path2D": true, + "devicePixelRatio": true + }, + "packages": { + "react": true } }, - "path-browserify": { + "@storybook/addon-knobs>qs": { "packages": { - "process": true + "string.prototype.matchall>side-channel": true } }, - "process": { + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { "globals": { - "clearTimeout": true, - "setTimeout": true - } - }, - "promise-to-callback": { - "packages": { - "promise-to-callback>is-fn": true, - "promise-to-callback>set-immediate-shim": true + "queueMicrotask": true } }, - "promise-to-callback>set-immediate-shim": { + "react-beautiful-dnd>raf-schd": { "globals": { - "setTimeout.apply": true - }, - "packages": { - "browserify>timers-browserify": true + "cancelAnimationFrame": true, + "requestAnimationFrame": true } }, - "prop-types": { + "crypto-browserify>randombytes": { "globals": { - "console": true + "crypto": true, + "msCrypto": true }, "packages": { - "prop-types>react-is": true, - "react>object-assign": true - } - }, - "prop-types>react-is": { - "globals": { - "console": true + "process": true, + "koa>content-disposition>safe-buffer": true } }, - "qrcode-generator": { + "ethereumjs-wallet>randombytes": { "globals": { - "define": true + "crypto.getRandomValues": true } }, - "qrcode.react": { + "crypto-browserify>randomfill": { "globals": { - "Path2D": true, - "devicePixelRatio": true + "crypto": true, + "msCrypto": true }, "packages": { - "react": true + "process": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true } }, "react": { @@ -5027,8 +4863,8 @@ "console": true }, "packages": { - "prop-types": true, - "react>object-assign": true + "react>object-assign": true, + "prop-types": true } }, "react-beautiful-dnd": { @@ -5050,43 +4886,28 @@ }, "packages": { "@babel/runtime": true, - "react": true, "react-beautiful-dnd>css-box-model": true, "react-beautiful-dnd>memoize-one": true, "react-beautiful-dnd>raf-schd": true, - "react-beautiful-dnd>use-memo-one": true, + "react": true, "react-dom": true, "react-redux": true, - "redux": true + "redux": true, + "react-beautiful-dnd>use-memo-one": true } }, - "react-beautiful-dnd>css-box-model": { + "react-chartjs-2": { "globals": { - "getComputedStyle": true, - "pageXOffset": true, - "pageYOffset": true + "setTimeout": true }, "packages": { - "react-router-dom>tiny-invariant": true - } - }, - "react-beautiful-dnd>raf-schd": { - "globals": { - "cancelAnimationFrame": true, - "requestAnimationFrame": true - } - }, - "react-beautiful-dnd>use-memo-one": { - "packages": { + "chart.js": true, "react": true } }, - "react-chartjs-2": { - "globals": { - "setTimeout": true - }, + "react-focus-lock>react-clientside-effect": { "packages": { - "chart.js": true, + "@babel/runtime": true, "react": true } }, @@ -5131,22 +4952,28 @@ "trustedTypes": true }, "packages": { + "react>object-assign": true, "prop-types": true, "react": true, - "react-dom>scheduler": true, - "react>object-assign": true + "react-dom>scheduler": true } }, - "react-dom>scheduler": { + "react-responsive-carousel>react-easy-swipe": { "globals": { - "MessageChannel": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "console": true, - "navigator": true, - "performance": true, - "requestAnimationFrame": true, - "setTimeout": true + "addEventListener": true, + "define": true, + "document.addEventListener": true, + "document.removeEventListener": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-popper>react-fast-compare": { + "globals": { + "Element": true, + "console.warn": true } }, "react-focus-lock": { @@ -5160,666 +4987,726 @@ }, "packages": { "@babel/runtime": true, + "react-focus-lock>focus-lock": true, "prop-types": true, "react": true, - "react-focus-lock>focus-lock": true, "react-focus-lock>react-clientside-effect": true, "react-focus-lock>use-callback-ref": true, "react-focus-lock>use-sidecar": true } }, - "react-focus-lock>focus-lock": { + "react-idle-timer": { "globals": { - "HTMLIFrameElement": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.DOCUMENT_NODE": true, - "Node.DOCUMENT_POSITION_CONTAINED_BY": true, - "Node.DOCUMENT_POSITION_CONTAINS": true, - "Node.ELEMENT_NODE": true, - "console.error": true, + "clearTimeout": true, + "document": true, + "setTimeout": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-inspector": { + "globals": { + "Node": true, + "chromeDark": true, + "chromeLight": true + }, + "packages": { + "react": true + } + }, + "prop-types>react-is": { + "globals": { + "console": true + } + }, + "react-markdown>react-is": { + "globals": { + "console": true + } + }, + "react-redux>react-is": { + "globals": { + "console": true + } + }, + "react-markdown": { + "globals": { + "console.warn": true + }, + "packages": { + "react-markdown>comma-separated-tokens": true, + "prop-types": true, + "react-markdown>property-information": true, + "react": true, + "react-markdown>react-is": true, + "react-markdown>remark-parse": true, + "react-markdown>remark-rehype": true, + "react-markdown>space-separated-tokens": true, + "react-markdown>style-to-object": true, + "react-markdown>unified": true, + "react-markdown>unist-util-visit": true, + "react-markdown>vfile": true + } + }, + "react-popper": { + "globals": { + "document": true + }, + "packages": { + "@popperjs/core": true, + "react": true, + "react-popper>react-fast-compare": true, + "react-popper>warning": true + } + }, + "react-redux": { + "globals": { + "console": true, + "document": true + }, + "packages": { + "@babel/runtime": true, + "react-redux>hoist-non-react-statics": true, + "prop-types": true, + "react": true, + "react-dom": true, + "react-redux>react-is": true + } + }, + "react-responsive-carousel": { + "globals": { + "HTMLElement": true, + "addEventListener": true, + "clearTimeout": true, "console.warn": true, "document": true, - "getComputedStyle": true, + "getComputedStyle": true, + "removeEventListener": true, + "setTimeout": true + }, + "packages": { + "classnames": true, + "react": true, + "react-dom": true, + "react-responsive-carousel>react-easy-swipe": true + } + }, + "react-router-dom": { + "packages": { + "react-router-dom>history": true, + "prop-types": true, + "react": true, + "react-router-dom>react-router": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom-v5-compat": { + "globals": { + "FormData": true, + "URL": true, + "URLSearchParams": true, + "__reactRouterVersion": "write", + "addEventListener": true, + "confirm": true, + "define": true, + "document": true, + "history.scrollRestoration": true, + "location.href": true, + "removeEventListener": true, + "scrollTo": true, + "scrollY": true, + "sessionStorage.getItem": true, + "sessionStorage.setItem": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true + "react-router-dom-v5-compat>@remix-run/router": true, + "history": true, + "react": true, + "react-dom": true, + "react-router-dom": true, + "react-router-dom-v5-compat>react-router": true } }, - "react-focus-lock>react-clientside-effect": { + "react-router-dom>react-router": { "packages": { - "@babel/runtime": true, - "react": true + "react-router-dom>history": true, + "react-redux>hoist-non-react-statics": true, + "serve-handler>path-to-regexp": true, + "prop-types": true, + "react": true, + "prop-types>react-is": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true } }, - "react-focus-lock>use-callback-ref": { + "react-router-dom-v5-compat>react-router": { + "globals": { + "console.error": true, + "define": true + }, "packages": { + "react-router-dom-v5-compat>@remix-run/router": true, "react": true } }, - "react-focus-lock>use-sidecar": { + "react-simple-file-input": { "globals": { - "console.error": true + "File": true, + "FileReader": true, + "console.warn": true }, "packages": { - "@swc/helpers>tslib": true, - "react": true, - "react-focus-lock>use-sidecar>detect-node-es": true + "prop-types": true, + "react": true } }, - "react-idle-timer": { + "react-tippy": { "globals": { + "Element": true, + "MSStream": true, + "MutationObserver": true, + "addEventListener": true, "clearTimeout": true, + "console.error": true, + "console.warn": true, + "define": true, "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.maxTouchPoints": true, + "navigator.msMaxTouchPoints": true, + "navigator.userAgent": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "react-tippy>popper.js": true, + "react": true, + "react-dom": true } }, - "react-inspector": { + "react-toggle-button": { "globals": { - "Node": true, - "chromeDark": true, - "chromeLight": true + "clearTimeout": true, + "console.warn": true, + "define": true, + "performance": true, + "setTimeout": true }, "packages": { "react": true } }, - "react-markdown": { + "@material-ui/core>react-transition-group": { "globals": { - "console.warn": true + "Element": true, + "setTimeout": true }, "packages": { + "@material-ui/core>react-transition-group>dom-helpers": true, "prop-types": true, "react": true, - "react-markdown>comma-separated-tokens": true, - "react-markdown>property-information": true, - "react-markdown>react-is": true, - "react-markdown>remark-parse": true, - "react-markdown>remark-rehype": true, - "react-markdown>space-separated-tokens": true, - "react-markdown>style-to-object": true, - "react-markdown>unified": true, - "react-markdown>unist-util-visit": true, - "react-markdown>vfile": true + "react-dom": true } }, - "react-markdown>property-information": { + "readable-stream": { "packages": { - "watchify>xtend": true + "browserify>browser-resolve": true, + "browserify>buffer": true, + "webpack>events": true, + "pumpify>inherits": true, + "process": true, + "browserify>string_decoder": true, + "readable-stream>util-deprecate": true } }, - "react-markdown>react-is": { + "extension-port-stream>readable-stream": { "globals": { - "console": true - } - }, - "react-markdown>remark-parse": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, - "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true, - "react-syntax-highlighter>refractor>parse-entities": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { + "AbortController": true, + "AbortSignal": true, + "AggregateError": true, + "Blob": true, + "ERR_INVALID_ARG_TYPE": true, + "queueMicrotask": true + }, "packages": { - "react-syntax-highlighter>refractor>parse-entities": true + "@lavamoat/lavapack>readable-stream>abort-controller": true, + "browserify>buffer": true, + "webpack>events": true, + "process": true, + "browserify>string_decoder": true } }, - "react-markdown>remark-rehype": { + "@metamask/snaps-controllers>readable-web-to-node-stream": { "packages": { - "react-markdown>remark-rehype>mdast-util-to-hast": true + "readable-stream": true } }, - "react-markdown>remark-rehype>mdast-util-to-hast": { + "redux": { "globals": { - "console.warn": true + "console": true }, "packages": { - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, - "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, - "react-markdown>unist-util-visit": true + "@babel/runtime": true } }, - "react-markdown>style-to-object": { + "string.prototype.matchall>regexp.prototype.flags": { "packages": { - "react-markdown>style-to-object>inline-style-parser": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": true } }, - "react-markdown>unified": { + "react-markdown>remark-parse": { "packages": { - "mocha>yargs-unparser>is-plain-obj": true, - "react-markdown>unified>bail": true, - "react-markdown>unified>extend": true, - "react-markdown>unified>is-buffer": true, - "react-markdown>unified>trough": true, - "react-markdown>vfile": true + "react-markdown>remark-parse>mdast-util-from-markdown": true } }, - "react-markdown>unist-util-visit": { + "react-markdown>remark-rehype": { "packages": { - "react-markdown>unist-util-visit>unist-util-visit-parents": true + "react-markdown>remark-rehype>mdast-util-to-hast": true } }, - "react-markdown>unist-util-visit>unist-util-visit-parents": { + "react-markdown>vfile>replace-ext": { "packages": { - "react-markdown>unist-util-visit>unist-util-is": true + "path-browserify": true } }, - "react-markdown>vfile": { - "packages": { - "path-browserify": true, - "process": true, - "react-markdown>vfile>is-buffer": true, - "react-markdown>vfile>replace-ext": true, - "react-markdown>vfile>vfile-message": true + "reselect": { + "globals": { + "WeakRef": true, + "console.warn": true, + "unstable_autotrackMemoize": true } }, - "react-markdown>vfile>replace-ext": { + "@metamask/snaps-utils>rfdc": { "packages": { - "path-browserify": true + "browserify>buffer": true } }, - "react-markdown>vfile>vfile-message": { + "ethereumjs-util>create-hash>ripemd160": { "packages": { - "react-markdown>vfile>unist-util-stringify-position": true + "browserify>buffer": true, + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true } }, - "react-popper": { - "globals": { - "document": true - }, + "@keystonehq/metamask-airgapped-keyring>rlp": { "packages": { - "@popperjs/core": true, - "react": true, - "react-popper>react-fast-compare": true, - "react-popper>warning": true - } - }, - "react-popper>react-fast-compare": { - "globals": { - "Element": true, - "console.warn": true + "bn.js": true, + "browserify>buffer": true } }, - "react-popper>warning": { + "eth-lattice-keyring>rlp": { "globals": { - "console": true + "TextEncoder": true } }, - "react-redux": { - "globals": { - "console": true, - "document": true - }, + "ethereumjs-util>rlp": { "packages": { - "@babel/runtime": true, - "prop-types": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true, - "react-redux>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>hoist-non-react-statics": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { "packages": { - "prop-types>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>react-is": { + "wait-on>rxjs": { "globals": { - "console": true + "cancelAnimationFrame": true, + "clearInterval": true, + "clearTimeout": true, + "performance": true, + "requestAnimationFrame": true, + "setInterval.apply": true, + "setTimeout.apply": true } }, - "react-responsive-carousel": { + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, + "react-dom>scheduler": { "globals": { - "HTMLElement": true, - "addEventListener": true, + "MessageChannel": true, + "cancelAnimationFrame": true, "clearTimeout": true, - "console.warn": true, - "document": true, - "getComputedStyle": true, - "removeEventListener": true, + "console": true, + "navigator": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true - }, - "packages": { - "classnames": true, - "react": true, - "react-dom": true, - "react-responsive-carousel>react-easy-swipe": true } }, - "react-responsive-carousel>react-easy-swipe": { + "ethers>@ethersproject/json-wallets>scrypt-js": { "globals": { - "addEventListener": true, "define": true, - "document.addEventListener": true, - "document.removeEventListener": true + "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "browserify>timers-browserify": true } }, - "react-router-dom": { + "ganache>secp256k1": { "packages": { - "prop-types": true, - "react": true, - "react-router-dom>history": true, - "react-router-dom>react-router": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "@metamask/ppom-validator>elliptic": true } }, - "react-router-dom-v5-compat": { + "semver": { "globals": { - "FormData": true, - "URL": true, - "URLSearchParams": true, - "__reactRouterVersion": "write", - "addEventListener": true, - "confirm": true, - "define": true, - "document": true, - "history.scrollRestoration": true, - "location.href": true, - "removeEventListener": true, - "scrollTo": true, - "scrollY": true, - "sessionStorage.getItem": true, - "sessionStorage.setItem": true, - "setTimeout": true + "console.error": true }, "packages": { - "history": true, - "react": true, - "react-dom": true, - "react-router-dom": true, - "react-router-dom-v5-compat>@remix-run/router": true, - "react-router-dom-v5-compat>react-router": true + "process": true } }, - "react-router-dom-v5-compat>@remix-run/router": { - "globals": { - "AbortController": true, - "DOMException": true, - "FormData": true, - "Headers": true, - "Request": true, - "Response": true, - "URL": true, - "URLSearchParams": true, - "console": true, - "document.defaultView": true + "string.prototype.matchall>call-bind>set-function-length": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>gopd": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true } }, - "react-router-dom-v5-compat>react-router": { - "globals": { - "console.error": true, - "define": true - }, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": { "packages": { - "react": true, - "react-router-dom-v5-compat>@remix-run/router": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true } }, - "react-router-dom>history": { + "promise-to-callback>set-immediate-shim": { "globals": { - "addEventListener": true, - "confirm": true, - "document": true, - "history": true, - "location": true, - "navigator.userAgent": true, - "removeEventListener": true + "setTimeout.apply": true }, "packages": { - "react-router-dom>history>resolve-pathname": true, - "react-router-dom>history>value-equal": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "browserify>timers-browserify": true } }, - "react-router-dom>react-router": { + "addons-linter>sha.js": { "packages": { - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-redux>hoist-non-react-statics": true, - "react-router-dom>history": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true, - "serve-handler>path-to-regexp": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "react-router-dom>tiny-warning": { - "globals": { - "console": true + "string.prototype.matchall>side-channel": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>object-inspect": true } }, - "react-simple-file-input": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "File": true, - "FileReader": true, + "console.error": true, "console.warn": true }, "packages": { - "prop-types": true, - "react": true + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "ethers": true, + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true } }, - "react-syntax-highlighter>refractor>parse-entities": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { "globals": { - "document.createElement": true + "StopIteration": true + }, + "packages": { + "string.prototype.matchall>internal-slot": true } }, - "react-tippy": { + "stream-browserify": { + "packages": { + "webpack>events": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "stream-http": { "globals": { - "Element": true, - "MSStream": true, - "MutationObserver": true, - "addEventListener": true, + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, "clearTimeout": true, - "console.error": true, - "console.warn": true, - "define": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.maxTouchPoints": true, - "navigator.msMaxTouchPoints": true, - "navigator.userAgent": true, - "performance": true, - "requestAnimationFrame": true, + "fetch": true, + "location.protocol.search": true, "setTimeout": true }, "packages": { - "react": true, - "react-dom": true, - "react-tippy>popper.js": true + "browserify>buffer": true, + "stream-http>builtin-status-codes": true, + "pumpify>inherits": true, + "process": true, + "readable-stream": true, + "browserify>url": true, + "watchify>xtend": true } }, - "react-tippy>popper.js": { - "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.userAgent": true, - "requestAnimationFrame": true, - "setTimeout": true + "@metamask/snaps-controllers>tar-stream>streamx": { + "packages": { + "webpack>events": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true } }, - "react-toggle-button": { - "globals": { - "clearTimeout": true, - "console.warn": true, - "define": true, - "performance": true, - "setTimeout": true - }, + "browserify>string_decoder": { "packages": { - "react": true + "koa>content-disposition>safe-buffer": true } }, - "readable-stream": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": { "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>string_decoder": true, - "process": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true } }, - "readable-stream>util-deprecate": { - "globals": { - "console.trace": true, - "console.warn": true, - "localStorage": true + "react-markdown>style-to-object": { + "packages": { + "react-markdown>style-to-object>inline-style-parser": true } }, - "redux": { - "globals": { - "console": true - }, + "@metamask/snaps-controllers>tar-stream": { "packages": { - "@babel/runtime": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "browserify>browser-resolve": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true } }, - "semver": { + "debounce-stream>through": { + "packages": { + "process": true, + "stream-browserify": true + } + }, + "browserify>timers-browserify": { "globals": { - "console.error": true + "clearInterval": true, + "clearTimeout": true, + "setInterval": true, + "setTimeout": true }, "packages": { "process": true } }, - "serve-handler>path-to-regexp": { - "packages": { - "serve-handler>path-to-regexp>isarray": true + "react-router-dom>tiny-warning": { + "globals": { + "console": true } }, - "stream-browserify": { - "packages": { - "pumpify>inherits": true, - "readable-stream": true, - "webpack>events": true + "copy-to-clipboard>toggle-selection": { + "globals": { + "document.activeElement": true, + "document.getSelection": true + } + }, + "tslib": { + "globals": { + "SuppressedError": true, + "define": true } }, - "stream-http": { + "@metamask/eth-sig-util>tweetnacl": { "globals": { - "AbortController": true, - "Blob": true, - "MSStreamReader": true, - "ReadableStream": true, - "WritableStream": true, - "XDomainRequest": true, - "XMLHttpRequest": true, - "clearTimeout": true, - "fetch": true, - "location.protocol.search": true, - "setTimeout": true + "crypto": true, + "msCrypto": true, + "nacl": "write" }, "packages": { - "browserify>buffer": true, - "browserify>url": true, - "process": true, - "pumpify>inherits": true, - "readable-stream": true, - "stream-http>builtin-status-codes": true, - "watchify>xtend": true + "browserify>browser-resolve": true } }, - "string.prototype.matchall>call-bind": { - "packages": { - "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>call-bind>set-function-length": true, - "string.prototype.matchall>get-intrinsic": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>call-bind>es-define-property": { + "@ensdomains/content-hash>cids>uint8arrays": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>cids>multibase": true } }, - "string.prototype.matchall>call-bind>set-function-length": { + "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>gopd": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true, - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "string.prototype.matchall>define-properties": { + "react-markdown>unified": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true } }, - "string.prototype.matchall>define-properties>define-data-property": { + "react-markdown>unist-util-visit>unist-util-visit-parents": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true + "react-markdown>unist-util-visit>unist-util-is": true } }, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "react-markdown>unist-util-visit": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true + "react-markdown>unist-util-visit>unist-util-visit-parents": true } }, - "string.prototype.matchall>es-abstract>available-typed-arrays": { - "packages": { - "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true + "uri-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "browserify>url": { "packages": { - "string.prototype.matchall>has-symbols": true + "browserify>punycode": true, + "@storybook/addon-knobs>qs": true } }, - "string.prototype.matchall>es-abstract>gopd": { + "react-focus-lock>use-callback-ref": { "packages": { - "string.prototype.matchall>get-intrinsic": true + "react": true } }, - "string.prototype.matchall>es-abstract>has-property-descriptors": { + "react-beautiful-dnd>use-memo-one": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true + "react": true } }, - "string.prototype.matchall>es-abstract>is-array-buffer": { + "react-focus-lock>use-sidecar": { + "globals": { + "console.error": true + }, "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "react-focus-lock>use-sidecar>detect-node-es": true, + "react": true, + "tslib": true } }, - "string.prototype.matchall>es-abstract>is-callable": { + "readable-stream>util-deprecate": { "globals": { - "document": true + "console.trace": true, + "console.warn": true, + "localStorage": true } }, - "string.prototype.matchall>es-abstract>is-regex": { + "browserify>assert>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true, + "process": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "browserify>assert>util>inherits": true, + "process": true } }, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": { + "browserify>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true + }, "packages": { - "string.prototype.matchall>call-bind": true + "pumpify>inherits": true, + "browserify>util>is-arguments": true, + "koa>is-generator-function": true, + "browserify>util>is-typed-array": true, + "process": true, + "browserify>util>which-typed-array": true } }, - "string.prototype.matchall>es-abstract>object-inspect": { + "uuid": { "globals": { - "HTMLElement": true, - "WeakRef": true - }, - "packages": { - "browserify>browser-resolve": true + "crypto": true, + "msCrypto": true } }, - "string.prototype.matchall>get-intrinsic": { + "@metamask/eth-snap-keyring>uuid": { "globals": { - "AggregateError": true, - "FinalizationRegistry": true, - "WeakRef": true - }, - "packages": { - "browserify>has>function-bind": true, - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>has-proto": true, - "string.prototype.matchall>has-symbols": true + "crypto": true } }, - "string.prototype.matchall>internal-slot": { - "packages": { - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>side-channel": true + "@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": true + "eth-lattice-keyring>gridplus-sdk>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": { - "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "web3-stream-provider>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>side-channel": { + "@metamask/snaps-utils>validate-npm-package-name": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>object-inspect": true, - "string.prototype.matchall>get-intrinsic": true + "@metamask/snaps-utils>validate-npm-package-name>builtins": true } }, - "superstruct": { - "globals": { - "console.warn": true, - "define": true + "react-markdown>vfile>vfile-message": { + "packages": { + "react-markdown>vfile>unist-util-stringify-position": true } }, - "terser>source-map-support>buffer-from": { + "react-markdown>vfile": { "packages": { - "browserify>buffer": true + "react-markdown>vfile>is-buffer": true, + "path-browserify": true, + "process": true, + "react-markdown>vfile>replace-ext": true, + "react-markdown>vfile>vfile-message": true } }, - "uri-js": { + "browserify>vm-browserify": { "globals": { - "define": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true } }, - "uuid": { + "react-popper>warning": { "globals": { - "crypto": true, - "msCrypto": true + "console": true } }, - "wait-on>rxjs": { + "@ensdomains/content-hash>multihashes>web-encoding": { "globals": { - "cancelAnimationFrame": true, - "clearInterval": true, - "clearTimeout": true, - "performance": true, - "requestAnimationFrame": true, - "setInterval.apply": true, - "setTimeout.apply": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>util": true } }, "web3": { @@ -5832,14 +5719,14 @@ "setTimeout": true }, "packages": { - "browserify>util": true, "readable-stream": true, + "browserify>util": true, "web3-stream-provider>uuid": true } }, - "web3-stream-provider>uuid": { + "@metamask/controllers>web3": { "globals": { - "crypto": true + "XMLHttpRequest": true } }, "webextension-polyfill": { @@ -5851,9 +5738,30 @@ "define": true } }, - "webpack>events": { - "globals": { - "console": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, + "eslint-plugin-react>array-includes>is-string": true, + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + } + }, + "@metamask/eth-token-tracker>deep-equal>which-collection": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + } + }, + "browserify>util>which-typed-array": { + "packages": { + "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind": true, + "browserify>util>which-typed-array>for-each": true, + "string.prototype.matchall>es-abstract>gopd": true, + "koa>is-generator-function>has-tostringtag": true } } } diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 30456a0bd61d..e029e41783d0 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -5,144 +5,124 @@ "regeneratorRuntime": "write" } }, - "@ensdomains/content-hash": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { "globals": { - "console.warn": true + "WeakRef": true }, "packages": { - "@ensdomains/content-hash>cids": true, - "@ensdomains/content-hash>js-base64": true, - "@ensdomains/content-hash>multicodec": true, - "@ensdomains/content-hash>multihashes": true, - "browserify>buffer": true + "browserify": true } }, - "@ensdomains/content-hash>cids": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec": true + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true } }, - "@ensdomains/content-hash>cids>multibase": { + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "SuppressedError": true + } + }, + "@ensdomains/content-hash": { + "globals": { + "console.warn": true }, "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true + "browserify>buffer": true, + "@ensdomains/content-hash>cids": true, + "@ensdomains/content-hash>js-base64": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>multihashes": true } }, - "@ensdomains/content-hash>cids>multihashes": { + "@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>cids>uint8arrays": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true } }, - "@ensdomains/content-hash>js-base64": { - "globals": { - "Base64": "write", - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true, - "define": true - }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays": true, - "sass-embedded>varint": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays": { + "@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "Buffer": true, - "TextDecoder": true, "TextEncoder": true - }, - "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "TextDecoder": true, - "TextEncoder": true, - "console.warn": true, - "crypto.subtle.digest": true - } - }, - "@ensdomains/content-hash>multihashes": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase": true, - "@ensdomains/content-hash>multihashes>varint": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase>base-x": { + "@ethereumjs/tx": { "packages": { - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, - "@ensdomains/content-hash>multihashes>web-encoding": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { - "browserify>util": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@ethereumjs/tx": { + "eth-lattice-keyring>@ethereumjs/tx": { "packages": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethersproject/providers": true, "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true } }, - "@ethereumjs/tx>@ethereumjs/common": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, "browserify>buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/common>crc-32": { - "globals": { - "DO_NOT_EXPORT_CRC": true, - "define": true - } - }, - "@ethereumjs/tx>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -150,88 +130,84 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true, "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { - "Headers": true, - "TextDecoder": true, - "URL": true, - "btoa": true, + "console.warn": true, "fetch": true }, "packages": { - "browserify>browserify-zlib": true, - "browserify>buffer": true, - "browserify>url": true, - "browserify>util": true, - "https-browserify": true, - "process": true, - "stream-http": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true } }, - "@ethereumjs/tx>ethereum-cryptography": { + "@ethersproject/abi": { "globals": { - "TextDecoder": true, - "crypto": true + "console.log": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { + "ethers>@ethersproject/abstract-signer": { "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/address": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/rlp": true } }, - "@ethersproject/abi": { + "ethers>@ethersproject/base64": { "globals": { - "console.log": true + "atob": true, + "btoa": true }, "packages": { - "@ethersproject/bignumber": true, + "@ethersproject/bytes": true + } + }, + "ethers>@ethersproject/basex": { + "packages": { "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "ethers>@ethersproject/properties": true } }, "@ethersproject/bignumber": { "packages": { "@ethersproject/bytes": true, - "bn.js": true, - "ethers>@ethersproject/logger": true + "ethers>@ethersproject/logger": true, + "bn.js": true } }, "@ethersproject/bytes": { @@ -239,17 +215,22 @@ "ethers>@ethersproject/logger": true } }, + "ethers>@ethersproject/constants": { + "packages": { + "@ethersproject/bignumber": true + } + }, "@ethersproject/contracts": { "globals": { "setTimeout": true }, "packages": { "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/abstract-provider": true, "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/transactions": true @@ -257,10 +238,10 @@ }, "@ethersproject/hash": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/address": true, "ethers>@ethersproject/base64": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, @@ -269,9 +250,9 @@ }, "@ethersproject/hdnode": { "packages": { + "ethers>@ethersproject/basex": true, "@ethersproject/bignumber": true, "@ethersproject/bytes": true, - "ethers>@ethersproject/basex": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, @@ -282,1147 +263,1033 @@ "ethers>@ethersproject/wordlists": true } }, - "@ethersproject/providers": { - "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "ethers>@ethersproject/json-wallets": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/networks": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true - } - }, - "@ethersproject/providers>@ethersproject/random": { - "globals": { - "crypto.getRandomValues": true + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/json-wallets>aes-js": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true } }, - "@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "ethers>@ethersproject/keccak256": { "packages": { "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "eth-ens-namehash>js-sha3": true } }, - "@ethersproject/wallet": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/transactions": true + "ethers>@ethersproject/logger": { + "globals": { + "console": true } }, - "@keystonehq/bc-ur-registry-eth": { + "ethers>@ethersproject/providers>@ethersproject/networks": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { - "globals": { - "define": true - }, + "@metamask/test-bundler>@ethersproject/networks": { "packages": { - "@ngraveio/bc-ur": true, - "@swc/helpers>tslib": true, - "browserify>buffer": true, - "buffer": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/metamask-airgapped-keyring": { + "ethers>@ethersproject/pbkdf2": { "packages": { - "@ethereumjs/tx": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, - "@metamask/obs-store": true, - "browserify>buffer": true, - "ethereumjs-util>rlp": true, - "uuid": true, - "webpack>events": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/sha2": true } }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { + "ethers>@ethersproject/properties": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { - "globals": { - "TextEncoder": true + "ethers>@ethersproject/logger": true } }, - "@lavamoat/lavadome-react": { + "@ethersproject/providers": { "globals": { - "Document.prototype": true, - "DocumentFragment.prototype": true, - "Element.prototype": true, - "Node.prototype": true, + "WebSocket": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, "console.warn": true, - "document": true + "setInterval": true, + "setTimeout": true }, "packages": { - "react": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "@metamask/test-bundler>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/providers>bech32": true } }, - "@material-ui/core": { + "ethers>@ethersproject/providers": { "globals": { - "Image": true, - "_formatMuiErrorMessage": true, - "addEventListener": true, + "WebSocket": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "getComputedStyle": true, - "getSelection": true, - "innerHeight": true, - "innerWidth": true, - "matchMedia": true, - "navigator": true, - "performance.now": true, - "removeEventListener": true, - "requestAnimationFrame": true, "setInterval": true, "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles": true, - "@material-ui/core>@material-ui/system": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "@material-ui/core>popper.js": true, - "@material-ui/core>react-transition-group": true, - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/providers>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/providers>bech32": true } }, - "@material-ui/core>@material-ui/styles": { + "@ethersproject/providers>@ethersproject/random": { "globals": { - "console.error": true, - "console.warn": true, - "document.createComment": true, - "document.head": true - }, - "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, - "@material-ui/core>@material-ui/styles>jss-plugin-global": true, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, - "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "prop-types": true, - "react": true, - "react-redux>hoist-non-react-statics": true + "crypto.getRandomValues": true } }, - "@material-ui/core>@material-ui/styles>jss": { - "globals": { - "CSS": true, - "document.createElement": true, - "document.querySelector": true - }, + "ethers>@ethersproject/random": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { + "ethers>@ethersproject/rlp": { "packages": { - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { - "globals": { - "CSS": true - }, + "ethers>@ethersproject/sha2": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2>hash.js": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-global": { + "ethers>@ethersproject/signing-key": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/signing-key>elliptic": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": { + "ethers>@ethersproject/solidity": { "packages": { - "@babel/runtime": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { + "ethers>@ethersproject/strings": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { + "ethers>@ethersproject/transactions": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/signing-key": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { - "globals": { - "document.createElement": true, - "document.documentElement": true, - "getComputedStyle": true - }, + "ethers>@ethersproject/units": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true + "@ethersproject/bignumber": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": { - "globals": { - "document": true + "@ethersproject/wallet": { + "packages": { + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/transactions": true } }, - "@material-ui/core>@material-ui/system": { + "@ethersproject/providers>@ethersproject/web": { "globals": { - "console.error": true + "clearTimeout": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/utils": true, - "prop-types": true - } - }, - "@material-ui/core>@material-ui/utils": { - "packages": { - "@babel/runtime": true, - "prop-types": true, - "prop-types>react-is": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>popper.js": { + "ethers>@ethersproject/providers>@ethersproject/web": { "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator": true, - "requestAnimationFrame": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>react-transition-group": { + "ethers>@ethersproject/web": { "globals": { - "Element": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true }, "packages": { - "@material-ui/core>react-transition-group>dom-helpers": true, - "prop-types": true, - "react": true, - "react-dom": true - } - }, - "@material-ui/core>react-transition-group>dom-helpers": { - "packages": { - "@babel/runtime": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask/abi-utils": { + "ethers>@ethersproject/wordlists": { "packages": { - "@metamask/abi-utils>@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask/abi-utils>@metamask/utils": { + "@metamask/notification-services-controller>firebase>@firebase/app": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "FinalizationRegistry": true, + "console.warn": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/accounts-controller": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/eth-snap-keyring": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/utils": true, - "uuid": true - } - }, - "@metamask/address-book-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask/announcement-controller": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { "packages": { - "@metamask/announcement-controller>@metamask/base-controller": true + "@metamask/notification-services-controller>firebase>@firebase/util": true } }, - "@metamask/announcement-controller>@metamask/base-controller": { + "@metamask/notification-services-controller>firebase>@firebase/installations": { "globals": { + "BroadcastChannel": true, + "Headers": true, + "btoa": true, + "console.error": true, + "crypto": true, + "fetch": true, + "msCrypto": true, + "navigator.onLine": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask/approval-controller": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { "globals": { - "console.info": true + "console": true }, "packages": { - "@metamask/approval-controller>nanoid": true, - "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "tslib": true } }, - "@metamask/approval-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/assets-controllers": { + "@metamask/notification-services-controller>firebase>@firebase/messaging": { "globals": { - "AbortController": true, "Headers": true, + "Notification.maxActions": true, + "Notification.permission": true, + "Notification.requestPermission": true, + "PushSubscription.prototype.hasOwnProperty": true, + "ServiceWorkerRegistration": true, "URL": true, - "URLSearchParams": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setInterval": true, + "addEventListener": true, + "atob": true, + "btoa": true, + "clients.matchAll": true, + "clients.openWindow": true, + "console.warn": true, + "document": true, + "fetch": true, + "indexedDB": true, + "location.href": true, + "location.origin": true, + "navigator": true, + "origin.replace": true, + "registration.showNotification": true, "setTimeout": true }, "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, - "@metamask/assets-controllers>@metamask/polling-controller": true, - "@metamask/base-controller": true, - "@metamask/contract-metadata": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "bn.js": true, - "cockatiel": true, - "ethers>@ethersproject/address": true, - "lodash": true, - "single-call-balance-checker-abi": true, - "uuid": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/installations": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, + "tslib": true } }, - "@metamask/assets-controllers>@metamask/polling-controller": { + "@metamask/notification-services-controller>firebase>@firebase/util": { "globals": { - "clearTimeout": true, - "console.error": true, + "atob": true, + "browser": true, + "btoa": true, + "chrome": true, + "console": true, + "document": true, + "indexedDB": true, + "navigator": true, + "process": true, + "self": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "process": true } }, - "@metamask/base-controller": { - "globals": { - "setTimeout": true - }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { - "immer": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "eth-lattice-keyring>rlp": true, + "uuid": true } }, - "@metamask/browser-passworder": { - "globals": { - "CryptoKey": true, - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, + "@keystonehq/bc-ur-registry-eth": { "packages": { - "@metamask/browser-passworder>@metamask/utils": true, - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "uuid": true } }, - "@metamask/browser-passworder>@metamask/utils": { + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "define": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ngraveio/bc-ur": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "buffer": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "tslib": true } }, - "@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "@keystonehq/metamask-airgapped-keyring": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, + "@ethereumjs/tx": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, + "@keystonehq/bc-ur-registry-eth": true, + "@metamask/obs-store": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true + "webpack>events": true, + "@keystonehq/metamask-airgapped-keyring>rlp": true, + "uuid": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser": { + "chart.js>@kurkle/color": { "globals": { - "console.error": true, - "console.log": true + "define": true + } + }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "DocumentFragment.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "react": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { "packages": { - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true } }, - "@metamask/controllers>web3": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { "globals": { - "XMLHttpRequest": true + "console.warn": true } }, - "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { - "globals": { - "fetch": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true } }, - "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { "globals": { - "fetch": true + "console.warn": true + }, + "packages": { + "@ethersproject/abi": true, + "ethers>@ethersproject/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, + "browserify>buffer": true, + "semver": true } }, - "@metamask/ens-controller": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { + "globals": { + "console.warn": true + }, "packages": { - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/ens-controller>@metamask/base-controller": true, - "@metamask/ens-controller>@metamask/utils": true, - "punycode": true + "wait-on>rxjs": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { + "globals": { + "__ledgerLogsListen": "write", + "console.error": true } }, - "@metamask/ens-controller>@metamask/base-controller": { + "@material-ui/core": { "globals": { + "Image": true, + "_formatMuiErrorMessage": true, + "addEventListener": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "getSelection": true, + "innerHeight": true, + "innerWidth": true, + "matchMedia": true, + "navigator": true, + "performance.now": true, + "removeEventListener": true, + "requestAnimationFrame": true, + "setInterval": true, "setTimeout": true }, "packages": { - "immer": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles": true, + "@material-ui/core>@material-ui/system": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>popper.js": true, + "prop-types": true, + "react": true, + "react-dom": true, + "prop-types>react-is": true, + "@material-ui/core>react-transition-group": true } }, - "@metamask/ens-controller>@metamask/utils": { + "@material-ui/core>@material-ui/styles": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "console.error": true, + "console.warn": true, + "document.createComment": true, + "document.head": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, + "@material-ui/core>@material-ui/styles>jss-plugin-global": true, + "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, + "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, + "@material-ui/core>@material-ui/styles>jss": true, + "prop-types": true, + "react": true } }, - "@metamask/eth-json-rpc-filters": { + "@material-ui/core>@material-ui/system": { "globals": { "console.error": true }, "packages": { - "@metamask/eth-query": true, - "@metamask/json-rpc-engine": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "prop-types": true } }, - "@metamask/eth-json-rpc-middleware": { - "globals": { - "URL": true, - "console.error": true, - "setTimeout": true - }, + "@material-ui/core>@material-ui/utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true + "@babel/runtime": true, + "prop-types": true, + "prop-types>react-is": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "@metamask/abi-utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-json-rpc-provider": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring": { - "globals": { - "addEventListener": true, - "console.error": true, - "document.createElement": true, - "document.head.appendChild": true, - "fetch": true, - "removeEventListener": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, - "@metamask/eth-sig-util": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { - "globals": { - "console.warn": true - }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, - "browserify>buffer": true, - "ethers>@ethersproject/rlp": true, - "semver": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { + "@metamask/accounts-controller": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/base-controller": true, + "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/accounts-controller>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "uuid": true + } + }, + "@metamask/address-book-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true + } + }, + "@metamask/announcement-controller": { "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true + "@metamask/announcement-controller>@metamask/base-controller": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { + "@metamask/approval-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { - "globals": { - "console.warn": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { - "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true, - "@metamask/ppom-validator>crypto-js": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { - "globals": { - "console.warn": true + "console.info": true }, "packages": { - "wait-on>rxjs": true + "@metamask/base-controller": true, + "@metamask/rpc-errors": true, + "nanoid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { + "@metamask/assets-controllers": { "globals": { - "Blob": true, - "FormData": true, + "AbortController": true, + "Headers": true, + "URL": true, "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.log": true, + "setInterval": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { - "packages": { - "@ethersproject/abi": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "ethers>@ethersproject/address": true, "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, "@ethersproject/providers": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/wordlists": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { - "globals": { - "__ledgerLogsListen": "write", - "console.error": true + "@metamask/abi-utils": true, + "@metamask/base-controller": true, + "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/metamask-eth-abis": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "cockatiel": true, + "lodash": true, + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, + "single-call-balance-checker-abi": true, + "uuid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { + "@metamask/base-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true + "immer": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "@metamask/announcement-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "define": true - } - }, - "@metamask/eth-query": { - "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "watchify>xtend": true - } - }, - "@metamask/eth-sig-util": { + "setTimeout": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "immer": true } }, - "@metamask/eth-sig-util>@metamask/utils": { + "@metamask/name-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, - "@metamask/eth-sig-util>tweetnacl": { + "@metamask/rate-limit-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "msCrypto": true, - "nacl": "write" + "setTimeout": true }, "packages": { - "browserify>browser-resolve": true + "immer": true } }, - "@metamask/eth-snap-keyring": { + "@metamask/browser-passworder": { "globals": { - "URL": true, - "console.error": true + "CryptoKey": true, + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true }, "packages": { - "@ethereumjs/tx": true, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/eth-snap-keyring>uuid": true, - "@metamask/keyring-api": true, - "@metamask/utils>@metamask/superstruct": true, - "webpack>events": true + "@metamask/browser-passworder>@metamask/utils": true, + "browserify>buffer": true } }, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "eth-keyring-controller>@metamask/browser-passworder": { + "globals": { + "crypto": true } }, - "@metamask/eth-snap-keyring>@metamask/utils": { + "@metamask/controller-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/controller-utils>@metamask/ethjs-unit": true, + "@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "bn.js": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "eth-ens-namehash": true, + "eslint>fast-deep-equal": true } }, - "@metamask/eth-snap-keyring>uuid": { - "globals": { - "crypto": true + "@metamask/ens-controller": { + "packages": { + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/utils": true, + "punycode": true } }, - "@metamask/eth-token-tracker": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { "globals": { - "console.warn": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, - "@metamask/eth-token-tracker>deep-equal": true, - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, "@metamask/safe-event-emitter": true, - "bn.js": true, - "human-standard-token-abi": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true, + "pify": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { + "@metamask/network-controller>@metamask/eth-block-tracker": { "globals": { "clearTimeout": true, "console.error": true, "setTimeout": true }, "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, "@metamask/safe-event-emitter": true, - "pify": true + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/scure-bip39": true, + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@metamask/eth-token-tracker>deep-equal": { - "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, - "@metamask/eth-token-tracker>deep-equal>is-date-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, - "@metamask/eth-token-tracker>deep-equal>which-collection": true, - "@ngraveio/bc-ur>assert>object-is": true, - "browserify>util>is-arguments": true, - "browserify>util>which-typed-array": true, - "gulp>vinyl-fs>object.assign": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true, - "string.prototype.matchall>es-abstract>is-regex": true, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>regexp.prototype.flags": true, - "string.prototype.matchall>side-channel": true + "@metamask/eth-json-rpc-filters": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/safe-event-emitter": true, + "@metamask/name-controller>async-mutex": true, + "pify": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "fetch": true, + "setTimeout": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true, - "browserify>util>is-arguments": true, - "eslint-plugin-react>array-includes>is-string": true, - "process": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>has-symbols": true + "@metamask/eth-json-rpc-provider": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { + "@metamask/eth-json-rpc-middleware": { "globals": { - "StopIteration": true + "URL": true, + "console.error": true, + "setTimeout": true }, "packages": { - "string.prototype.matchall>internal-slot": true + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/eth-json-rpc-middleware>klona": true, + "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true } }, - "@metamask/eth-token-tracker>deep-equal>is-date-object": { + "@metamask/eth-json-rpc-provider": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "uuid": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "@metamask/eth-ledger-bridge-keyring": { + "globals": { + "addEventListener": true, + "console.error": true, + "document.createElement": true, + "document.head.appendChild": true, + "fetch": true, + "removeEventListener": true + }, "packages": { - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, - "eslint-plugin-react>array-includes>is-string": true, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { + "@metamask/controller-utils>@metamask/eth-query": { "packages": { - "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true + "@metamask/ppom-validator>json-rpc-random-id": true, + "watchify>xtend": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/abi-utils": true, + "@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring": { - "globals": { - "setTimeout": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { + "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-sig-util": true, - "@swc/helpers>tslib": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>hdkey": { + "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { - "browserify>assert": true, - "crypto-browserify": true, - "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/etherscan-link": { - "globals": { - "URL": true + "@metamask/keyring-controller>@metamask/eth-simple-keyring": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "crypto-browserify>randombytes": true } }, - "@metamask/ethjs": { + "@metamask/eth-snap-keyring": { "globals": { - "clearInterval": true, - "setInterval": true + "URL": true, + "console.error": true, + "console.info": true }, "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-provider-http": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/number-to-bn": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "@ethereumjs/tx": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, + "@metamask/keyring-api": true, + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "webpack>events": true, + "@metamask/eth-snap-keyring>uuid": true } }, - "@metamask/ethjs-contract": { + "@metamask/eth-token-tracker": { + "globals": { + "console.warn": true + }, "packages": { "@babel/runtime": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "promise-to-callback": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true, + "@metamask/safe-event-emitter": true, + "bn.js": true, + "@metamask/eth-token-tracker>deep-equal": true, + "human-standard-token-abi": true } }, - "@metamask/ethjs-query": { + "@metamask/eth-trezor-keyring": { "globals": { - "console": true + "setTimeout": true }, "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format": true, - "@metamask/ethjs-query>@metamask/ethjs-rpc": true, - "promise-to-callback": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@trezor/connect-web": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/ethjs-query>@metamask/ethjs-format": { - "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "@metamask/ethjs>@metamask/number-to-bn": true + "@metamask/etherscan-link": { + "globals": { + "URL": true } }, - "@metamask/ethjs-query>@metamask/ethjs-rpc": { + "eth-method-registry>@metamask/ethjs-contract": { "packages": { + "@babel/runtime": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": true, + "eth-ens-namehash>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs>@metamask/ethjs-filter": { + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { "globals": { "clearInterval": true, "setInterval": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { "packages": { - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": true + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": { + "eth-method-registry>@metamask/ethjs-query": { "globals": { - "XMLHttpRequest": true - } - }, - "@metamask/ethjs>@metamask/ethjs-unit": { - "packages": { - "@metamask/ethjs>@metamask/number-to-bn": true, - "bn.js": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "browserify>buffer": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, - "@metamask/ethjs>@metamask/number-to-bn": { + "console": true + }, "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "bn.js": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi>number-to-bn": { + "@metamask/controller-utils>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, "bn.js": true } }, - "@metamask/ethjs>js-sha3": { - "globals": { - "define": true - }, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { "packages": { - "process": true + "browserify>buffer": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, "@metamask/gas-fee-controller": { @@ -1433,111 +1300,58 @@ }, "packages": { "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, "bn.js": true, "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/base-controller": { + "@metamask/jazzicon": { "globals": { - "setTimeout": true + "document.createElement": true, + "document.createElementNS": true }, "packages": { - "immer": true + "@metamask/jazzicon>color": true, + "@metamask/jazzicon>mersenne-twister": true + } + }, + "@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/json-rpc-engine>@metamask/utils": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/json-rpc-middleware-stream": { "globals": { - "clearTimeout": true, - "console.error": true, + "console.warn": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/safe-event-emitter": true, + "@metamask/json-rpc-middleware-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/jazzicon": { + "@metamask/snaps-sdk>@metamask/key-tree": { "globals": { - "document.createElement": true, - "document.createElementNS": true + "crypto.subtle": true }, "packages": { - "@metamask/jazzicon>color": true, - "@metamask/jazzicon>mersenne-twister": true + "@metamask/scure-bip39": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/jazzicon>color": { - "packages": { - "@metamask/jazzicon>color>clone": true, - "@metamask/jazzicon>color>color-convert": true, - "@metamask/jazzicon>color>color-string": true - } - }, - "@metamask/jazzicon>color>clone": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask/jazzicon>color>color-convert": { - "packages": { - "@metamask/jazzicon>color>color-convert>color-name": true - } - }, - "@metamask/jazzicon>color>color-string": { - "packages": { - "jest-canvas-mock>moo-color>color-name": true - } - }, - "@metamask/json-rpc-engine": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, - "@metamask/json-rpc-middleware-stream": { - "globals": { - "console.warn": true, - "setTimeout": true - }, - "packages": { - "@metamask/safe-event-emitter": true, - "@metamask/utils": true, - "readable-stream": true - } - }, - "@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/keyring-api>@metamask/utils": true, - "@metamask/keyring-api>bech32": true, - "@metamask/keyring-api>uuid": true, - "@metamask/utils>@metamask/superstruct": true - } - }, - "@metamask/keyring-api>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-api": { "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-api>uuid": { - "globals": { - "crypto": true + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>bech32": true } }, "@metamask/keyring-controller": { @@ -1548,126 +1362,32 @@ "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/keyring-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "@metamask/utils": true + "@metamask/keyring-controller>ethereumjs-wallet": true } }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring": { - "globals": { - "TextEncoder": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, - "@metamask/scure-bip39": true, - "browserify>buffer": true - } - }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-sig-util": { + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "@metamask/keyring-snap-client": true } }, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-snap-client": { "packages": { + "@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "@metamask/keyring-snap-client>uuid": true } }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { + "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, - "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, - "browserify>buffer": true, - "crypto-browserify": true, - "crypto-browserify>randombytes": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "uuid": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>assert": true, - "browserify>buffer": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethers>@ethersproject/sha2>hash.js": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>rlp": true + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": true, + "bitcoin-address-validation": true } }, "@metamask/logging-controller": { @@ -1694,53 +1414,20 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-sig-util": true, - "@metamask/message-manager>@metamask/utils": true, - "@metamask/message-manager>jsonschema": true, - "browserify>buffer": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/message-manager>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/message-manager>jsonschema": { - "packages": { - "browserify>url": true - } - }, - "@metamask/message-signing-snap>@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true + "webpack>events": true, + "uuid": true } }, - "@metamask/message-signing-snap>@noble/curves": { - "globals": { - "TextEncoder": true - }, + "@metamask/multichain": { "packages": { - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": true - } - }, - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@metamask/multichain>@metamask/api-specs": true, + "@metamask/controller-utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "lodash": true } }, "@metamask/name-controller": { @@ -1748,44 +1435,12 @@ "fetch": true }, "packages": { - "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true } }, - "@metamask/name-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/name-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/name-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true - } - }, "@metamask/network-controller": { "globals": { "btoa": true, @@ -1795,721 +1450,853 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-json-rpc-provider": true, - "@metamask/eth-query": true, "@metamask/network-controller>@metamask/eth-block-tracker": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-provider": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/network-controller>reselect": true, - "browserify>assert": true, - "browserify>util": true, + "@metamask/utils": true, + "eslint>fast-deep-equal": true, + "reselect": true, "uri-js": true, "uuid": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, + "@metamask/transaction-controller>@metamask/nonce-tracker": { "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@ethersproject/providers": true, + "browserify>assert": true, + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/notification-services-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Intl.NumberFormat": true, + "addEventListener": true, + "fetch": true, + "registration": true, + "removeEventListener": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/profile-sync-controller": true, + "@metamask/utils": true, + "@metamask/notification-services-controller>bignumber.js": true, + "@metamask/notification-services-controller>firebase": true, + "loglevel": true, + "uuid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": { + "packages": { + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true + } + }, + "@metamask/object-multiplex": { "globals": { - "setTimeout": true + "console.warn": true }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "node-fetch": true + "@metamask/object-multiplex>once": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": { + "@metamask/obs-store": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true + "@metamask/safe-event-emitter": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { + "@metamask/permission-controller": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/permission-controller>@metamask/utils": true, + "deep-freeze-strict": true, + "immer": true, + "nanoid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": { + "@metamask/permission-log-controller": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/permission-log-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": { + "@metamask/phishing-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "console.error": true, + "fetch": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack-cli>fastest-levenshtein": true, + "punycode": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "@metamask/polling-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, + "@metamask/post-message-stream": { + "globals": { + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true + }, + "packages": { + "@metamask/post-message-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": { + "@metamask/ppom-validator": { "globals": { "URL": true, "console.error": true, - "setTimeout": true + "crypto": true }, "packages": { - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, - "bn.js": true, - "pify": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "await-semaphore": true, + "browserify>buffer": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/ppom-validator>elliptic": true, + "@metamask/ppom-validator>json-rpc-random-id": true + } + }, + "@metamask/preferences-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": { + "@metamask/profile-sync-controller": { "globals": { + "Event": true, + "Headers": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/network-controller": true, + "@metamask/profile-sync-controller>@noble/ciphers": true, "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "loglevel": true, + "@metamask/profile-sync-controller>siwe": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/network-controller>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/selected-network-controller": true, + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/rpc-errors": { + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/network-controller>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/rate-limit-controller>@metamask/base-controller": true, + "@metamask/rate-limit-controller>@metamask/rpc-errors": true, + "@metamask/rate-limit-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/remote-feature-flag-controller": { "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "cockatiel": true, + "uuid": true } }, - "@metamask/network-controller>reselect": { - "globals": { - "WeakRef": true, - "console.warn": true, - "unstable_autotrackMemoize": true + "@metamask/rpc-errors": { + "packages": { + "@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-controller": { + "@metamask/rate-limit-controller>@metamask/rpc-errors": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-controller>@metamask/base-controller": { + "@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "immer": true + "webpack>events": true } }, - "@metamask/notification-controller>@metamask/utils": { + "@metamask/scure-bip39": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/scure-bip39>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/notification-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@metamask/selected-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true } }, - "@metamask/notification-services-controller": { + "@metamask/signature-controller": { "globals": { - "Intl.NumberFormat": true, - "addEventListener": true, - "fetch": true, - "registration": true, - "removeEventListener": true + "fetch": true }, "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, - "@metamask/notification-services-controller>bignumber.js": true, - "@metamask/notification-services-controller>firebase": true, - "@metamask/profile-sync-controller": true, + "@metamask/signature-controller>@metamask/eth-sig-util": true, + "@metamask/keyring-controller": true, + "@metamask/logging-controller": true, "@metamask/utils": true, - "loglevel": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/message-manager>jsonschema": true, "uuid": true } }, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { + "@metamask/smart-transactions-controller": { "globals": { - "SuppressedError": true + "URLSearchParams": true, + "clearInterval": true, + "console.error": true, + "console.log": true, + "fetch": true, + "setInterval": true + }, + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethersproject/bytes": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, + "@metamask/transaction-controller": true, + "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, + "fast-json-patch": true, + "lodash": true } }, - "@metamask/notification-services-controller>bignumber.js": { + "@metamask/snaps-controllers": { "globals": { - "crypto": true, - "define": true - } - }, - "@metamask/notification-services-controller>firebase": { - "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/messaging": true + "DecompressionStream": true, + "URL": true, + "clearTimeout": true, + "document.getElementById": true, + "fetch.bind": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/json-rpc-middleware-stream": true, + "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, + "@metamask/post-message-stream": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/snaps-registry": true, + "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/utils": true, + "@metamask/snaps-controllers>@xstate/fsm": true, + "@metamask/name-controller>async-mutex": true, + "browserify>browserify-zlib": true, + "@metamask/snaps-controllers>concat-stream": true, + "eslint>fast-deep-equal": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, + "immer": true, + "luxon": true, + "nanoid": true, + "readable-stream": true, + "@metamask/snaps-controllers>readable-web-to-node-stream": true, + "semver": true, + "@metamask/snaps-controllers>tar-stream": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app": { + "@metamask/snaps-execution-environments": { "globals": { - "FinalizationRegistry": true, - "console.warn": true + "document.getElementById": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/post-message-stream": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true, + "@metamask/snaps-execution-environments>@metamask/utils": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { + "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { - "globals": { - "console": true - }, + "@metamask/snaps-rpc-methods": { "packages": { - "@swc/helpers>tslib": true - } - }, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": { - "globals": { - "DOMException": true, - "IDBCursor": true, - "IDBDatabase": true, - "IDBIndex": true, - "IDBObjectStore": true, - "IDBRequest": true, - "IDBTransaction": true, - "indexedDB.deleteDatabase": true, - "indexedDB.open": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@noble/hashes": true, + "luxon": true } }, - "@metamask/notification-services-controller>firebase>@firebase/installations": { + "@metamask/snaps-sdk": { "globals": { - "BroadcastChannel": true, - "Headers": true, - "btoa": true, - "console.error": true, - "crypto": true, - "fetch": true, - "msCrypto": true, - "navigator.onLine": true, - "setTimeout": true + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-sdk>@metamask/utils": true } }, - "@metamask/notification-services-controller>firebase>@firebase/messaging": { + "@metamask/snaps-utils": { "globals": { - "Headers": true, - "Notification.maxActions": true, - "Notification.permission": true, - "Notification.requestPermission": true, - "PushSubscription.prototype.hasOwnProperty": true, - "ServiceWorkerRegistration": true, + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, "URL": true, - "addEventListener": true, - "atob": true, - "btoa": true, - "clients.matchAll": true, - "clients.openWindow": true, + "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "fetch": true, - "indexedDB": true, - "location.href": true, - "location.origin": true, - "navigator": true, - "origin.replace": true, - "registration.showNotification": true, - "setTimeout": true + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/installations": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true, - "@swc/helpers>tslib": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true } }, - "@metamask/notification-services-controller>firebase>@firebase/util": { + "@metamask/transaction-controller": { "globals": { - "atob": true, - "browser": true, - "btoa": true, - "chrome": true, - "console": true, - "document": true, - "indexedDB": true, - "navigator": true, - "process": true, - "self": true, + "clearTimeout": true, + "console.error": true, + "fetch": true, "setTimeout": true }, "packages": { - "process": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/abi": true, + "@ethersproject/contracts": true, + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/network-controller": true, + "@metamask/transaction-controller>@metamask/nonce-tracker": true, + "@metamask/rpc-errors": true, + "@metamask/transaction-controller>@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "browserify>buffer": true, + "eth-method-registry": true, + "webpack>events": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true } }, - "@metamask/object-multiplex": { + "@metamask/user-operation-controller": { "globals": { - "console.warn": true + "fetch": true }, "packages": { - "@metamask/object-multiplex>once": true, - "readable-stream": true - } - }, - "@metamask/object-multiplex>once": { - "packages": { - "@metamask/object-multiplex>once>wrappy": true - } - }, - "@metamask/obs-store": { - "packages": { - "@metamask/safe-event-emitter": true, - "readable-stream": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/transaction-controller": true, + "@metamask/user-operation-controller>@metamask/utils": true, + "bn.js": true, + "webpack>events": true, + "lodash": true, + "uuid": true } }, - "@metamask/permission-controller": { + "@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/permission-controller>@metamask/utils": true, - "@metamask/permission-controller>nanoid": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-controller>@metamask/base-controller": { + "@metamask/abi-utils>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/accounts-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/utils": { + "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>nanoid": { + "@metamask/controller-utils>@metamask/utils": { "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/permission-log-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/permission-log-controller>@metamask/base-controller": true, - "@metamask/permission-log-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/base-controller": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/utils": { + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/phishing-controller": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { "globals": { - "TextEncoder": true, - "URL": true, - "console.error": true, - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, - "punycode": true, - "webpack-cli>fastest-levenshtein": true + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/polling-controller": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream": { + "@metamask/eth-json-rpc-middleware>@metamask/utils": { "globals": { - "MessageEvent.prototype": true, - "WorkerGlobalScope": true, - "addEventListener": true, - "browser": true, - "chrome": true, - "location.origin": true, - "postMessage": true, - "removeEventListener": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "readable-stream": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream>@metamask/utils": { + "@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/ppom-validator": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "URL": true, - "console.error": true, - "crypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/ppom-validator>crypto-js": true, - "@metamask/ppom-validator>elliptic": true, - "await-semaphore": true, - "browserify>buffer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>crypto-js": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "crypto": true, - "define": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "@metamask/ppom-validator>elliptic>hmac-drbg": true, - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "bn.js": true, - "ethers>@ethersproject/sha2>hash.js": true, - "pumpify>inherits": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>brorand": { + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>hmac-drbg": { + "@metamask/eth-snap-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "ethers>@ethersproject/sha2>hash.js": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/preferences-controller": { + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller": { + "@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "Event": true, - "Headers": true, "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "URLSearchParams": true, - "addEventListener": true, - "console.error": true, - "dispatchEvent": true, - "fetch": true, - "removeEventListener": true, - "setTimeout": true + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/message-signing-snap>@noble/ciphers": true, - "@metamask/profile-sync-controller>siwe": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "loglevel": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe": { + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { "globals": { - "console.error": true, - "console.warn": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random": true, - "ethers": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "@metamask/keyring-api>@metamask/utils": { "globals": { - "console.error": true, - "console.log": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "@metamask/keyring-controller>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true - } - }, - "@metamask/queued-request-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller": { + "@metamask/name-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/rate-limit-controller>@metamask/base-controller": true, - "@metamask/rate-limit-controller>@metamask/rpc-errors": true, - "@metamask/rate-limit-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/base-controller": { + "@metamask/permission-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors": { + "@metamask/permission-log-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/post-message-stream>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, @@ -2520,1700 +2307,1799 @@ }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/rpc-errors": { + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods>nanoid": { + "@metamask/snaps-controllers>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/safe-event-emitter": { + "@metamask/snaps-execution-environments>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39": { + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { "globals": { + "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/scure-bip39>@noble/hashes": true, - "@metamask/utils>@scure/base": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39>@noble/hashes": { + "@metamask/snaps-rpc-methods>@metamask/utils": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@metamask/selected-network-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/network-controller>@metamask/swappable-obj-proxy": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller": { + "@metamask/snaps-sdk>@metamask/utils": { "globals": { - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/keyring-controller": true, - "@metamask/logging-controller": true, - "@metamask/message-manager>jsonschema": true, - "@metamask/signature-controller>@metamask/eth-sig-util": true, - "@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "uuid": true, - "webpack>events": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util": { + "@metamask/snaps-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "@metamask/transaction-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/smart-transactions-controller": { + "@metamask/user-operation-controller>@metamask/utils": { "globals": { - "URLSearchParams": true, - "clearInterval": true, - "console.error": true, - "console.log": true, - "fetch": true, - "setInterval": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bytes": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/polling-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, - "@metamask/smart-transactions-controller>bignumber.js": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "fast-json-patch": true, - "lodash": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "@ngraveio/bc-ur": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true + "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, + "browserify>assert": true, + "@ngraveio/bc-ur>bignumber.js": true, + "browserify>buffer": true, + "@ngraveio/bc-ur>cbor-sync": true, + "@ngraveio/bc-ur>crc": true, + "@ngraveio/bc-ur>jsbi": true, + "addons-linter>sha.js": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { - "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "webpack>events": true + "@metamask/profile-sync-controller>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/util": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "console.warn": true, - "fetch": true + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@noble/hashes": { "globals": { - "setTimeout": true - }, - "packages": { - "immer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "@metamask/scure-bip39>@noble/hashes": { "globals": { - "crypto.getRandomValues": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { "globals": { - "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "TextEncoder": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": { - "packages": { - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": true + "@popperjs/core": { + "globals": { + "Element": true, + "HTMLElement": true, + "ShadowRoot": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator.userAgent": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "console.log": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "XMLHttpRequest": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/smart-transactions-controller>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, - "@metamask/snaps-controllers": { + "@reduxjs/toolkit": { "globals": { - "DecompressionStream": true, - "URL": true, - "clearTimeout": true, - "document.getElementById": true, - "fetch.bind": true, + "AbortController": true, + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, + "__REDUX_DEVTOOLS_EXTENSION__": true, + "console": true, + "queueMicrotask": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/json-rpc-middleware-stream": true, - "@metamask/object-multiplex": true, - "@metamask/post-message-stream": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, - "@metamask/snaps-controllers>@xstate/fsm": true, - "@metamask/snaps-controllers>concat-stream": true, - "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/snaps-controllers>readable-web-to-node-stream": true, - "@metamask/snaps-controllers>tar-stream": true, - "@metamask/snaps-rpc-methods": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, - "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/utils": true, - "browserify>browserify-zlib": true, - "eslint>fast-deep-equal": true, "immer": true, - "readable-stream": true, - "semver": true - } - }, - "@metamask/snaps-controllers-flask>nanoid": { - "globals": { - "crypto.getRandomValues": true + "process": true, + "redux": true, + "redux-thunk": true, + "@reduxjs/toolkit>reselect": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "react-router-dom-v5-compat>@remix-run/router": { "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-controllers>concat-stream": { - "packages": { - "browserify>buffer": true, - "browserify>concat-stream>typedarray": true, - "pumpify>inherits": true, - "readable-stream": true, - "terser>source-map-support>buffer-from": true + "AbortController": true, + "DOMException": true, + "FormData": true, + "Headers": true, + "Request": true, + "Response": true, + "URL": true, + "URLSearchParams": true, + "console": true, + "document.defaultView": true } }, - "@metamask/snaps-controllers>nanoid": { + "@metamask/utils>@scure/base": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>readable-web-to-node-stream": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { "packages": { - "readable-stream": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/snaps-controllers>tar-stream": { + "@segment/loosely-validate-event": { "packages": { - "@metamask/snaps-controllers>tar-stream>b4a": true, - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx": true, - "browserify>browser-resolve": true + "browserify>assert": true, + "browserify>buffer": true, + "@segment/loosely-validate-event>component-type": true, + "@segment/loosely-validate-event>join-component": true } }, - "@metamask/snaps-controllers>tar-stream>b4a": { + "@sentry/browser>@sentry-internal/browser-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true - } - }, - "@metamask/snaps-controllers>tar-stream>streamx": { + "PerformanceEventTiming.prototype": true, + "PerformanceObserver": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "clearTimeout": true, + "performance": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, - "webpack>events": true - } - }, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { - "globals": { - "queueMicrotask": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-execution-environments": { + "@sentry/browser>@sentry-internal/feedback": { "globals": { - "document.getElementById": true + "FormData": true, + "HTMLFormElement": true, + "__SENTRY_DEBUG__": true, + "cancelAnimationFrame": true, + "clearTimeout": true, + "document.createElement": true, + "document.createElementNS": true, + "document.createTextNode": true, + "isSecureContext": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods": { + "@sentry/browser>@sentry-internal/replay-canvas": { + "globals": { + "Blob": true, + "HTMLCanvasElement": true, + "HTMLImageElement": true, + "ImageData": true, + "URL.createObjectURL": true, + "WeakRef": true, + "Worker": true, + "cancelAnimationFrame": true, + "console.error": true, + "createImageBitmap": true, + "document": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { + "@sentry/browser>@sentry-internal/replay": { "globals": { - "console.error": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSRule": true, + "CSSSupportsRule": true, + "Document": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLElement": true, + "HTMLFormElement": true, + "Headers": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.prototype.contains": true, + "PointerEvent": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "__RRWEB_EXCLUDE_IFRAME__": true, + "__RRWEB_EXCLUDE_SHADOW_DOM__": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, + "__rrMutationObserver": true, + "addEventListener": true, + "clearTimeout": true, + "console.debug": true, + "console.error": true, + "console.warn": true, + "customElements.get": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "location.origin": true, + "parent": true, + "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk": { + "@sentry/browser": { "globals": { - "fetch": true + "PerformanceObserver.supportedEntryTypes": true, + "Request": true, + "URL": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "addEventListener": true, + "console.error": true, + "indexedDB.open": true, + "performance.timeOrigin": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true - } - }, - "@metamask/snaps-sdk>@metamask/key-tree": { - "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/scure-bip39": true, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry-internal/feedback": true, + "@sentry/browser>@sentry-internal/replay-canvas": true, + "@sentry/browser>@sentry-internal/replay": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "@sentry/browser>@sentry/core": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Headers": true, + "Request": true, + "URL": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@sentry/utils": true } }, - "@metamask/snaps-utils": { + "@sentry/utils": { "globals": { - "File": true, - "FileReader": true, + "CustomEvent": true, + "DOMError": true, + "DOMException": true, + "EdgeRuntime": true, + "Element": true, + "ErrorEvent": true, + "Event": true, + "HTMLElement": true, + "Headers": true, + "Request": true, + "Response": true, "TextDecoder": true, "TextEncoder": true, "URL": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, + "clearTimeout": true, "console.error": true, - "console.log": true, - "console.warn": true, - "crypto": true, - "document.body.appendChild": true, - "document.createElement": true, - "fetch": true + "document": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, - "@metamask/snaps-utils>cron-parser": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>fast-xml-parser": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, - "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true, - "chalk": true, - "semver": true + "process": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { + "@solana/addresses": { "globals": { - "console.error": true + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@solana/addresses>@solana/assertions": true, + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/codecs-strings": true, + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { + "@solana/addresses>@solana/assertions": { "globals": { - "crypto.getRandomValues": true + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>@metamask/snaps-registry": { + "@solana/addresses>@solana/codecs-core": { "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>cron-parser": { + "@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, "packages": { - "browserify>browser-resolve": true, - "luxon": true + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>fast-xml-parser": { + "@solana/addresses>@solana/errors": { "globals": { - "entityName": true, - "val": true + "btoa": true + } + }, + "@metamask/controller-utils>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true }, "packages": { - "@metamask/snaps-utils>fast-xml-parser>strnum": true + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true } }, - "@metamask/snaps-utils>marked": { + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { "globals": { "console.error": true, - "console.warn": true, - "define": true + "console.log": true + }, + "packages": { + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true } }, - "@metamask/snaps-utils>rfdc": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { "packages": { - "browserify>buffer": true + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/snaps-utils>validate-npm-package-name": { + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, "packages": { - "@metamask/snaps-utils>validate-npm-package-name>builtins": true + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true } }, - "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, "packages": { - "process": true, - "semver": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true } }, - "@metamask/test-bundler>@ethersproject/networks": { + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { - "ethers>@ethersproject/logger": true + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "tslib": true } }, - "@metamask/transaction-controller": { + "@trezor/connect-web": { "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/rpc-errors": true, - "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/utils": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker": { + "@trezor/connect-web>@trezor/connect": { "packages": { - "@ethersproject/providers": true, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>@trezor/connect>@trezor/transport": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { "globals": { - "clearTimeout": true, - "setTimeout": true + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true }, "packages": { - "@swc/helpers>tslib": true + "process": true, + "tslib": true, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true } }, - "@metamask/user-operation-controller": { - "globals": { - "fetch": true - }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/transaction-controller": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "@metamask/user-operation-controller>@metamask/polling-controller": true, - "@metamask/user-operation-controller>@metamask/rpc-errors": true, - "@metamask/user-operation-controller>@metamask/utils": true, - "bn.js": true, - "lodash": true, - "superstruct": true, - "uuid": true, - "webpack>events": true + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "browserify>buffer": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/base-controller": { + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { "globals": { - "setTimeout": true + "console.warn": true }, "packages": { - "immer": true + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, + "browserify>buffer": true, + "ts-mixer": true } }, - "@metamask/user-operation-controller>@metamask/polling-controller": { + "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, + "clearInterval": true, "clearTimeout": true, "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, + "setInterval": true, "setTimeout": true }, "packages": { - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "uuid": true - } - }, - "@metamask/user-operation-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": true + "@trezor/connect-web>@trezor/utils>bignumber.js": true, + "browserify>buffer": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": { + "@welldone-software/why-did-you-render": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Element": true, + "console.group": true, + "console.groupCollapsed": true, + "console.groupEnd": true, + "console.log": true, + "console.warn": true, + "define": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "lodash": true, + "react": true } }, - "@metamask/user-operation-controller>@metamask/utils": { + "@zxing/browser": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "HTMLElement": true, + "HTMLImageElement": true, + "HTMLVideoElement": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@zxing/library": true } }, - "@metamask/utils": { + "@zxing/library": { "globals": { + "HTMLImageElement": true, + "HTMLVideoElement": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL.createObjectURL": true, + "btoa": true, + "console.log": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@zxing/library>ts-custom-error": true } }, - "@metamask/utils>@scure/base": { + "@lavamoat/lavapack>readable-stream>abort-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true - } - }, - "@ngraveio/bc-ur": { - "packages": { - "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, - "@ngraveio/bc-ur>bignumber.js": true, - "@ngraveio/bc-ur>cbor-sync": true, - "@ngraveio/bc-ur>crc": true, - "@ngraveio/bc-ur>jsbi": true, - "addons-linter>sha.js": true, - "browserify>assert": true, - "browserify>buffer": true + "AbortController": true } }, - "@ngraveio/bc-ur>assert>object-is": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true + "currency-formatter>accounting": { + "globals": { + "define": true } }, - "@ngraveio/bc-ur>bignumber.js": { + "ethers>@ethersproject/json-wallets>aes-js": { "globals": { - "crypto": true, "define": true } }, - "@ngraveio/bc-ur>cbor-sync": { + "eth-lattice-keyring>gridplus-sdk>aes-js": { "globals": { "define": true - }, + } + }, + "chalk>ansi-styles": { "packages": { - "browserify>buffer": true + "chalk>ansi-styles>color-convert": true } }, - "@ngraveio/bc-ur>crc": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { "packages": { "browserify>buffer": true } }, - "@ngraveio/bc-ur>jsbi": { - "globals": { - "define": true + "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true } }, - "@noble/hashes": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "browserify>vm-browserify": true + } + }, + "browserify>assert": { "globals": { - "TextEncoder": true, - "crypto": true + "Buffer": true + }, + "packages": { + "react>object-assign": true, + "browserify>assert>util": true } }, - "@popperjs/core": { + "@metamask/name-controller>async-mutex": { "globals": { - "Element": true, - "HTMLElement": true, - "ShadowRoot": true, - "console.error": true, - "console.warn": true, - "document": true, - "navigator.userAgent": true + "clearTimeout": true, + "setTimeout": true + }, + "packages": { + "tslib": true } }, - "@reduxjs/toolkit": { + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { "globals": { - "AbortController": true, - "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, - "__REDUX_DEVTOOLS_EXTENSION__": true, - "console": true, - "queueMicrotask": true, - "requestAnimationFrame": true, + "clearTimeout": true, "setTimeout": true }, "packages": { - "@reduxjs/toolkit>reselect": true, - "immer": true, - "process": true, - "redux": true, - "redux-thunk": true + "tslib": true } }, - "@segment/loosely-validate-event": { + "string.prototype.matchall>es-abstract>available-typed-arrays": { "packages": { - "@segment/loosely-validate-event>component-type": true, - "@segment/loosely-validate-event>join-component": true, - "browserify>assert": true, - "browserify>buffer": true + "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true } }, - "@sentry/browser": { + "await-semaphore": { + "packages": { + "process": true, + "browserify>timers-browserify": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { "globals": { - "PerformanceObserver.supportedEntryTypes": true, - "Request": true, - "URL": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_RELEASE__": true, - "addEventListener": true, - "console.error": true, - "indexedDB.open": true, - "performance.timeOrigin": true, - "setTimeout": true - }, - "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry-internal/feedback": true, - "@sentry/browser>@sentry-internal/replay": true, - "@sentry/browser>@sentry-internal/replay-canvas": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry-internal/browser-utils": { - "globals": { - "PerformanceEventTiming.prototype": true, - "PerformanceObserver": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "addEventListener": true, - "clearTimeout": true, - "performance": true, - "removeEventListener": true, + "Blob": true, + "FormData": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/feedback": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { "globals": { + "Blob": true, "FormData": true, - "HTMLFormElement": true, - "__SENTRY_DEBUG__": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "document.createElement": true, - "document.createElementNS": true, - "document.createTextNode": true, - "isSecureContext": true, - "requestAnimationFrame": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/replay": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { "globals": { "Blob": true, - "CSSConditionRule": true, - "CSSGroupingRule": true, - "CSSMediaRule": true, - "CSSRule": true, - "CSSSupportsRule": true, - "Document": true, - "DragEvent": true, - "Element": true, "FormData": true, - "HTMLElement": true, - "HTMLFormElement": true, - "Headers": true, - "MouseEvent": true, - "MutationObserver": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.prototype.contains": true, - "PointerEvent": true, - "TextEncoder": true, - "URL": true, "URLSearchParams": true, - "Worker": true, - "__RRWEB_EXCLUDE_IFRAME__": true, - "__RRWEB_EXCLUDE_SHADOW_DOM__": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, - "__rrMutationObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.debug": true, - "console.error": true, + "XMLHttpRequest": true, + "btoa": true, "console.warn": true, - "customElements.get": true, "document": true, - "innerHeight": true, - "innerWidth": true, "location.href": true, - "location.origin": true, - "parent": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/replay-canvas": { + "@metamask/snaps-controllers>tar-stream>b4a": { "globals": { - "Blob": true, - "HTMLCanvasElement": true, - "HTMLImageElement": true, - "ImageData": true, - "URL.createObjectURL": true, - "WeakRef": true, - "Worker": true, - "cancelAnimationFrame": true, - "console.error": true, - "createImageBitmap": true, - "document": true + "TextDecoder": true, + "TextEncoder": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true + } + }, + "base32-encode": { + "packages": { + "base32-encode>to-data-view": true + } + }, + "bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/notification-services-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/smart-transactions-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@ngraveio/bc-ur>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@trezor/connect-web>@trezor/utils>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bitwise": { + "packages": { + "browserify>buffer": true + } + }, + "blo": { + "globals": { + "btoa": true + } + }, + "bn.js": { + "globals": { + "Buffer": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>browser-resolve": true } }, - "@sentry/browser>@sentry/core": { + "eth-lattice-keyring>gridplus-sdk>borc": { "globals": { - "Headers": true, - "Request": true, - "URL": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_TRACING__": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true + "console": true }, "packages": { - "@sentry/utils": true + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, + "browserify>buffer": true, + "buffer>ieee754": true, + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true } }, - "@sentry/utils": { + "bowser": { "globals": { - "CustomEvent": true, - "DOMError": true, - "DOMException": true, - "EdgeRuntime": true, - "Element": true, - "ErrorEvent": true, - "Event": true, - "HTMLElement": true, - "Headers": true, - "Request": true, - "Response": true, - "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "__SENTRY_BROWSER_BUNDLE__": true, - "__SENTRY_DEBUG__": true, - "clearTimeout": true, - "console.error": true, - "document": true, - "setInterval": true, - "setTimeout": true + "define": true + } + }, + "@metamask/ppom-validator>elliptic>brorand": { + "globals": { + "crypto": true, + "msCrypto": true }, "packages": { - "process": true + "browserify>browser-resolve": true + } + }, + "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true + } + }, + "crypto-browserify>browserify-cipher": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "crypto-browserify>browserify-cipher>browserify-des": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true + } + }, + "crypto-browserify>browserify-cipher>browserify-des": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>browserify-des>des.js": true, + "pumpify>inherits": true + } + }, + "crypto-browserify>public-encrypt>browserify-rsa": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>randombytes": true + } + }, + "crypto-browserify>browserify-sign": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "@metamask/ppom-validator>elliptic": true, + "pumpify>inherits": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "stream-browserify": true + } + }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>buffer": true, + "browserify>browserify-zlib>pako": true, + "process": true, + "stream-browserify": true, + "browserify>util": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, - "@solana/addresses": { - "globals": { - "Intl.Collator": true, - "TextEncoder": true, - "crypto.subtle.digest": true, - "crypto.subtle.exportKey": true - }, + "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { - "@solana/addresses>@solana/assertions": true, - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/codecs-strings": true, - "@solana/addresses>@solana/errors": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "ethereumjs-util>create-hash": true, + "koa>content-disposition>safe-buffer": true } }, - "@solana/addresses>@solana/assertions": { + "buffer": { "globals": { - "crypto": true, - "isSecureContext": true + "console": true }, "packages": { - "@solana/addresses>@solana/errors": true + "base64-js": true, + "buffer>ieee754": true } }, - "@solana/addresses>@solana/codecs-core": { + "terser>source-map-support>buffer-from": { "packages": { - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/codecs-strings": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true - }, + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { "packages": { - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/errors": { + "browserify>buffer": { "globals": { - "btoa": true + "console": true + }, + "packages": { + "base64-js": true, + "buffer>ieee754": true } }, - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": { "packages": { - "react-markdown>unist-util-visit": true + "process": true, + "semver": true } }, - "@storybook/addon-knobs>qs": { + "string.prototype.matchall>call-bind": { "packages": { - "string.prototype.matchall>side-channel": true + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>call-bind>set-function-length": true } }, - "@swc/helpers>tslib": { + "@ngraveio/bc-ur>cbor-sync": { "globals": { - "SuppressedError": true, "define": true + }, + "packages": { + "browserify>buffer": true } }, - "@trezor/connect-web": { + "chalk": { + "packages": { + "chalk>ansi-styles": true, + "chalk>supports-color": true + } + }, + "chart.js": { "globals": { - "URLSearchParams": true, - "__TREZOR_CONNECT_SRC": true, + "Intl.NumberFormat": true, + "MutationObserver": true, + "OffscreenCanvas": true, + "Path2D": true, + "ResizeObserver": true, "addEventListener": true, - "btoa": true, - "chrome": true, - "clearInterval": true, "clearTimeout": true, + "console.error": true, "console.warn": true, - "document.body": true, - "document.createElement": true, - "document.createTextNode": true, - "document.getElementById": true, - "document.querySelectorAll": true, - "location": true, - "navigator": true, - "open": true, - "origin": true, + "devicePixelRatio": true, + "document": true, "removeEventListener": true, - "setInterval": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect": true, - "@trezor/connect-web>@trezor/connect-common": true, - "@trezor/connect-web>@trezor/utils": true, - "webpack>events": true - } - }, - "@trezor/connect-web>@trezor/connect": { - "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, - "@trezor/connect-web>@trezor/connect>@trezor/transport": true, - "@trezor/connect-web>@trezor/utils": true + "chart.js>@kurkle/color": true } }, - "@trezor/connect-web>@trezor/connect-common": { - "globals": { - "console.warn": true, - "localStorage.getItem": true, - "localStorage.setItem": true, - "navigator": true, - "setTimeout": true, - "window": true - }, + "@ensdomains/content-hash>cids": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, - "@trezor/connect-web>@trezor/utils": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>cids>multihashes": true, + "@ensdomains/content-hash>cids>uint8arrays": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { - "globals": { - "innerHeight": true, - "innerWidth": true, - "location.hostname": true, - "location.origin": true, - "navigator.languages": true, - "navigator.platform": true, - "navigator.userAgent": true, - "screen.height": true, - "screen.width": true - }, + "ethereumjs-util>create-hash>cipher-base": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, - "process": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true, + "stream-browserify": true, + "browserify>string_decoder": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "classnames": { "globals": { + "classNames": "write", "define": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { + "@metamask/jazzicon>color>clone": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, "browserify>buffer": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "cockatiel": { "globals": { - "process": true, + "AbortController": true, + "AbortSignal": true, + "WeakRef": true, + "clearTimeout": true, + "performance": true, "setTimeout": true }, "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true - } - }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { - "globals": { - "console.log": true + "process": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { - "globals": { - "XMLHttpRequest": true - }, + "chalk>ansi-styles>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { - "globals": { - "console.warn": true - }, + "@metamask/jazzicon>color>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "browserify>buffer": true, - "ts-mixer": true + "@metamask/jazzicon>color>color-convert>color-name": true } }, - "@trezor/connect-web>@trezor/utils": { - "globals": { - "AbortController": true, - "Intl.NumberFormat": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.info": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "@metamask/jazzicon>color>color-string": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/utils>bignumber.js": true, - "browserify>buffer": true, - "webpack>events": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/utils>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/jazzicon>color": { + "packages": { + "@metamask/jazzicon>color>clone": true, + "@metamask/jazzicon>color>color-convert": true, + "@metamask/jazzicon>color>color-string": true } }, - "@welldone-software/why-did-you-render": { - "globals": { - "Element": true, - "console.group": true, - "console.groupCollapsed": true, - "console.groupEnd": true, - "console.log": true, - "console.warn": true, - "define": true, - "setTimeout": true - }, + "@metamask/snaps-controllers>concat-stream": { "packages": { - "lodash": true, - "react": true + "terser>source-map-support>buffer-from": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "readable-stream": true, + "browserify>concat-stream>typedarray": true } }, - "@zxing/browser": { + "copy-to-clipboard": { "globals": { - "HTMLElement": true, - "HTMLImageElement": true, - "HTMLVideoElement": true, - "clearTimeout": true, + "clipboardData": true, "console.error": true, "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true, + "document.createRange": true, + "document.execCommand": true, + "document.getSelection": true, + "navigator.userAgent": true, + "prompt": true }, "packages": { - "@zxing/library": true + "copy-to-clipboard>toggle-selection": true } }, - "@zxing/library": { + "@ethereumjs/tx>@ethereumjs/common>crc-32": { "globals": { - "HTMLImageElement": true, - "HTMLVideoElement": true, - "TextDecoder": true, - "TextEncoder": true, - "URL.createObjectURL": true, - "btoa": true, - "console.log": true, - "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "@zxing/library>ts-custom-error": true + "DO_NOT_EXPORT_CRC": true, + "define": true } }, - "addons-linter>sha.js": { + "@ngraveio/bc-ur>crc": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "await-semaphore": { + "crypto-browserify>create-ecdh": { "packages": { - "browserify>timers-browserify": true, - "process": true + "bn.js": true, + "browserify>buffer": true, + "@metamask/ppom-validator>elliptic": true } }, - "axios>form-data": { - "globals": { - "FormData": true + "ethereumjs-util>create-hash": { + "packages": { + "ethereumjs-util>create-hash>cipher-base": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>md5.js": true, + "ethereumjs-util>create-hash>ripemd160": true, + "addons-linter>sha.js": true } }, - "base32-encode": { + "crypto-browserify>create-hmac": { "packages": { - "base32-encode>to-data-view": true + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>create-hash": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true } }, - "blo": { - "globals": { - "btoa": true + "crypto-browserify": { + "packages": { + "crypto-browserify>browserify-cipher": true, + "crypto-browserify>browserify-sign": true, + "crypto-browserify>create-ecdh": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "crypto-browserify>diffie-hellman": true, + "crypto-browserify>pbkdf2": true, + "crypto-browserify>public-encrypt": true, + "crypto-browserify>randombytes": true, + "crypto-browserify>randomfill": true } }, - "bn.js": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "Buffer": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { "browserify>browser-resolve": true } }, - "bowser": { - "globals": { - "define": true - } - }, - "browserify>assert": { + "react-beautiful-dnd>css-box-model": { "globals": { - "Buffer": true + "getComputedStyle": true, + "pageXOffset": true, + "pageYOffset": true }, "packages": { - "browserify>assert>util": true, - "react>object-assign": true + "react-router-dom>tiny-invariant": true } }, - "browserify>assert>util": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true, - "process": true + "document.createElement": true, + "document.documentElement": true, + "getComputedStyle": true }, "packages": { - "browserify>assert>util>inherits": true, - "process": true - } - }, - "browserify>browserify-zlib": { - "packages": { - "browserify>assert": true, - "browserify>browserify-zlib>pako": true, - "browserify>buffer": true, - "browserify>util": true, - "process": true, - "stream-browserify": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true } }, - "browserify>buffer": { - "globals": { - "console": true - }, + "currency-formatter": { "packages": { - "base64-js": true, - "buffer>ieee754": true - } - }, - "browserify>punycode": { - "globals": { - "define": true + "currency-formatter>accounting": true, + "currency-formatter>locale-currency": true, + "react>object-assign": true } }, - "browserify>string_decoder": { + "debounce-stream": { "packages": { - "koa>content-disposition>safe-buffer": true + "debounce-stream>debounce": true, + "debounce-stream>duplexer": true, + "debounce-stream>through": true } }, - "browserify>timers-browserify": { + "debounce-stream>debounce": { "globals": { - "clearInterval": true, "clearTimeout": true, - "setInterval": true, "setTimeout": true - }, - "packages": { - "process": true - } - }, - "browserify>url": { - "packages": { - "@storybook/addon-knobs>qs": true, - "browserify>punycode": true } }, - "browserify>util": { + "nock>debug": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "browserify>util>is-arguments": true, - "browserify>util>is-typed-array": true, - "browserify>util>which-typed-array": true, - "koa>is-generator-function": true, - "process": true, - "pumpify>inherits": true - } - }, - "browserify>util>is-arguments": { - "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "nock>debug>ms": true, + "process": true } }, - "browserify>util>is-typed-array": { + "@metamask/eth-token-tracker>deep-equal": { "packages": { + "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, + "string.prototype.matchall>call-bind": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, + "string.prototype.matchall>get-intrinsic": true, + "browserify>util>is-arguments": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true, + "@metamask/eth-token-tracker>deep-equal>is-date-object": true, + "string.prototype.matchall>es-abstract>is-regex": true, + "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "@ngraveio/bc-ur>assert>object-is": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true, + "gulp>vinyl-fs>object.assign": true, + "string.prototype.matchall>regexp.prototype.flags": true, + "string.prototype.matchall>side-channel": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, + "@metamask/eth-token-tracker>deep-equal>which-collection": true, "browserify>util>which-typed-array": true } }, - "browserify>util>which-typed-array": { + "string.prototype.matchall>define-properties>define-data-property": { "packages": { - "browserify>util>which-typed-array>for-each": true, - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>es-abstract>gopd": true } }, - "browserify>util>which-typed-array>for-each": { + "string.prototype.matchall>define-properties": { "packages": { - "string.prototype.matchall>es-abstract>is-callable": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "browserify>vm-browserify": { - "globals": { - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true + "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, - "buffer": { - "globals": { - "console": true - }, + "crypto-browserify>diffie-hellman": { "packages": { - "base64-js": true, - "buffer>ieee754": true + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>diffie-hellman>miller-rabin": true, + "crypto-browserify>randombytes": true } }, - "chalk": { + "@material-ui/core>react-transition-group>dom-helpers": { "packages": { - "chalk>ansi-styles": true, - "chalk>supports-color": true + "@babel/runtime": true } }, - "chalk>ansi-styles": { + "debounce-stream>duplexer": { "packages": { - "chalk>ansi-styles>color-convert": true + "stream-browserify": true } }, - "chalk>ansi-styles>color-convert": { + "ethers>@ethersproject/signing-key>elliptic": { "packages": { - "jest-canvas-mock>moo-color>color-name": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chart.js": { - "globals": { - "Intl.NumberFormat": true, - "MutationObserver": true, - "OffscreenCanvas": true, - "Path2D": true, - "ResizeObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.error": true, - "console.warn": true, - "devicePixelRatio": true, - "document": true, - "removeEventListener": true, - "requestAnimationFrame": true, - "setTimeout": true - }, + "@metamask/ppom-validator>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, + "eth-lattice-keyring>gridplus-sdk>elliptic": { "packages": { - "chart.js>@kurkle/color": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chart.js>@kurkle/color": { - "globals": { - "define": true + "string.prototype.matchall>call-bind>es-define-property": { + "packages": { + "string.prototype.matchall>get-intrinsic": true } }, - "classnames": { - "globals": { - "classNames": "write", - "define": true + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>has-symbols": true, + "browserify>util>is-arguments": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "eslint-plugin-react>array-includes>is-string": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "process": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "cockatiel": { + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { "globals": { - "AbortController": true, - "AbortSignal": true, - "WeakRef": true, - "clearTimeout": true, - "performance": true, - "setTimeout": true + "intToBuffer": true }, "packages": { - "process": true + "bn.js": true, + "buffer": true, + "eth-ens-namehash>js-sha3": true } }, - "copy-to-clipboard": { + "eth-ens-namehash": { "globals": { - "clipboardData": true, - "console.error": true, - "console.warn": true, - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true, - "document.createRange": true, - "document.execCommand": true, - "document.getSelection": true, - "navigator.userAgent": true, - "prompt": true + "name": "write" }, "packages": { - "copy-to-clipboard>toggle-selection": true + "browserify>buffer": true, + "eth-ens-namehash>idna-uts46-hx": true, + "eth-ens-namehash>js-sha3": true } }, - "copy-to-clipboard>toggle-selection": { + "eth-lattice-keyring": { "globals": { - "document.activeElement": true, - "document.getSelection": true + "addEventListener": true, + "browser": true, + "clearInterval": true, + "fetch": true, + "open": true, + "setInterval": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify": true, + "webpack>events": true, + "eth-lattice-keyring>gridplus-sdk": true, + "eth-lattice-keyring>rlp": true } }, - "crypto-browserify": { + "eth-method-registry": { "packages": { - "crypto-browserify>browserify-cipher": true, - "crypto-browserify>browserify-sign": true, - "crypto-browserify>create-ecdh": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>diffie-hellman": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt": true, - "crypto-browserify>randombytes": true, - "crypto-browserify>randomfill": true, - "ethereumjs-util>create-hash": true + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true } }, - "crypto-browserify>browserify-cipher": { + "@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "crypto-browserify>browserify-cipher>browserify-des": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true } }, - "crypto-browserify>browserify-cipher>browserify-des": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>browserify-des>des.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "pumpify>inherits": true + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>evp_bytestokey": { + "ethereumjs-util>ethereum-cryptography": { "packages": { - "ethereumjs-util>create-hash>md5.js": true, - "koa>content-disposition>safe-buffer": true + "browserify>buffer": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "ganache>secp256k1": true } }, - "crypto-browserify>browserify-sign": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "browserify>buffer": true, "crypto-browserify>create-hmac": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "ethereumjs-util>create-hash": true, - "pumpify>inherits": true, - "stream-browserify": true + "ethers>@ethersproject/sha2>hash.js": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true } }, - "crypto-browserify>create-ecdh": { + "ethereumjs-util": { "packages": { - "@metamask/ppom-validator>elliptic": true, + "browserify>assert": true, "bn.js": true, - "browserify>buffer": true + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>rlp": true } }, - "crypto-browserify>create-hmac": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { - "addons-linter>sha.js": true, + "browserify>assert": true, + "bn.js": true, + "browserify>buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true } }, - "crypto-browserify>diffie-hellman": { + "@metamask/keyring-controller>ethereumjs-wallet": { "packages": { - "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "browserify>buffer": true, - "crypto-browserify>diffie-hellman>miller-rabin": true, - "crypto-browserify>randombytes": true + "crypto-browserify": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, + "crypto-browserify>randombytes": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true, + "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, + "uuid": true } }, - "crypto-browserify>diffie-hellman>miller-rabin": { + "ethers": { "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "bn.js": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "ethers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>pbkdf2": { - "globals": { - "crypto": true, - "process": true, - "queueMicrotask": true, - "setImmediate": true, - "setTimeout": true - }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>public-encrypt": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, "browserify>buffer": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>create-hash": true + "eth-ens-namehash>js-sha3": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": true } }, - "crypto-browserify>public-encrypt>browserify-rsa": { - "packages": { - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "webpack>events": { + "globals": { + "console": true } }, - "crypto-browserify>public-encrypt>parse-asn1": { - "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "crypto-browserify>browserify-cipher>evp_bytestokey": { + "packages": { + "ethereumjs-util>create-hash>md5.js": true, + "koa>content-disposition>safe-buffer": true } }, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "extension-port-stream": { "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "bn.js": true, "browserify>buffer": true, - "browserify>vm-browserify": true, - "pumpify>inherits": true + "extension-port-stream>readable-stream": true } }, - "crypto-browserify>randombytes": { + "fast-json-patch": { "globals": { - "crypto": true, - "msCrypto": true + "addEventListener": true, + "clearTimeout": true, + "removeEventListener": true, + "setTimeout": true + } + }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true }, "packages": { - "koa>content-disposition>safe-buffer": true, - "process": true + "@metamask/snaps-utils>fast-xml-parser>strnum": true } }, - "crypto-browserify>randomfill": { + "@metamask/notification-services-controller>firebase": { + "packages": { + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/messaging": true + } + }, + "react-focus-lock>focus-lock": { "globals": { - "crypto": true, - "msCrypto": true + "HTMLIFrameElement": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.DOCUMENT_NODE": true, + "Node.DOCUMENT_POSITION_CONTAINED_BY": true, + "Node.DOCUMENT_POSITION_CONTAINS": true, + "Node.ELEMENT_NODE": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "setTimeout": true }, "packages": { - "crypto-browserify>randombytes": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "tslib": true } }, - "currency-formatter": { + "browserify>util>which-typed-array>for-each": { "packages": { - "currency-formatter>accounting": true, - "currency-formatter>locale-currency": true, - "react>object-assign": true + "string.prototype.matchall>es-abstract>is-callable": true } }, - "currency-formatter>accounting": { + "axios>form-data": { + "globals": { + "FormData": true + } + }, + "fuse.js": { "globals": { + "console": true, "define": true } }, - "currency-formatter>locale-currency": { + "string.prototype.matchall>get-intrinsic": { "globals": { - "countryCode": true + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true, + "depcheck>is-core-module>hasown": true } }, - "debounce-stream": { + "string.prototype.matchall>es-abstract>gopd": { "packages": { - "debounce-stream>debounce": true, - "debounce-stream>duplexer": true, - "debounce-stream>through": true + "string.prototype.matchall>get-intrinsic": true } }, - "debounce-stream>debounce": { + "eth-lattice-keyring>gridplus-sdk": { "globals": { + "AbortController": true, + "Request": true, + "URL": true, + "__values": true, + "caches": true, "clearTimeout": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "@ethersproject/abi": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "@metamask/keyring-api>bech32": true, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, + "eth-lattice-keyring>gridplus-sdk>bitwise": true, + "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>borc": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>elliptic": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "ethers>@ethersproject/sha2>hash.js": true, + "eth-ens-namehash>js-sha3": true, + "lodash": true, + "eth-lattice-keyring>rlp": true, + "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>uuid": true } }, - "debounce-stream>duplexer": { + "string.prototype.matchall>es-abstract>has-property-descriptors": { "packages": { - "stream-browserify": true + "string.prototype.matchall>call-bind>es-define-property": true } }, - "debounce-stream>through": { + "koa>is-generator-function>has-tostringtag": { "packages": { - "process": true, - "stream-browserify": true + "string.prototype.matchall>has-symbols": true } }, - "depcheck>@vue/compiler-sfc>postcss>nanoid": { - "globals": { - "crypto.getRandomValues": true + "ethereumjs-util>create-hash>md5.js>hash-base": { + "packages": { + "pumpify>inherits": true, + "readable-stream": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethers>@ethersproject/sha2>hash.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, "depcheck>is-core-module>hasown": { @@ -4221,276 +4107,257 @@ "browserify>has>function-bind": true } }, - "dependency-tree>precinct>detective-postcss>postcss>nanoid": { + "@metamask/eth-trezor-keyring>hdkey": { + "packages": { + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "crypto-browserify": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true + } + }, + "he": { "globals": { - "crypto.getRandomValues": true + "define": true } }, - "eslint-plugin-react>array-includes>is-string": { - "packages": { - "koa>is-generator-function>has-tostringtag": true + "history": { + "globals": { + "console": true, + "define": true, + "document.defaultView": true, + "document.querySelector": true } }, - "eth-ens-namehash": { + "react-router-dom>history": { "globals": { - "name": "write" + "addEventListener": true, + "confirm": true, + "document": true, + "history": true, + "location": true, + "navigator.userAgent": true, + "removeEventListener": true }, "packages": { - "@metamask/ethjs>js-sha3": true, - "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true + "react-router-dom>history>resolve-pathname": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true, + "react-router-dom>history>value-equal": true } }, - "eth-ens-namehash>idna-uts46-hx": { - "globals": { - "define": true - }, + "@metamask/ppom-validator>elliptic>hmac-drbg": { "packages": { - "browserify>punycode": true + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "eth-keyring-controller>@metamask/browser-passworder": { - "globals": { - "crypto": true + "react-redux>hoist-non-react-statics": { + "packages": { + "prop-types>react-is": true } }, - "eth-lattice-keyring": { - "globals": { - "addEventListener": true, - "browser": true, - "clearInterval": true, - "fetch": true, - "open": true, - "setInterval": true - }, + "https-browserify": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify": true, - "eth-lattice-keyring>gridplus-sdk": true, - "eth-lattice-keyring>rlp": true, - "webpack>events": true + "stream-http": true, + "browserify>url": true } }, - "eth-lattice-keyring>gridplus-sdk": { + "@metamask/notification-services-controller>firebase>@firebase/app>idb": { "globals": { - "AbortController": true, - "Request": true, - "URL": true, - "__values": true, - "caches": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "console.warn": true, - "fetch": true, - "setTimeout": true + "DOMException": true, + "IDBCursor": true, + "IDBDatabase": true, + "IDBIndex": true, + "IDBObjectStore": true, + "IDBRequest": true, + "IDBTransaction": true, + "indexedDB.deleteDatabase": true, + "indexedDB.open": true + } + }, + "eth-ens-namehash>idna-uts46-hx": { + "globals": { + "define": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-sig-util": true, - "@metamask/ethjs>js-sha3": true, - "@metamask/keyring-api>bech32": true, - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc": true, - "eth-lattice-keyring>gridplus-sdk>bs58check": true, - "eth-lattice-keyring>gridplus-sdk>secp256k1": true, - "eth-lattice-keyring>gridplus-sdk>uuid": true, - "ethers>@ethersproject/sha2>hash.js": true, - "lodash": true + "browserify>punycode": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "string.prototype.matchall>internal-slot": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "string.prototype.matchall>call-bind>es-errors": true, + "depcheck>is-core-module>hasown": true, + "string.prototype.matchall>side-channel": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "browserify>util>is-arguments": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "string.prototype.matchall>es-abstract>is-array-buffer": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { - "globals": { - "console.warn": true, - "fetch": true - }, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true } }, - "eth-lattice-keyring>gridplus-sdk>aes-js": { - "globals": { - "define": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "packages": { + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "string.prototype.matchall>es-abstract>is-callable": { "globals": { - "crypto": true, - "define": true + "document": true } }, - "eth-lattice-keyring>gridplus-sdk>borc": { - "globals": { - "console": true - }, + "@metamask/eth-token-tracker>deep-equal>is-date-object": { "packages": { - "browserify>buffer": true, - "buffer>ieee754": true, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "koa>is-generator-function": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "@material-ui/core>@material-ui/styles>jss>is-in-browser": { "globals": { - "URL": true, - "URLSearchParams": true, - "location": true, - "navigator": true + "document": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { "packages": { - "@noble/hashes": true, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "string.prototype.matchall>es-abstract>is-regex": { "packages": { - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "string.prototype.matchall>es-abstract>is-shared-array-buffer": { "packages": { - "@metamask/ppom-validator>elliptic": true + "string.prototype.matchall>call-bind": true } }, - "eth-lattice-keyring>gridplus-sdk>uuid": { - "globals": { - "crypto": true + "eslint-plugin-react>array-includes>is-string": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>rlp": { - "globals": { - "TextEncoder": true + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "packages": { + "string.prototype.matchall>has-symbols": true } }, - "eth-method-registry": { + "browserify>util>is-typed-array": { "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true + "browserify>util>which-typed-array": true } }, - "ethereumjs-util": { + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { "packages": { - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "ethereumjs-util>create-hash": { + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "globals": { + "URL": true, + "URLSearchParams": true, + "location": true + } + }, + "@ensdomains/content-hash>js-base64": { + "globals": { + "Base64": "write", + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true, + "define": true + }, "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-util>create-hash>ripemd160": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "ethereumjs-util>create-hash>cipher-base": { + "eth-ens-namehash>js-sha3": { + "globals": { + "define": true + }, "packages": { - "browserify>string_decoder": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "stream-browserify": true + "process": true } }, - "ethereumjs-util>create-hash>md5.js": { + "@ngraveio/bc-ur>jsbi": { + "globals": { + "define": true + } + }, + "@metamask/message-manager>jsonschema": { "packages": { - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>url": true } }, - "ethereumjs-util>create-hash>md5.js>hash-base": { + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "readable-stream": true + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true } }, - "ethereumjs-util>create-hash>ripemd160": { + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { + "globals": { + "CSS": true + }, "packages": { - "browserify>buffer": true, - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "pumpify>inherits": true + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography": { + "@material-ui/core>@material-ui/styles>jss-plugin-global": { "packages": { - "browserify>buffer": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ganache>secp256k1": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "@material-ui/core>@material-ui/styles>jss-plugin-nested": { "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@babel/runtime": true, + "react-router-dom>tiny-warning": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { "packages": { - "browserify>buffer": true + "@material-ui/core>@material-ui/styles>jss": true, + "react-router-dom>tiny-warning": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, - "koa>content-disposition>safe-buffer": true + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "@material-ui/core>@material-ui/styles>jss": { + "globals": { + "CSS": true, + "document.createElement": true, + "document.querySelector": true + }, "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, + "react-router-dom>tiny-warning": true } }, "ethereumjs-util>ethereum-cryptography>keccak": { @@ -4499,527 +4366,496 @@ "readable-stream": true } }, - "ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, - "ethereumjs-wallet>randombytes": { + "currency-formatter>locale-currency": { "globals": { - "crypto.getRandomValues": true + "countryCode": true } }, - "ethers": { - "packages": { - "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/web": true, - "ethers>@ethersproject/wordlists": true + "localforage": { + "globals": { + "Blob": true, + "BlobBuilder": true, + "FileReader": true, + "IDBKeyRange": true, + "MSBlobBuilder": true, + "MozBlobBuilder": true, + "OIndexedDB": true, + "WebKitBlobBuilder": true, + "atob": true, + "btoa": true, + "console.error": true, + "console.info": true, + "console.warn": true, + "define": true, + "fetch": true, + "indexedDB": true, + "localStorage": true, + "mozIndexedDB": true, + "msIndexedDB": true, + "navigator.platform": true, + "navigator.userAgent": true, + "openDatabase": true, + "setTimeout": true, + "webkitIndexedDB": true } }, - "ethers>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "lodash": { + "globals": { + "clearTimeout": true, + "define": true, + "setTimeout": true } }, - "ethers>@ethersproject/abstract-signer": { - "packages": { - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "loglevel": { + "globals": { + "console": true, + "define": true, + "document.cookie": true, + "localStorage": true, + "log": "write", + "navigator": true } }, - "ethers>@ethersproject/address": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/rlp": true + "lottie-web": { + "globals": { + "Blob": true, + "Howl": true, + "OffscreenCanvas": true, + "URL.createObjectURL": true, + "Worker": true, + "XMLHttpRequest": true, + "bodymovin": "write", + "clearInterval": true, + "console": true, + "define": true, + "document.body": true, + "document.createElement": true, + "document.createElementNS": true, + "document.getElementsByClassName": true, + "document.getElementsByTagName": true, + "document.querySelectorAll": true, + "document.readyState": true, + "location.origin": true, + "location.pathname": true, + "navigator": true, + "requestAnimationFrame": true, + "setInterval": true, + "setTimeout": true } }, - "ethers>@ethersproject/base64": { + "luxon": { "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/bytes": true + "Intl": true } }, - "ethers>@ethersproject/basex": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/properties": true + "@metamask/snaps-utils>marked": { + "globals": { + "console.error": true, + "console.warn": true, + "define": true } }, - "ethers>@ethersproject/constants": { + "ethereumjs-util>create-hash>md5.js": { "packages": { - "@ethersproject/bignumber": true + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "ethers>@ethersproject/json-wallets": { + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets>aes-js": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/pbkdf2": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/json-wallets>aes-js": { - "globals": { - "define": true + "react-markdown>remark-parse>mdast-util-from-markdown": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, + "react-syntax-highlighter>refractor>parse-entities": true, + "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true } }, - "ethers>@ethersproject/json-wallets>scrypt-js": { + "react-markdown>remark-rehype>mdast-util-to-hast": { "globals": { - "define": true, - "setTimeout": true + "console.warn": true }, "packages": { - "browserify>timers-browserify": true - } - }, - "ethers>@ethersproject/keccak256": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ethjs>js-sha3": true + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, + "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/logger": { + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { "globals": { - "console": true + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "https-browserify": true, + "process": true, + "stream-http": true, + "browserify>url": true, + "browserify>util": true } }, - "ethers>@ethersproject/pbkdf2": { + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/sha2": true + "react-syntax-highlighter>refractor>parse-entities": true } }, - "ethers>@ethersproject/properties": { + "crypto-browserify>diffie-hellman>miller-rabin": { "packages": { - "ethers>@ethersproject/logger": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true } }, - "ethers>@ethersproject/providers": { + "@ensdomains/content-hash>cids>multibase": { "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers>@ethersproject/networks": true, - "ethers>@ethersproject/providers>@ethersproject/web": true, - "ethers>@ethersproject/providers>bech32": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "ethers>@ethersproject/providers>@ethersproject/networks": { + "@ensdomains/content-hash>multihashes>multibase": { "packages": { - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "@ensdomains/content-hash>multicodec": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@ensdomains/content-hash>multicodec>uint8arrays": true, + "sass-embedded>varint": true } }, - "ethers>@ethersproject/random": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "console.warn": true, + "crypto.subtle.digest": true } }, - "ethers>@ethersproject/rlp": { + "@ensdomains/content-hash>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>multibase": true, + "@ensdomains/content-hash>multihashes>varint": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/sha2": { + "@ensdomains/content-hash>cids>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2>hash.js": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>cids>multihashes>varint": true } }, - "ethers>@ethersproject/sha2>hash.js": { - "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/signing-key": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ppom-validator>elliptic": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "@metamask/approval-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/solidity": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true + "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/strings": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true + "@metamask/notification-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/transactions": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/signing-key": true + "@metamask/permission-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/rpc-methods>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/units": { - "packages": { - "@ethersproject/bignumber": true, - "ethers>@ethersproject/logger": true + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/web": { + "@metamask/snaps-controllers>nanoid": { "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/wordlists": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@metamask/snaps-controllers-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream": { - "packages": { - "browserify>buffer": true, - "extension-port-stream>readable-stream": true + "depcheck>@vue/compiler-sfc>postcss>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream": { + "dependency-tree>precinct>detective-postcss>postcss>nanoid": { "globals": { - "AbortController": true, - "AggregateError": true, - "Blob": true - }, - "packages": { - "browserify>buffer": true, - "browserify>string_decoder": true, - "extension-port-stream>readable-stream>abort-controller": true, - "process": true, - "webpack>events": true + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream>abort-controller": { + "node-fetch": { "globals": { - "AbortController": true + "Headers": true, + "Request": true, + "Response": true, + "fetch": true } }, - "fast-json-patch": { + "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { "globals": { - "addEventListener": true, - "clearTimeout": true, - "removeEventListener": true, - "setTimeout": true + "fetch": true } }, - "fuse.js": { + "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { "globals": { - "console": true, - "define": true + "fetch": true } }, - "ganache>secp256k1": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": { "packages": { - "@metamask/ppom-validator>elliptic": true + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true + } + }, + "string.prototype.matchall>es-abstract>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@ngraveio/bc-ur>assert>object-is": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true } }, "gulp>vinyl-fs>object.assign": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, "string.prototype.matchall>call-bind": true, "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>has-symbols": true + "string.prototype.matchall>has-symbols": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "he": { - "globals": { - "define": true + "@metamask/object-multiplex>once": { + "packages": { + "@metamask/object-multiplex>once>wrappy": true } }, - "history": { + "crypto-browserify>public-encrypt>parse-asn1": { + "packages": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "browserify>buffer": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "crypto-browserify>pbkdf2": true + } + }, + "react-syntax-highlighter>refractor>parse-entities": { "globals": { - "console": true, - "define": true, - "document.defaultView": true, - "document.querySelector": true + "document.createElement": true } }, - "https-browserify": { + "path-browserify": { "packages": { - "browserify>url": true, - "stream-http": true + "process": true } }, - "koa>content-disposition>safe-buffer": { + "serve-handler>path-to-regexp": { "packages": { - "browserify>buffer": true + "serve-handler>path-to-regexp>isarray": true } }, - "koa>is-generator-function": { + "crypto-browserify>pbkdf2": { + "globals": { + "crypto": true, + "process": true, + "queueMicrotask": true, + "setImmediate": true, + "setTimeout": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true + "ethereumjs-util>create-hash": true, + "process": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "koa>is-generator-function>has-tostringtag": { - "packages": { - "string.prototype.matchall>has-symbols": true + "@material-ui/core>popper.js": { + "globals": { + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator": true, + "requestAnimationFrame": true, + "setTimeout": true } }, - "localforage": { + "react-tippy>popper.js": { "globals": { - "Blob": true, - "BlobBuilder": true, - "FileReader": true, - "IDBKeyRange": true, - "MSBlobBuilder": true, - "MozBlobBuilder": true, - "OIndexedDB": true, - "WebKitBlobBuilder": true, - "atob": true, - "btoa": true, - "console.error": true, - "console.info": true, + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, "console.warn": true, "define": true, - "fetch": true, - "indexedDB": true, - "localStorage": true, - "mozIndexedDB": true, - "msIndexedDB": true, - "navigator.platform": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, "navigator.userAgent": true, - "openDatabase": true, - "setTimeout": true, - "webkitIndexedDB": true + "requestAnimationFrame": true, + "setTimeout": true } }, - "lodash": { + "process": { "globals": { "clearTimeout": true, - "define": true, "setTimeout": true } }, - "loglevel": { + "promise-to-callback": { + "packages": { + "promise-to-callback>is-fn": true, + "promise-to-callback>set-immediate-shim": true + } + }, + "prop-types": { "globals": { - "console": true, - "define": true, - "document.cookie": true, - "localStorage": true, - "log": "write", - "navigator": true + "console": true + }, + "packages": { + "react>object-assign": true, + "prop-types>react-is": true } }, - "lottie-web": { + "react-markdown>property-information": { + "packages": { + "watchify>xtend": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { "globals": { - "Blob": true, - "Howl": true, - "OffscreenCanvas": true, - "URL.createObjectURL": true, - "Worker": true, - "XMLHttpRequest": true, - "bodymovin": "write", - "clearInterval": true, - "console": true, - "define": true, - "document.body": true, - "document.createElement": true, - "document.createElementNS": true, - "document.getElementsByClassName": true, - "document.getElementsByTagName": true, - "document.querySelectorAll": true, - "document.readyState": true, - "location.origin": true, - "location.pathname": true, - "navigator": true, - "requestAnimationFrame": true, - "setInterval": true, + "process": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true } }, - "luxon": { - "globals": { - "Intl": true + "crypto-browserify>public-encrypt": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "crypto-browserify>randombytes": true } }, - "nanoid": { + "browserify>punycode": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "define": true } }, - "nock>debug": { + "qrcode-generator": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "nock>debug>ms": true, - "process": true + "define": true } }, - "node-fetch": { + "qrcode.react": { "globals": { - "Headers": true, - "Request": true, - "Response": true, - "fetch": true + "Path2D": true, + "devicePixelRatio": true + }, + "packages": { + "react": true } }, - "path-browserify": { + "@storybook/addon-knobs>qs": { "packages": { - "process": true + "string.prototype.matchall>side-channel": true } }, - "process": { + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { "globals": { - "clearTimeout": true, - "setTimeout": true - } - }, - "promise-to-callback": { - "packages": { - "promise-to-callback>is-fn": true, - "promise-to-callback>set-immediate-shim": true + "queueMicrotask": true } }, - "promise-to-callback>set-immediate-shim": { + "react-beautiful-dnd>raf-schd": { "globals": { - "setTimeout.apply": true - }, - "packages": { - "browserify>timers-browserify": true + "cancelAnimationFrame": true, + "requestAnimationFrame": true } }, - "prop-types": { + "crypto-browserify>randombytes": { "globals": { - "console": true + "crypto": true, + "msCrypto": true }, "packages": { - "prop-types>react-is": true, - "react>object-assign": true - } - }, - "prop-types>react-is": { - "globals": { - "console": true + "process": true, + "koa>content-disposition>safe-buffer": true } }, - "qrcode-generator": { + "ethereumjs-wallet>randombytes": { "globals": { - "define": true + "crypto.getRandomValues": true } }, - "qrcode.react": { + "crypto-browserify>randomfill": { "globals": { - "Path2D": true, - "devicePixelRatio": true + "crypto": true, + "msCrypto": true }, "packages": { - "react": true + "process": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true } }, "react": { @@ -5027,8 +4863,8 @@ "console": true }, "packages": { - "prop-types": true, - "react>object-assign": true + "react>object-assign": true, + "prop-types": true } }, "react-beautiful-dnd": { @@ -5050,43 +4886,28 @@ }, "packages": { "@babel/runtime": true, - "react": true, "react-beautiful-dnd>css-box-model": true, "react-beautiful-dnd>memoize-one": true, "react-beautiful-dnd>raf-schd": true, - "react-beautiful-dnd>use-memo-one": true, + "react": true, "react-dom": true, "react-redux": true, - "redux": true + "redux": true, + "react-beautiful-dnd>use-memo-one": true } }, - "react-beautiful-dnd>css-box-model": { + "react-chartjs-2": { "globals": { - "getComputedStyle": true, - "pageXOffset": true, - "pageYOffset": true + "setTimeout": true }, "packages": { - "react-router-dom>tiny-invariant": true - } - }, - "react-beautiful-dnd>raf-schd": { - "globals": { - "cancelAnimationFrame": true, - "requestAnimationFrame": true - } - }, - "react-beautiful-dnd>use-memo-one": { - "packages": { + "chart.js": true, "react": true } }, - "react-chartjs-2": { - "globals": { - "setTimeout": true - }, + "react-focus-lock>react-clientside-effect": { "packages": { - "chart.js": true, + "@babel/runtime": true, "react": true } }, @@ -5131,22 +4952,28 @@ "trustedTypes": true }, "packages": { + "react>object-assign": true, "prop-types": true, "react": true, - "react-dom>scheduler": true, - "react>object-assign": true + "react-dom>scheduler": true } }, - "react-dom>scheduler": { + "react-responsive-carousel>react-easy-swipe": { "globals": { - "MessageChannel": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "console": true, - "navigator": true, - "performance": true, - "requestAnimationFrame": true, - "setTimeout": true + "addEventListener": true, + "define": true, + "document.addEventListener": true, + "document.removeEventListener": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-popper>react-fast-compare": { + "globals": { + "Element": true, + "console.warn": true } }, "react-focus-lock": { @@ -5160,666 +4987,726 @@ }, "packages": { "@babel/runtime": true, + "react-focus-lock>focus-lock": true, "prop-types": true, "react": true, - "react-focus-lock>focus-lock": true, "react-focus-lock>react-clientside-effect": true, "react-focus-lock>use-callback-ref": true, "react-focus-lock>use-sidecar": true } }, - "react-focus-lock>focus-lock": { + "react-idle-timer": { "globals": { - "HTMLIFrameElement": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.DOCUMENT_NODE": true, - "Node.DOCUMENT_POSITION_CONTAINED_BY": true, - "Node.DOCUMENT_POSITION_CONTAINS": true, - "Node.ELEMENT_NODE": true, - "console.error": true, + "clearTimeout": true, + "document": true, + "setTimeout": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-inspector": { + "globals": { + "Node": true, + "chromeDark": true, + "chromeLight": true + }, + "packages": { + "react": true + } + }, + "prop-types>react-is": { + "globals": { + "console": true + } + }, + "react-markdown>react-is": { + "globals": { + "console": true + } + }, + "react-redux>react-is": { + "globals": { + "console": true + } + }, + "react-markdown": { + "globals": { + "console.warn": true + }, + "packages": { + "react-markdown>comma-separated-tokens": true, + "prop-types": true, + "react-markdown>property-information": true, + "react": true, + "react-markdown>react-is": true, + "react-markdown>remark-parse": true, + "react-markdown>remark-rehype": true, + "react-markdown>space-separated-tokens": true, + "react-markdown>style-to-object": true, + "react-markdown>unified": true, + "react-markdown>unist-util-visit": true, + "react-markdown>vfile": true + } + }, + "react-popper": { + "globals": { + "document": true + }, + "packages": { + "@popperjs/core": true, + "react": true, + "react-popper>react-fast-compare": true, + "react-popper>warning": true + } + }, + "react-redux": { + "globals": { + "console": true, + "document": true + }, + "packages": { + "@babel/runtime": true, + "react-redux>hoist-non-react-statics": true, + "prop-types": true, + "react": true, + "react-dom": true, + "react-redux>react-is": true + } + }, + "react-responsive-carousel": { + "globals": { + "HTMLElement": true, + "addEventListener": true, + "clearTimeout": true, "console.warn": true, "document": true, - "getComputedStyle": true, + "getComputedStyle": true, + "removeEventListener": true, + "setTimeout": true + }, + "packages": { + "classnames": true, + "react": true, + "react-dom": true, + "react-responsive-carousel>react-easy-swipe": true + } + }, + "react-router-dom": { + "packages": { + "react-router-dom>history": true, + "prop-types": true, + "react": true, + "react-router-dom>react-router": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom-v5-compat": { + "globals": { + "FormData": true, + "URL": true, + "URLSearchParams": true, + "__reactRouterVersion": "write", + "addEventListener": true, + "confirm": true, + "define": true, + "document": true, + "history.scrollRestoration": true, + "location.href": true, + "removeEventListener": true, + "scrollTo": true, + "scrollY": true, + "sessionStorage.getItem": true, + "sessionStorage.setItem": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true + "react-router-dom-v5-compat>@remix-run/router": true, + "history": true, + "react": true, + "react-dom": true, + "react-router-dom": true, + "react-router-dom-v5-compat>react-router": true } }, - "react-focus-lock>react-clientside-effect": { + "react-router-dom>react-router": { "packages": { - "@babel/runtime": true, - "react": true + "react-router-dom>history": true, + "react-redux>hoist-non-react-statics": true, + "serve-handler>path-to-regexp": true, + "prop-types": true, + "react": true, + "prop-types>react-is": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true } }, - "react-focus-lock>use-callback-ref": { + "react-router-dom-v5-compat>react-router": { + "globals": { + "console.error": true, + "define": true + }, "packages": { + "react-router-dom-v5-compat>@remix-run/router": true, "react": true } }, - "react-focus-lock>use-sidecar": { + "react-simple-file-input": { "globals": { - "console.error": true + "File": true, + "FileReader": true, + "console.warn": true }, "packages": { - "@swc/helpers>tslib": true, - "react": true, - "react-focus-lock>use-sidecar>detect-node-es": true + "prop-types": true, + "react": true } }, - "react-idle-timer": { + "react-tippy": { "globals": { + "Element": true, + "MSStream": true, + "MutationObserver": true, + "addEventListener": true, "clearTimeout": true, + "console.error": true, + "console.warn": true, + "define": true, "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.maxTouchPoints": true, + "navigator.msMaxTouchPoints": true, + "navigator.userAgent": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "react-tippy>popper.js": true, + "react": true, + "react-dom": true } }, - "react-inspector": { + "react-toggle-button": { "globals": { - "Node": true, - "chromeDark": true, - "chromeLight": true + "clearTimeout": true, + "console.warn": true, + "define": true, + "performance": true, + "setTimeout": true }, "packages": { "react": true } }, - "react-markdown": { + "@material-ui/core>react-transition-group": { "globals": { - "console.warn": true + "Element": true, + "setTimeout": true }, "packages": { + "@material-ui/core>react-transition-group>dom-helpers": true, "prop-types": true, "react": true, - "react-markdown>comma-separated-tokens": true, - "react-markdown>property-information": true, - "react-markdown>react-is": true, - "react-markdown>remark-parse": true, - "react-markdown>remark-rehype": true, - "react-markdown>space-separated-tokens": true, - "react-markdown>style-to-object": true, - "react-markdown>unified": true, - "react-markdown>unist-util-visit": true, - "react-markdown>vfile": true + "react-dom": true } }, - "react-markdown>property-information": { + "readable-stream": { "packages": { - "watchify>xtend": true + "browserify>browser-resolve": true, + "browserify>buffer": true, + "webpack>events": true, + "pumpify>inherits": true, + "process": true, + "browserify>string_decoder": true, + "readable-stream>util-deprecate": true } }, - "react-markdown>react-is": { + "extension-port-stream>readable-stream": { "globals": { - "console": true - } - }, - "react-markdown>remark-parse": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, - "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true, - "react-syntax-highlighter>refractor>parse-entities": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { + "AbortController": true, + "AbortSignal": true, + "AggregateError": true, + "Blob": true, + "ERR_INVALID_ARG_TYPE": true, + "queueMicrotask": true + }, "packages": { - "react-syntax-highlighter>refractor>parse-entities": true + "@lavamoat/lavapack>readable-stream>abort-controller": true, + "browserify>buffer": true, + "webpack>events": true, + "process": true, + "browserify>string_decoder": true } }, - "react-markdown>remark-rehype": { + "@metamask/snaps-controllers>readable-web-to-node-stream": { "packages": { - "react-markdown>remark-rehype>mdast-util-to-hast": true + "readable-stream": true } }, - "react-markdown>remark-rehype>mdast-util-to-hast": { + "redux": { "globals": { - "console.warn": true + "console": true }, "packages": { - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, - "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, - "react-markdown>unist-util-visit": true + "@babel/runtime": true } }, - "react-markdown>style-to-object": { + "string.prototype.matchall>regexp.prototype.flags": { "packages": { - "react-markdown>style-to-object>inline-style-parser": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": true } }, - "react-markdown>unified": { + "react-markdown>remark-parse": { "packages": { - "mocha>yargs-unparser>is-plain-obj": true, - "react-markdown>unified>bail": true, - "react-markdown>unified>extend": true, - "react-markdown>unified>is-buffer": true, - "react-markdown>unified>trough": true, - "react-markdown>vfile": true + "react-markdown>remark-parse>mdast-util-from-markdown": true } }, - "react-markdown>unist-util-visit": { + "react-markdown>remark-rehype": { "packages": { - "react-markdown>unist-util-visit>unist-util-visit-parents": true + "react-markdown>remark-rehype>mdast-util-to-hast": true } }, - "react-markdown>unist-util-visit>unist-util-visit-parents": { + "react-markdown>vfile>replace-ext": { "packages": { - "react-markdown>unist-util-visit>unist-util-is": true + "path-browserify": true } }, - "react-markdown>vfile": { - "packages": { - "path-browserify": true, - "process": true, - "react-markdown>vfile>is-buffer": true, - "react-markdown>vfile>replace-ext": true, - "react-markdown>vfile>vfile-message": true + "reselect": { + "globals": { + "WeakRef": true, + "console.warn": true, + "unstable_autotrackMemoize": true } }, - "react-markdown>vfile>replace-ext": { + "@metamask/snaps-utils>rfdc": { "packages": { - "path-browserify": true + "browserify>buffer": true } }, - "react-markdown>vfile>vfile-message": { + "ethereumjs-util>create-hash>ripemd160": { "packages": { - "react-markdown>vfile>unist-util-stringify-position": true + "browserify>buffer": true, + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true } }, - "react-popper": { - "globals": { - "document": true - }, + "@keystonehq/metamask-airgapped-keyring>rlp": { "packages": { - "@popperjs/core": true, - "react": true, - "react-popper>react-fast-compare": true, - "react-popper>warning": true - } - }, - "react-popper>react-fast-compare": { - "globals": { - "Element": true, - "console.warn": true + "bn.js": true, + "browserify>buffer": true } }, - "react-popper>warning": { + "eth-lattice-keyring>rlp": { "globals": { - "console": true + "TextEncoder": true } }, - "react-redux": { - "globals": { - "console": true, - "document": true - }, + "ethereumjs-util>rlp": { "packages": { - "@babel/runtime": true, - "prop-types": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true, - "react-redux>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>hoist-non-react-statics": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { "packages": { - "prop-types>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>react-is": { + "wait-on>rxjs": { "globals": { - "console": true + "cancelAnimationFrame": true, + "clearInterval": true, + "clearTimeout": true, + "performance": true, + "requestAnimationFrame": true, + "setInterval.apply": true, + "setTimeout.apply": true } }, - "react-responsive-carousel": { + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, + "react-dom>scheduler": { "globals": { - "HTMLElement": true, - "addEventListener": true, + "MessageChannel": true, + "cancelAnimationFrame": true, "clearTimeout": true, - "console.warn": true, - "document": true, - "getComputedStyle": true, - "removeEventListener": true, + "console": true, + "navigator": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true - }, - "packages": { - "classnames": true, - "react": true, - "react-dom": true, - "react-responsive-carousel>react-easy-swipe": true } }, - "react-responsive-carousel>react-easy-swipe": { + "ethers>@ethersproject/json-wallets>scrypt-js": { "globals": { - "addEventListener": true, "define": true, - "document.addEventListener": true, - "document.removeEventListener": true + "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "browserify>timers-browserify": true } }, - "react-router-dom": { + "ganache>secp256k1": { "packages": { - "prop-types": true, - "react": true, - "react-router-dom>history": true, - "react-router-dom>react-router": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "@metamask/ppom-validator>elliptic": true } }, - "react-router-dom-v5-compat": { + "semver": { "globals": { - "FormData": true, - "URL": true, - "URLSearchParams": true, - "__reactRouterVersion": "write", - "addEventListener": true, - "confirm": true, - "define": true, - "document": true, - "history.scrollRestoration": true, - "location.href": true, - "removeEventListener": true, - "scrollTo": true, - "scrollY": true, - "sessionStorage.getItem": true, - "sessionStorage.setItem": true, - "setTimeout": true + "console.error": true }, "packages": { - "history": true, - "react": true, - "react-dom": true, - "react-router-dom": true, - "react-router-dom-v5-compat>@remix-run/router": true, - "react-router-dom-v5-compat>react-router": true + "process": true } }, - "react-router-dom-v5-compat>@remix-run/router": { - "globals": { - "AbortController": true, - "DOMException": true, - "FormData": true, - "Headers": true, - "Request": true, - "Response": true, - "URL": true, - "URLSearchParams": true, - "console": true, - "document.defaultView": true + "string.prototype.matchall>call-bind>set-function-length": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>gopd": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true } }, - "react-router-dom-v5-compat>react-router": { - "globals": { - "console.error": true, - "define": true - }, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": { "packages": { - "react": true, - "react-router-dom-v5-compat>@remix-run/router": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true } }, - "react-router-dom>history": { + "promise-to-callback>set-immediate-shim": { "globals": { - "addEventListener": true, - "confirm": true, - "document": true, - "history": true, - "location": true, - "navigator.userAgent": true, - "removeEventListener": true + "setTimeout.apply": true }, "packages": { - "react-router-dom>history>resolve-pathname": true, - "react-router-dom>history>value-equal": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "browserify>timers-browserify": true } }, - "react-router-dom>react-router": { + "addons-linter>sha.js": { "packages": { - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-redux>hoist-non-react-statics": true, - "react-router-dom>history": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true, - "serve-handler>path-to-regexp": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "react-router-dom>tiny-warning": { - "globals": { - "console": true + "string.prototype.matchall>side-channel": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>object-inspect": true } }, - "react-simple-file-input": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "File": true, - "FileReader": true, + "console.error": true, "console.warn": true }, "packages": { - "prop-types": true, - "react": true + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "ethers": true, + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true } }, - "react-syntax-highlighter>refractor>parse-entities": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { "globals": { - "document.createElement": true + "StopIteration": true + }, + "packages": { + "string.prototype.matchall>internal-slot": true } }, - "react-tippy": { + "stream-browserify": { + "packages": { + "webpack>events": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "stream-http": { "globals": { - "Element": true, - "MSStream": true, - "MutationObserver": true, - "addEventListener": true, + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, "clearTimeout": true, - "console.error": true, - "console.warn": true, - "define": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.maxTouchPoints": true, - "navigator.msMaxTouchPoints": true, - "navigator.userAgent": true, - "performance": true, - "requestAnimationFrame": true, + "fetch": true, + "location.protocol.search": true, "setTimeout": true }, "packages": { - "react": true, - "react-dom": true, - "react-tippy>popper.js": true + "browserify>buffer": true, + "stream-http>builtin-status-codes": true, + "pumpify>inherits": true, + "process": true, + "readable-stream": true, + "browserify>url": true, + "watchify>xtend": true } }, - "react-tippy>popper.js": { - "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.userAgent": true, - "requestAnimationFrame": true, - "setTimeout": true + "@metamask/snaps-controllers>tar-stream>streamx": { + "packages": { + "webpack>events": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true } }, - "react-toggle-button": { - "globals": { - "clearTimeout": true, - "console.warn": true, - "define": true, - "performance": true, - "setTimeout": true - }, + "browserify>string_decoder": { "packages": { - "react": true + "koa>content-disposition>safe-buffer": true } }, - "readable-stream": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": { "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>string_decoder": true, - "process": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true } }, - "readable-stream>util-deprecate": { - "globals": { - "console.trace": true, - "console.warn": true, - "localStorage": true + "react-markdown>style-to-object": { + "packages": { + "react-markdown>style-to-object>inline-style-parser": true } }, - "redux": { - "globals": { - "console": true - }, + "@metamask/snaps-controllers>tar-stream": { "packages": { - "@babel/runtime": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "browserify>browser-resolve": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true } }, - "semver": { + "debounce-stream>through": { + "packages": { + "process": true, + "stream-browserify": true + } + }, + "browserify>timers-browserify": { "globals": { - "console.error": true + "clearInterval": true, + "clearTimeout": true, + "setInterval": true, + "setTimeout": true }, "packages": { "process": true } }, - "serve-handler>path-to-regexp": { - "packages": { - "serve-handler>path-to-regexp>isarray": true + "react-router-dom>tiny-warning": { + "globals": { + "console": true } }, - "stream-browserify": { - "packages": { - "pumpify>inherits": true, - "readable-stream": true, - "webpack>events": true + "copy-to-clipboard>toggle-selection": { + "globals": { + "document.activeElement": true, + "document.getSelection": true + } + }, + "tslib": { + "globals": { + "SuppressedError": true, + "define": true } }, - "stream-http": { + "@metamask/eth-sig-util>tweetnacl": { "globals": { - "AbortController": true, - "Blob": true, - "MSStreamReader": true, - "ReadableStream": true, - "WritableStream": true, - "XDomainRequest": true, - "XMLHttpRequest": true, - "clearTimeout": true, - "fetch": true, - "location.protocol.search": true, - "setTimeout": true + "crypto": true, + "msCrypto": true, + "nacl": "write" }, "packages": { - "browserify>buffer": true, - "browserify>url": true, - "process": true, - "pumpify>inherits": true, - "readable-stream": true, - "stream-http>builtin-status-codes": true, - "watchify>xtend": true + "browserify>browser-resolve": true } }, - "string.prototype.matchall>call-bind": { - "packages": { - "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>call-bind>set-function-length": true, - "string.prototype.matchall>get-intrinsic": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>call-bind>es-define-property": { + "@ensdomains/content-hash>cids>uint8arrays": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>cids>multibase": true } }, - "string.prototype.matchall>call-bind>set-function-length": { + "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>gopd": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true, - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "string.prototype.matchall>define-properties": { + "react-markdown>unified": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true } }, - "string.prototype.matchall>define-properties>define-data-property": { + "react-markdown>unist-util-visit>unist-util-visit-parents": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true + "react-markdown>unist-util-visit>unist-util-is": true } }, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "react-markdown>unist-util-visit": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true + "react-markdown>unist-util-visit>unist-util-visit-parents": true } }, - "string.prototype.matchall>es-abstract>available-typed-arrays": { - "packages": { - "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true + "uri-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "browserify>url": { "packages": { - "string.prototype.matchall>has-symbols": true + "browserify>punycode": true, + "@storybook/addon-knobs>qs": true } }, - "string.prototype.matchall>es-abstract>gopd": { + "react-focus-lock>use-callback-ref": { "packages": { - "string.prototype.matchall>get-intrinsic": true + "react": true } }, - "string.prototype.matchall>es-abstract>has-property-descriptors": { + "react-beautiful-dnd>use-memo-one": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true + "react": true } }, - "string.prototype.matchall>es-abstract>is-array-buffer": { + "react-focus-lock>use-sidecar": { + "globals": { + "console.error": true + }, "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "react-focus-lock>use-sidecar>detect-node-es": true, + "react": true, + "tslib": true } }, - "string.prototype.matchall>es-abstract>is-callable": { + "readable-stream>util-deprecate": { "globals": { - "document": true + "console.trace": true, + "console.warn": true, + "localStorage": true } }, - "string.prototype.matchall>es-abstract>is-regex": { + "browserify>assert>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true, + "process": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "browserify>assert>util>inherits": true, + "process": true } }, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": { + "browserify>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true + }, "packages": { - "string.prototype.matchall>call-bind": true + "pumpify>inherits": true, + "browserify>util>is-arguments": true, + "koa>is-generator-function": true, + "browserify>util>is-typed-array": true, + "process": true, + "browserify>util>which-typed-array": true } }, - "string.prototype.matchall>es-abstract>object-inspect": { + "uuid": { "globals": { - "HTMLElement": true, - "WeakRef": true - }, - "packages": { - "browserify>browser-resolve": true + "crypto": true, + "msCrypto": true } }, - "string.prototype.matchall>get-intrinsic": { + "@metamask/eth-snap-keyring>uuid": { "globals": { - "AggregateError": true, - "FinalizationRegistry": true, - "WeakRef": true - }, - "packages": { - "browserify>has>function-bind": true, - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>has-proto": true, - "string.prototype.matchall>has-symbols": true + "crypto": true } }, - "string.prototype.matchall>internal-slot": { - "packages": { - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>side-channel": true + "@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": true + "eth-lattice-keyring>gridplus-sdk>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": { - "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "web3-stream-provider>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>side-channel": { + "@metamask/snaps-utils>validate-npm-package-name": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>object-inspect": true, - "string.prototype.matchall>get-intrinsic": true + "@metamask/snaps-utils>validate-npm-package-name>builtins": true } }, - "superstruct": { - "globals": { - "console.warn": true, - "define": true + "react-markdown>vfile>vfile-message": { + "packages": { + "react-markdown>vfile>unist-util-stringify-position": true } }, - "terser>source-map-support>buffer-from": { + "react-markdown>vfile": { "packages": { - "browserify>buffer": true + "react-markdown>vfile>is-buffer": true, + "path-browserify": true, + "process": true, + "react-markdown>vfile>replace-ext": true, + "react-markdown>vfile>vfile-message": true } }, - "uri-js": { + "browserify>vm-browserify": { "globals": { - "define": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true } }, - "uuid": { + "react-popper>warning": { "globals": { - "crypto": true, - "msCrypto": true + "console": true } }, - "wait-on>rxjs": { + "@ensdomains/content-hash>multihashes>web-encoding": { "globals": { - "cancelAnimationFrame": true, - "clearInterval": true, - "clearTimeout": true, - "performance": true, - "requestAnimationFrame": true, - "setInterval.apply": true, - "setTimeout.apply": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>util": true } }, "web3": { @@ -5832,14 +5719,14 @@ "setTimeout": true }, "packages": { - "browserify>util": true, "readable-stream": true, + "browserify>util": true, "web3-stream-provider>uuid": true } }, - "web3-stream-provider>uuid": { + "@metamask/controllers>web3": { "globals": { - "crypto": true + "XMLHttpRequest": true } }, "webextension-polyfill": { @@ -5851,9 +5738,30 @@ "define": true } }, - "webpack>events": { - "globals": { - "console": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, + "eslint-plugin-react>array-includes>is-string": true, + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + } + }, + "@metamask/eth-token-tracker>deep-equal>which-collection": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + } + }, + "browserify>util>which-typed-array": { + "packages": { + "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind": true, + "browserify>util>which-typed-array>for-each": true, + "string.prototype.matchall>es-abstract>gopd": true, + "koa>is-generator-function>has-tostringtag": true } } } diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 30456a0bd61d..e029e41783d0 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -5,144 +5,124 @@ "regeneratorRuntime": "write" } }, - "@ensdomains/content-hash": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { "globals": { - "console.warn": true + "WeakRef": true }, "packages": { - "@ensdomains/content-hash>cids": true, - "@ensdomains/content-hash>js-base64": true, - "@ensdomains/content-hash>multicodec": true, - "@ensdomains/content-hash>multihashes": true, - "browserify>buffer": true + "browserify": true } }, - "@ensdomains/content-hash>cids": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec": true + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true } }, - "@ensdomains/content-hash>cids>multibase": { + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "SuppressedError": true + } + }, + "@ensdomains/content-hash": { + "globals": { + "console.warn": true }, "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true + "browserify>buffer": true, + "@ensdomains/content-hash>cids": true, + "@ensdomains/content-hash>js-base64": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>multihashes": true } }, - "@ensdomains/content-hash>cids>multihashes": { + "@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>cids>uint8arrays": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true } }, - "@ensdomains/content-hash>js-base64": { - "globals": { - "Base64": "write", - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true, - "define": true - }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays": true, - "sass-embedded>varint": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays": { + "@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "Buffer": true, - "TextDecoder": true, "TextEncoder": true - }, - "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "TextDecoder": true, - "TextEncoder": true, - "console.warn": true, - "crypto.subtle.digest": true - } - }, - "@ensdomains/content-hash>multihashes": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase": true, - "@ensdomains/content-hash>multihashes>varint": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase>base-x": { + "@ethereumjs/tx": { "packages": { - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, - "@ensdomains/content-hash>multihashes>web-encoding": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { - "browserify>util": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@ethereumjs/tx": { + "eth-lattice-keyring>@ethereumjs/tx": { "packages": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethersproject/providers": true, "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true } }, - "@ethereumjs/tx>@ethereumjs/common": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, "browserify>buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/common>crc-32": { - "globals": { - "DO_NOT_EXPORT_CRC": true, - "define": true - } - }, - "@ethereumjs/tx>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -150,88 +130,84 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true, "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { - "Headers": true, - "TextDecoder": true, - "URL": true, - "btoa": true, + "console.warn": true, "fetch": true }, "packages": { - "browserify>browserify-zlib": true, - "browserify>buffer": true, - "browserify>url": true, - "browserify>util": true, - "https-browserify": true, - "process": true, - "stream-http": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true } }, - "@ethereumjs/tx>ethereum-cryptography": { + "@ethersproject/abi": { "globals": { - "TextDecoder": true, - "crypto": true + "console.log": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { + "ethers>@ethersproject/abstract-signer": { "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/address": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/rlp": true } }, - "@ethersproject/abi": { + "ethers>@ethersproject/base64": { "globals": { - "console.log": true + "atob": true, + "btoa": true }, "packages": { - "@ethersproject/bignumber": true, + "@ethersproject/bytes": true + } + }, + "ethers>@ethersproject/basex": { + "packages": { "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "ethers>@ethersproject/properties": true } }, "@ethersproject/bignumber": { "packages": { "@ethersproject/bytes": true, - "bn.js": true, - "ethers>@ethersproject/logger": true + "ethers>@ethersproject/logger": true, + "bn.js": true } }, "@ethersproject/bytes": { @@ -239,17 +215,22 @@ "ethers>@ethersproject/logger": true } }, + "ethers>@ethersproject/constants": { + "packages": { + "@ethersproject/bignumber": true + } + }, "@ethersproject/contracts": { "globals": { "setTimeout": true }, "packages": { "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/abstract-provider": true, "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/transactions": true @@ -257,10 +238,10 @@ }, "@ethersproject/hash": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/address": true, "ethers>@ethersproject/base64": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, @@ -269,9 +250,9 @@ }, "@ethersproject/hdnode": { "packages": { + "ethers>@ethersproject/basex": true, "@ethersproject/bignumber": true, "@ethersproject/bytes": true, - "ethers>@ethersproject/basex": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, @@ -282,1147 +263,1033 @@ "ethers>@ethersproject/wordlists": true } }, - "@ethersproject/providers": { - "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "ethers>@ethersproject/json-wallets": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/networks": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true - } - }, - "@ethersproject/providers>@ethersproject/random": { - "globals": { - "crypto.getRandomValues": true + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/json-wallets>aes-js": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true } }, - "@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "ethers>@ethersproject/keccak256": { "packages": { "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "eth-ens-namehash>js-sha3": true } }, - "@ethersproject/wallet": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/transactions": true + "ethers>@ethersproject/logger": { + "globals": { + "console": true } }, - "@keystonehq/bc-ur-registry-eth": { + "ethers>@ethersproject/providers>@ethersproject/networks": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { - "globals": { - "define": true - }, + "@metamask/test-bundler>@ethersproject/networks": { "packages": { - "@ngraveio/bc-ur": true, - "@swc/helpers>tslib": true, - "browserify>buffer": true, - "buffer": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/metamask-airgapped-keyring": { + "ethers>@ethersproject/pbkdf2": { "packages": { - "@ethereumjs/tx": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, - "@metamask/obs-store": true, - "browserify>buffer": true, - "ethereumjs-util>rlp": true, - "uuid": true, - "webpack>events": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/sha2": true } }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { + "ethers>@ethersproject/properties": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { - "globals": { - "TextEncoder": true + "ethers>@ethersproject/logger": true } }, - "@lavamoat/lavadome-react": { + "@ethersproject/providers": { "globals": { - "Document.prototype": true, - "DocumentFragment.prototype": true, - "Element.prototype": true, - "Node.prototype": true, + "WebSocket": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, "console.warn": true, - "document": true + "setInterval": true, + "setTimeout": true }, "packages": { - "react": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "@metamask/test-bundler>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/providers>bech32": true } }, - "@material-ui/core": { + "ethers>@ethersproject/providers": { "globals": { - "Image": true, - "_formatMuiErrorMessage": true, - "addEventListener": true, + "WebSocket": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "getComputedStyle": true, - "getSelection": true, - "innerHeight": true, - "innerWidth": true, - "matchMedia": true, - "navigator": true, - "performance.now": true, - "removeEventListener": true, - "requestAnimationFrame": true, "setInterval": true, "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles": true, - "@material-ui/core>@material-ui/system": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "@material-ui/core>popper.js": true, - "@material-ui/core>react-transition-group": true, - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/providers>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/providers>bech32": true } }, - "@material-ui/core>@material-ui/styles": { + "@ethersproject/providers>@ethersproject/random": { "globals": { - "console.error": true, - "console.warn": true, - "document.createComment": true, - "document.head": true - }, - "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, - "@material-ui/core>@material-ui/styles>jss-plugin-global": true, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, - "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "prop-types": true, - "react": true, - "react-redux>hoist-non-react-statics": true + "crypto.getRandomValues": true } }, - "@material-ui/core>@material-ui/styles>jss": { - "globals": { - "CSS": true, - "document.createElement": true, - "document.querySelector": true - }, + "ethers>@ethersproject/random": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { + "ethers>@ethersproject/rlp": { "packages": { - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { - "globals": { - "CSS": true - }, + "ethers>@ethersproject/sha2": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2>hash.js": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-global": { + "ethers>@ethersproject/signing-key": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/signing-key>elliptic": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": { + "ethers>@ethersproject/solidity": { "packages": { - "@babel/runtime": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { + "ethers>@ethersproject/strings": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { + "ethers>@ethersproject/transactions": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/signing-key": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { - "globals": { - "document.createElement": true, - "document.documentElement": true, - "getComputedStyle": true - }, + "ethers>@ethersproject/units": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true + "@ethersproject/bignumber": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": { - "globals": { - "document": true + "@ethersproject/wallet": { + "packages": { + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/transactions": true } }, - "@material-ui/core>@material-ui/system": { + "@ethersproject/providers>@ethersproject/web": { "globals": { - "console.error": true + "clearTimeout": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/utils": true, - "prop-types": true - } - }, - "@material-ui/core>@material-ui/utils": { - "packages": { - "@babel/runtime": true, - "prop-types": true, - "prop-types>react-is": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>popper.js": { + "ethers>@ethersproject/providers>@ethersproject/web": { "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator": true, - "requestAnimationFrame": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>react-transition-group": { + "ethers>@ethersproject/web": { "globals": { - "Element": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true }, "packages": { - "@material-ui/core>react-transition-group>dom-helpers": true, - "prop-types": true, - "react": true, - "react-dom": true - } - }, - "@material-ui/core>react-transition-group>dom-helpers": { - "packages": { - "@babel/runtime": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask/abi-utils": { + "ethers>@ethersproject/wordlists": { "packages": { - "@metamask/abi-utils>@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask/abi-utils>@metamask/utils": { + "@metamask/notification-services-controller>firebase>@firebase/app": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "FinalizationRegistry": true, + "console.warn": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/accounts-controller": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/eth-snap-keyring": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/utils": true, - "uuid": true - } - }, - "@metamask/address-book-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask/announcement-controller": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { "packages": { - "@metamask/announcement-controller>@metamask/base-controller": true + "@metamask/notification-services-controller>firebase>@firebase/util": true } }, - "@metamask/announcement-controller>@metamask/base-controller": { + "@metamask/notification-services-controller>firebase>@firebase/installations": { "globals": { + "BroadcastChannel": true, + "Headers": true, + "btoa": true, + "console.error": true, + "crypto": true, + "fetch": true, + "msCrypto": true, + "navigator.onLine": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask/approval-controller": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { "globals": { - "console.info": true + "console": true }, "packages": { - "@metamask/approval-controller>nanoid": true, - "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "tslib": true } }, - "@metamask/approval-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/assets-controllers": { + "@metamask/notification-services-controller>firebase>@firebase/messaging": { "globals": { - "AbortController": true, "Headers": true, + "Notification.maxActions": true, + "Notification.permission": true, + "Notification.requestPermission": true, + "PushSubscription.prototype.hasOwnProperty": true, + "ServiceWorkerRegistration": true, "URL": true, - "URLSearchParams": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setInterval": true, + "addEventListener": true, + "atob": true, + "btoa": true, + "clients.matchAll": true, + "clients.openWindow": true, + "console.warn": true, + "document": true, + "fetch": true, + "indexedDB": true, + "location.href": true, + "location.origin": true, + "navigator": true, + "origin.replace": true, + "registration.showNotification": true, "setTimeout": true }, "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, - "@metamask/assets-controllers>@metamask/polling-controller": true, - "@metamask/base-controller": true, - "@metamask/contract-metadata": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "bn.js": true, - "cockatiel": true, - "ethers>@ethersproject/address": true, - "lodash": true, - "single-call-balance-checker-abi": true, - "uuid": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/installations": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, + "tslib": true } }, - "@metamask/assets-controllers>@metamask/polling-controller": { + "@metamask/notification-services-controller>firebase>@firebase/util": { "globals": { - "clearTimeout": true, - "console.error": true, + "atob": true, + "browser": true, + "btoa": true, + "chrome": true, + "console": true, + "document": true, + "indexedDB": true, + "navigator": true, + "process": true, + "self": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "process": true } }, - "@metamask/base-controller": { - "globals": { - "setTimeout": true - }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { - "immer": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "eth-lattice-keyring>rlp": true, + "uuid": true } }, - "@metamask/browser-passworder": { - "globals": { - "CryptoKey": true, - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, + "@keystonehq/bc-ur-registry-eth": { "packages": { - "@metamask/browser-passworder>@metamask/utils": true, - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "uuid": true } }, - "@metamask/browser-passworder>@metamask/utils": { + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "define": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ngraveio/bc-ur": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "buffer": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "tslib": true } }, - "@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "@keystonehq/metamask-airgapped-keyring": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, + "@ethereumjs/tx": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, + "@keystonehq/bc-ur-registry-eth": true, + "@metamask/obs-store": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true + "webpack>events": true, + "@keystonehq/metamask-airgapped-keyring>rlp": true, + "uuid": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser": { + "chart.js>@kurkle/color": { "globals": { - "console.error": true, - "console.log": true + "define": true + } + }, + "@lavamoat/lavadome-react": { + "globals": { + "Document.prototype": true, + "DocumentFragment.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "react": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { "packages": { - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true } }, - "@metamask/controllers>web3": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { "globals": { - "XMLHttpRequest": true + "console.warn": true } }, - "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { - "globals": { - "fetch": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { + "packages": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true } }, - "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { "globals": { - "fetch": true + "console.warn": true + }, + "packages": { + "@ethersproject/abi": true, + "ethers>@ethersproject/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, + "browserify>buffer": true, + "semver": true } }, - "@metamask/ens-controller": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { + "globals": { + "console.warn": true + }, "packages": { - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/ens-controller>@metamask/base-controller": true, - "@metamask/ens-controller>@metamask/utils": true, - "punycode": true + "wait-on>rxjs": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { + "globals": { + "__ledgerLogsListen": "write", + "console.error": true } }, - "@metamask/ens-controller>@metamask/base-controller": { + "@material-ui/core": { "globals": { + "Image": true, + "_formatMuiErrorMessage": true, + "addEventListener": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "getSelection": true, + "innerHeight": true, + "innerWidth": true, + "matchMedia": true, + "navigator": true, + "performance.now": true, + "removeEventListener": true, + "requestAnimationFrame": true, + "setInterval": true, "setTimeout": true }, "packages": { - "immer": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles": true, + "@material-ui/core>@material-ui/system": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>popper.js": true, + "prop-types": true, + "react": true, + "react-dom": true, + "prop-types>react-is": true, + "@material-ui/core>react-transition-group": true } }, - "@metamask/ens-controller>@metamask/utils": { + "@material-ui/core>@material-ui/styles": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "console.error": true, + "console.warn": true, + "document.createComment": true, + "document.head": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, + "@material-ui/core>@material-ui/styles>jss-plugin-global": true, + "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, + "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, + "@material-ui/core>@material-ui/styles>jss": true, + "prop-types": true, + "react": true } }, - "@metamask/eth-json-rpc-filters": { + "@material-ui/core>@material-ui/system": { "globals": { "console.error": true }, "packages": { - "@metamask/eth-query": true, - "@metamask/json-rpc-engine": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "prop-types": true } }, - "@metamask/eth-json-rpc-middleware": { - "globals": { - "URL": true, - "console.error": true, - "setTimeout": true - }, + "@material-ui/core>@material-ui/utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true + "@babel/runtime": true, + "prop-types": true, + "prop-types>react-is": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "@metamask/abi-utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-json-rpc-provider": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring": { - "globals": { - "addEventListener": true, - "console.error": true, - "document.createElement": true, - "document.head.appendChild": true, - "fetch": true, - "removeEventListener": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, - "@metamask/eth-sig-util": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { - "globals": { - "console.warn": true - }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, - "browserify>buffer": true, - "ethers>@ethersproject/rlp": true, - "semver": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { + "@metamask/accounts-controller": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/base-controller": true, + "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/accounts-controller>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "uuid": true + } + }, + "@metamask/address-book-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true + } + }, + "@metamask/announcement-controller": { "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true + "@metamask/announcement-controller>@metamask/base-controller": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { + "@metamask/approval-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { - "globals": { - "console.warn": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { - "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true, - "@metamask/ppom-validator>crypto-js": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { - "globals": { - "console.warn": true + "console.info": true }, "packages": { - "wait-on>rxjs": true + "@metamask/base-controller": true, + "@metamask/rpc-errors": true, + "nanoid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { + "@metamask/assets-controllers": { "globals": { - "Blob": true, - "FormData": true, + "AbortController": true, + "Headers": true, + "URL": true, "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.log": true, + "setInterval": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { - "packages": { - "@ethersproject/abi": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "ethers>@ethersproject/address": true, "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, "@ethersproject/providers": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/wordlists": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { - "globals": { - "__ledgerLogsListen": "write", - "console.error": true + "@metamask/abi-utils": true, + "@metamask/base-controller": true, + "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/metamask-eth-abis": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "cockatiel": true, + "lodash": true, + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, + "single-call-balance-checker-abi": true, + "uuid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { + "@metamask/base-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true + "immer": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "@metamask/announcement-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "define": true - } - }, - "@metamask/eth-query": { - "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "watchify>xtend": true - } - }, - "@metamask/eth-sig-util": { + "setTimeout": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "immer": true } }, - "@metamask/eth-sig-util>@metamask/utils": { + "@metamask/name-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, - "@metamask/eth-sig-util>tweetnacl": { + "@metamask/rate-limit-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "msCrypto": true, - "nacl": "write" + "setTimeout": true }, "packages": { - "browserify>browser-resolve": true + "immer": true } }, - "@metamask/eth-snap-keyring": { + "@metamask/browser-passworder": { "globals": { - "URL": true, - "console.error": true + "CryptoKey": true, + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true }, "packages": { - "@ethereumjs/tx": true, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/eth-snap-keyring>uuid": true, - "@metamask/keyring-api": true, - "@metamask/utils>@metamask/superstruct": true, - "webpack>events": true + "@metamask/browser-passworder>@metamask/utils": true, + "browserify>buffer": true } }, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "eth-keyring-controller>@metamask/browser-passworder": { + "globals": { + "crypto": true } }, - "@metamask/eth-snap-keyring>@metamask/utils": { + "@metamask/controller-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/controller-utils>@metamask/ethjs-unit": true, + "@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "bn.js": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "eth-ens-namehash": true, + "eslint>fast-deep-equal": true } }, - "@metamask/eth-snap-keyring>uuid": { - "globals": { - "crypto": true + "@metamask/ens-controller": { + "packages": { + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/utils": true, + "punycode": true } }, - "@metamask/eth-token-tracker": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { "globals": { - "console.warn": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, - "@metamask/eth-token-tracker>deep-equal": true, - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, "@metamask/safe-event-emitter": true, - "bn.js": true, - "human-standard-token-abi": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true, + "pify": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { + "@metamask/network-controller>@metamask/eth-block-tracker": { "globals": { "clearTimeout": true, "console.error": true, "setTimeout": true }, "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, "@metamask/safe-event-emitter": true, - "pify": true + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/scure-bip39": true, + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@metamask/eth-token-tracker>deep-equal": { - "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, - "@metamask/eth-token-tracker>deep-equal>is-date-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, - "@metamask/eth-token-tracker>deep-equal>which-collection": true, - "@ngraveio/bc-ur>assert>object-is": true, - "browserify>util>is-arguments": true, - "browserify>util>which-typed-array": true, - "gulp>vinyl-fs>object.assign": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true, - "string.prototype.matchall>es-abstract>is-regex": true, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>regexp.prototype.flags": true, - "string.prototype.matchall>side-channel": true + "@metamask/eth-json-rpc-filters": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/safe-event-emitter": true, + "@metamask/name-controller>async-mutex": true, + "pify": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "fetch": true, + "setTimeout": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true, - "browserify>util>is-arguments": true, - "eslint-plugin-react>array-includes>is-string": true, - "process": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>has-symbols": true + "@metamask/eth-json-rpc-provider": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { + "@metamask/eth-json-rpc-middleware": { "globals": { - "StopIteration": true + "URL": true, + "console.error": true, + "setTimeout": true }, "packages": { - "string.prototype.matchall>internal-slot": true + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/eth-json-rpc-middleware>klona": true, + "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true } }, - "@metamask/eth-token-tracker>deep-equal>is-date-object": { + "@metamask/eth-json-rpc-provider": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "uuid": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "@metamask/eth-ledger-bridge-keyring": { + "globals": { + "addEventListener": true, + "console.error": true, + "document.createElement": true, + "document.head.appendChild": true, + "fetch": true, + "removeEventListener": true + }, "packages": { - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, - "eslint-plugin-react>array-includes>is-string": true, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { + "@metamask/controller-utils>@metamask/eth-query": { "packages": { - "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true + "@metamask/ppom-validator>json-rpc-random-id": true, + "watchify>xtend": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/abi-utils": true, + "@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring": { - "globals": { - "setTimeout": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { + "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-sig-util": true, - "@swc/helpers>tslib": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>hdkey": { + "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { - "browserify>assert": true, - "crypto-browserify": true, - "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/etherscan-link": { - "globals": { - "URL": true + "@metamask/keyring-controller>@metamask/eth-simple-keyring": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "crypto-browserify>randombytes": true } }, - "@metamask/ethjs": { + "@metamask/eth-snap-keyring": { "globals": { - "clearInterval": true, - "setInterval": true + "URL": true, + "console.error": true, + "console.info": true }, "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-provider-http": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/number-to-bn": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "@ethereumjs/tx": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, + "@metamask/keyring-api": true, + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "webpack>events": true, + "@metamask/eth-snap-keyring>uuid": true } }, - "@metamask/ethjs-contract": { + "@metamask/eth-token-tracker": { + "globals": { + "console.warn": true + }, "packages": { "@babel/runtime": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "promise-to-callback": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true, + "@metamask/safe-event-emitter": true, + "bn.js": true, + "@metamask/eth-token-tracker>deep-equal": true, + "human-standard-token-abi": true } }, - "@metamask/ethjs-query": { + "@metamask/eth-trezor-keyring": { "globals": { - "console": true + "setTimeout": true }, "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format": true, - "@metamask/ethjs-query>@metamask/ethjs-rpc": true, - "promise-to-callback": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@trezor/connect-web": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/ethjs-query>@metamask/ethjs-format": { - "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "@metamask/ethjs>@metamask/number-to-bn": true + "@metamask/etherscan-link": { + "globals": { + "URL": true } }, - "@metamask/ethjs-query>@metamask/ethjs-rpc": { + "eth-method-registry>@metamask/ethjs-contract": { "packages": { + "@babel/runtime": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": true, + "eth-ens-namehash>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs>@metamask/ethjs-filter": { + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { "globals": { "clearInterval": true, "setInterval": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { "packages": { - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": true + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": { + "eth-method-registry>@metamask/ethjs-query": { "globals": { - "XMLHttpRequest": true - } - }, - "@metamask/ethjs>@metamask/ethjs-unit": { - "packages": { - "@metamask/ethjs>@metamask/number-to-bn": true, - "bn.js": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "browserify>buffer": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, - "@metamask/ethjs>@metamask/number-to-bn": { + "console": true + }, "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "bn.js": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi>number-to-bn": { + "@metamask/controller-utils>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, "bn.js": true } }, - "@metamask/ethjs>js-sha3": { - "globals": { - "define": true - }, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { "packages": { - "process": true + "browserify>buffer": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, "@metamask/gas-fee-controller": { @@ -1433,111 +1300,58 @@ }, "packages": { "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, "bn.js": true, "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/base-controller": { + "@metamask/jazzicon": { "globals": { - "setTimeout": true + "document.createElement": true, + "document.createElementNS": true }, "packages": { - "immer": true + "@metamask/jazzicon>color": true, + "@metamask/jazzicon>mersenne-twister": true + } + }, + "@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/json-rpc-engine>@metamask/utils": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/json-rpc-middleware-stream": { "globals": { - "clearTimeout": true, - "console.error": true, + "console.warn": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/safe-event-emitter": true, + "@metamask/json-rpc-middleware-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/jazzicon": { + "@metamask/snaps-sdk>@metamask/key-tree": { "globals": { - "document.createElement": true, - "document.createElementNS": true + "crypto.subtle": true }, "packages": { - "@metamask/jazzicon>color": true, - "@metamask/jazzicon>mersenne-twister": true + "@metamask/scure-bip39": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/jazzicon>color": { - "packages": { - "@metamask/jazzicon>color>clone": true, - "@metamask/jazzicon>color>color-convert": true, - "@metamask/jazzicon>color>color-string": true - } - }, - "@metamask/jazzicon>color>clone": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask/jazzicon>color>color-convert": { - "packages": { - "@metamask/jazzicon>color>color-convert>color-name": true - } - }, - "@metamask/jazzicon>color>color-string": { - "packages": { - "jest-canvas-mock>moo-color>color-name": true - } - }, - "@metamask/json-rpc-engine": { - "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, - "@metamask/json-rpc-middleware-stream": { - "globals": { - "console.warn": true, - "setTimeout": true - }, - "packages": { - "@metamask/safe-event-emitter": true, - "@metamask/utils": true, - "readable-stream": true - } - }, - "@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/keyring-api>@metamask/utils": true, - "@metamask/keyring-api>bech32": true, - "@metamask/keyring-api>uuid": true, - "@metamask/utils>@metamask/superstruct": true - } - }, - "@metamask/keyring-api>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-api": { "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-api>uuid": { - "globals": { - "crypto": true + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>bech32": true } }, "@metamask/keyring-controller": { @@ -1548,126 +1362,32 @@ "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/keyring-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "@metamask/utils": true + "@metamask/keyring-controller>ethereumjs-wallet": true } }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring": { - "globals": { - "TextEncoder": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, - "@metamask/scure-bip39": true, - "browserify>buffer": true - } - }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-sig-util": { + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "@metamask/keyring-snap-client": true } }, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-snap-client": { "packages": { + "@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "@metamask/keyring-snap-client>uuid": true } }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { + "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, - "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, - "browserify>buffer": true, - "crypto-browserify": true, - "crypto-browserify>randombytes": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "uuid": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>assert": true, - "browserify>buffer": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethers>@ethersproject/sha2>hash.js": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>rlp": true + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": true, + "bitcoin-address-validation": true } }, "@metamask/logging-controller": { @@ -1694,53 +1414,20 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-sig-util": true, - "@metamask/message-manager>@metamask/utils": true, - "@metamask/message-manager>jsonschema": true, - "browserify>buffer": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/message-manager>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/message-manager>jsonschema": { - "packages": { - "browserify>url": true - } - }, - "@metamask/message-signing-snap>@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true + "webpack>events": true, + "uuid": true } }, - "@metamask/message-signing-snap>@noble/curves": { - "globals": { - "TextEncoder": true - }, + "@metamask/multichain": { "packages": { - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": true - } - }, - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@metamask/multichain>@metamask/api-specs": true, + "@metamask/controller-utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "lodash": true } }, "@metamask/name-controller": { @@ -1748,44 +1435,12 @@ "fetch": true }, "packages": { - "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true } }, - "@metamask/name-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/name-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/name-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true - } - }, "@metamask/network-controller": { "globals": { "btoa": true, @@ -1795,721 +1450,853 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-json-rpc-provider": true, - "@metamask/eth-query": true, "@metamask/network-controller>@metamask/eth-block-tracker": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-provider": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/network-controller>reselect": true, - "browserify>assert": true, - "browserify>util": true, + "@metamask/utils": true, + "eslint>fast-deep-equal": true, + "reselect": true, "uri-js": true, "uuid": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, + "@metamask/transaction-controller>@metamask/nonce-tracker": { "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@ethersproject/providers": true, + "browserify>assert": true, + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/notification-services-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Intl.NumberFormat": true, + "addEventListener": true, + "fetch": true, + "registration": true, + "removeEventListener": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/profile-sync-controller": true, + "@metamask/utils": true, + "@metamask/notification-services-controller>bignumber.js": true, + "@metamask/notification-services-controller>firebase": true, + "loglevel": true, + "uuid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": { + "packages": { + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true + } + }, + "@metamask/object-multiplex": { "globals": { - "setTimeout": true + "console.warn": true }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "node-fetch": true + "@metamask/object-multiplex>once": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": { + "@metamask/obs-store": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true + "@metamask/safe-event-emitter": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { + "@metamask/permission-controller": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/permission-controller>@metamask/utils": true, + "deep-freeze-strict": true, + "immer": true, + "nanoid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": { + "@metamask/permission-log-controller": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/permission-log-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": { + "@metamask/phishing-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "console.error": true, + "fetch": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack-cli>fastest-levenshtein": true, + "punycode": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "@metamask/polling-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, + "@metamask/post-message-stream": { + "globals": { + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true + }, + "packages": { + "@metamask/post-message-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": { + "@metamask/ppom-validator": { "globals": { "URL": true, "console.error": true, - "setTimeout": true + "crypto": true }, "packages": { - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, - "bn.js": true, - "pify": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "await-semaphore": true, + "browserify>buffer": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/ppom-validator>elliptic": true, + "@metamask/ppom-validator>json-rpc-random-id": true + } + }, + "@metamask/preferences-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": { + "@metamask/profile-sync-controller": { "globals": { + "Event": true, + "Headers": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/network-controller": true, + "@metamask/profile-sync-controller>@noble/ciphers": true, "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "loglevel": true, + "@metamask/profile-sync-controller>siwe": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/network-controller>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/selected-network-controller": true, + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/rpc-errors": { + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/network-controller>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/rate-limit-controller>@metamask/base-controller": true, + "@metamask/rate-limit-controller>@metamask/rpc-errors": true, + "@metamask/rate-limit-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/remote-feature-flag-controller": { "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "cockatiel": true, + "uuid": true } }, - "@metamask/network-controller>reselect": { - "globals": { - "WeakRef": true, - "console.warn": true, - "unstable_autotrackMemoize": true + "@metamask/rpc-errors": { + "packages": { + "@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-controller": { + "@metamask/rate-limit-controller>@metamask/rpc-errors": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-controller>@metamask/base-controller": { + "@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "immer": true + "webpack>events": true } }, - "@metamask/notification-controller>@metamask/utils": { + "@metamask/scure-bip39": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/scure-bip39>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/notification-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@metamask/selected-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true } }, - "@metamask/notification-services-controller": { + "@metamask/signature-controller": { "globals": { - "Intl.NumberFormat": true, - "addEventListener": true, - "fetch": true, - "registration": true, - "removeEventListener": true + "fetch": true }, "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, - "@metamask/notification-services-controller>bignumber.js": true, - "@metamask/notification-services-controller>firebase": true, - "@metamask/profile-sync-controller": true, + "@metamask/signature-controller>@metamask/eth-sig-util": true, + "@metamask/keyring-controller": true, + "@metamask/logging-controller": true, "@metamask/utils": true, - "loglevel": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/message-manager>jsonschema": true, "uuid": true } }, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { + "@metamask/smart-transactions-controller": { "globals": { - "SuppressedError": true + "URLSearchParams": true, + "clearInterval": true, + "console.error": true, + "console.log": true, + "fetch": true, + "setInterval": true + }, + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethersproject/bytes": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, + "@metamask/transaction-controller": true, + "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, + "fast-json-patch": true, + "lodash": true } }, - "@metamask/notification-services-controller>bignumber.js": { + "@metamask/snaps-controllers": { "globals": { - "crypto": true, - "define": true - } - }, - "@metamask/notification-services-controller>firebase": { - "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/messaging": true + "DecompressionStream": true, + "URL": true, + "clearTimeout": true, + "document.getElementById": true, + "fetch.bind": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/json-rpc-middleware-stream": true, + "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, + "@metamask/post-message-stream": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/snaps-registry": true, + "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/utils": true, + "@metamask/snaps-controllers>@xstate/fsm": true, + "@metamask/name-controller>async-mutex": true, + "browserify>browserify-zlib": true, + "@metamask/snaps-controllers>concat-stream": true, + "eslint>fast-deep-equal": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, + "immer": true, + "luxon": true, + "nanoid": true, + "readable-stream": true, + "@metamask/snaps-controllers>readable-web-to-node-stream": true, + "semver": true, + "@metamask/snaps-controllers>tar-stream": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app": { + "@metamask/snaps-execution-environments": { "globals": { - "FinalizationRegistry": true, - "console.warn": true + "document.getElementById": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/post-message-stream": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true, + "@metamask/snaps-execution-environments>@metamask/utils": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { + "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { - "globals": { - "console": true - }, + "@metamask/snaps-rpc-methods": { "packages": { - "@swc/helpers>tslib": true - } - }, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": { - "globals": { - "DOMException": true, - "IDBCursor": true, - "IDBDatabase": true, - "IDBIndex": true, - "IDBObjectStore": true, - "IDBRequest": true, - "IDBTransaction": true, - "indexedDB.deleteDatabase": true, - "indexedDB.open": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@noble/hashes": true, + "luxon": true } }, - "@metamask/notification-services-controller>firebase>@firebase/installations": { + "@metamask/snaps-sdk": { "globals": { - "BroadcastChannel": true, - "Headers": true, - "btoa": true, - "console.error": true, - "crypto": true, - "fetch": true, - "msCrypto": true, - "navigator.onLine": true, - "setTimeout": true + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-sdk>@metamask/utils": true } }, - "@metamask/notification-services-controller>firebase>@firebase/messaging": { + "@metamask/snaps-utils": { "globals": { - "Headers": true, - "Notification.maxActions": true, - "Notification.permission": true, - "Notification.requestPermission": true, - "PushSubscription.prototype.hasOwnProperty": true, - "ServiceWorkerRegistration": true, + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, "URL": true, - "addEventListener": true, - "atob": true, - "btoa": true, - "clients.matchAll": true, - "clients.openWindow": true, + "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "fetch": true, - "indexedDB": true, - "location.href": true, - "location.origin": true, - "navigator": true, - "origin.replace": true, - "registration.showNotification": true, - "setTimeout": true + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/installations": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true, - "@swc/helpers>tslib": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true } }, - "@metamask/notification-services-controller>firebase>@firebase/util": { + "@metamask/transaction-controller": { "globals": { - "atob": true, - "browser": true, - "btoa": true, - "chrome": true, - "console": true, - "document": true, - "indexedDB": true, - "navigator": true, - "process": true, - "self": true, + "clearTimeout": true, + "console.error": true, + "fetch": true, "setTimeout": true }, "packages": { - "process": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/abi": true, + "@ethersproject/contracts": true, + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/network-controller": true, + "@metamask/transaction-controller>@metamask/nonce-tracker": true, + "@metamask/rpc-errors": true, + "@metamask/transaction-controller>@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "browserify>buffer": true, + "eth-method-registry": true, + "webpack>events": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true } }, - "@metamask/object-multiplex": { + "@metamask/user-operation-controller": { "globals": { - "console.warn": true + "fetch": true }, "packages": { - "@metamask/object-multiplex>once": true, - "readable-stream": true - } - }, - "@metamask/object-multiplex>once": { - "packages": { - "@metamask/object-multiplex>once>wrappy": true - } - }, - "@metamask/obs-store": { - "packages": { - "@metamask/safe-event-emitter": true, - "readable-stream": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/transaction-controller": true, + "@metamask/user-operation-controller>@metamask/utils": true, + "bn.js": true, + "webpack>events": true, + "lodash": true, + "uuid": true } }, - "@metamask/permission-controller": { + "@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/permission-controller>@metamask/utils": true, - "@metamask/permission-controller>nanoid": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-controller>@metamask/base-controller": { + "@metamask/abi-utils>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/accounts-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/utils": { + "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>nanoid": { + "@metamask/controller-utils>@metamask/utils": { "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/permission-log-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/permission-log-controller>@metamask/base-controller": true, - "@metamask/permission-log-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/base-controller": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/utils": { + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/phishing-controller": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { "globals": { - "TextEncoder": true, - "URL": true, - "console.error": true, - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, - "punycode": true, - "webpack-cli>fastest-levenshtein": true + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/polling-controller": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream": { + "@metamask/eth-json-rpc-middleware>@metamask/utils": { "globals": { - "MessageEvent.prototype": true, - "WorkerGlobalScope": true, - "addEventListener": true, - "browser": true, - "chrome": true, - "location.origin": true, - "postMessage": true, - "removeEventListener": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "readable-stream": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream>@metamask/utils": { + "@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/ppom-validator": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "URL": true, - "console.error": true, - "crypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/ppom-validator>crypto-js": true, - "@metamask/ppom-validator>elliptic": true, - "await-semaphore": true, - "browserify>buffer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>crypto-js": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "crypto": true, - "define": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "@metamask/ppom-validator>elliptic>hmac-drbg": true, - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "bn.js": true, - "ethers>@ethersproject/sha2>hash.js": true, - "pumpify>inherits": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>brorand": { + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>hmac-drbg": { + "@metamask/eth-snap-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "ethers>@ethersproject/sha2>hash.js": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/preferences-controller": { + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller": { + "@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "Event": true, - "Headers": true, "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "URLSearchParams": true, - "addEventListener": true, - "console.error": true, - "dispatchEvent": true, - "fetch": true, - "removeEventListener": true, - "setTimeout": true + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/message-signing-snap>@noble/ciphers": true, - "@metamask/profile-sync-controller>siwe": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "loglevel": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe": { + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { "globals": { - "console.error": true, - "console.warn": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random": true, - "ethers": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "@metamask/keyring-api>@metamask/utils": { "globals": { - "console.error": true, - "console.log": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "@metamask/keyring-controller>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true - } - }, - "@metamask/queued-request-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller": { + "@metamask/name-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/rate-limit-controller>@metamask/base-controller": true, - "@metamask/rate-limit-controller>@metamask/rpc-errors": true, - "@metamask/rate-limit-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/base-controller": { + "@metamask/permission-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors": { + "@metamask/permission-log-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/post-message-stream>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, @@ -2520,1700 +2307,1799 @@ }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/rpc-errors": { + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods>nanoid": { + "@metamask/snaps-controllers>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/safe-event-emitter": { + "@metamask/snaps-execution-environments>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39": { + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { "globals": { + "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/scure-bip39>@noble/hashes": true, - "@metamask/utils>@scure/base": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39>@noble/hashes": { + "@metamask/snaps-rpc-methods>@metamask/utils": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@metamask/selected-network-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/network-controller>@metamask/swappable-obj-proxy": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller": { + "@metamask/snaps-sdk>@metamask/utils": { "globals": { - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/keyring-controller": true, - "@metamask/logging-controller": true, - "@metamask/message-manager>jsonschema": true, - "@metamask/signature-controller>@metamask/eth-sig-util": true, - "@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "uuid": true, - "webpack>events": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util": { + "@metamask/snaps-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "@metamask/transaction-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/smart-transactions-controller": { + "@metamask/user-operation-controller>@metamask/utils": { "globals": { - "URLSearchParams": true, - "clearInterval": true, - "console.error": true, - "console.log": true, - "fetch": true, - "setInterval": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bytes": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/polling-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, - "@metamask/smart-transactions-controller>bignumber.js": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "fast-json-patch": true, - "lodash": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "@ngraveio/bc-ur": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true + "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, + "browserify>assert": true, + "@ngraveio/bc-ur>bignumber.js": true, + "browserify>buffer": true, + "@ngraveio/bc-ur>cbor-sync": true, + "@ngraveio/bc-ur>crc": true, + "@ngraveio/bc-ur>jsbi": true, + "addons-linter>sha.js": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { - "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "webpack>events": true + "@metamask/profile-sync-controller>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@ethereumjs/util": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "console.warn": true, - "fetch": true + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@noble/hashes": { "globals": { - "setTimeout": true - }, - "packages": { - "immer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "@metamask/scure-bip39>@noble/hashes": { "globals": { - "crypto.getRandomValues": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/polling-controller": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { "globals": { - "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "TextEncoder": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": { - "packages": { - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": true + "@popperjs/core": { + "globals": { + "Element": true, + "HTMLElement": true, + "ShadowRoot": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator.userAgent": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "console.log": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "XMLHttpRequest": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/smart-transactions-controller>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, - "@metamask/snaps-controllers": { + "@reduxjs/toolkit": { "globals": { - "DecompressionStream": true, - "URL": true, - "clearTimeout": true, - "document.getElementById": true, - "fetch.bind": true, + "AbortController": true, + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, + "__REDUX_DEVTOOLS_EXTENSION__": true, + "console": true, + "queueMicrotask": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/json-rpc-middleware-stream": true, - "@metamask/object-multiplex": true, - "@metamask/post-message-stream": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, - "@metamask/snaps-controllers>@xstate/fsm": true, - "@metamask/snaps-controllers>concat-stream": true, - "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/snaps-controllers>readable-web-to-node-stream": true, - "@metamask/snaps-controllers>tar-stream": true, - "@metamask/snaps-rpc-methods": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, - "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/utils": true, - "browserify>browserify-zlib": true, - "eslint>fast-deep-equal": true, "immer": true, - "readable-stream": true, - "semver": true - } - }, - "@metamask/snaps-controllers-flask>nanoid": { - "globals": { - "crypto.getRandomValues": true + "process": true, + "redux": true, + "redux-thunk": true, + "@reduxjs/toolkit>reselect": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "react-router-dom-v5-compat>@remix-run/router": { "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-controllers>concat-stream": { - "packages": { - "browserify>buffer": true, - "browserify>concat-stream>typedarray": true, - "pumpify>inherits": true, - "readable-stream": true, - "terser>source-map-support>buffer-from": true + "AbortController": true, + "DOMException": true, + "FormData": true, + "Headers": true, + "Request": true, + "Response": true, + "URL": true, + "URLSearchParams": true, + "console": true, + "document.defaultView": true } }, - "@metamask/snaps-controllers>nanoid": { + "@metamask/utils>@scure/base": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>readable-web-to-node-stream": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { "packages": { - "readable-stream": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/snaps-controllers>tar-stream": { + "@segment/loosely-validate-event": { "packages": { - "@metamask/snaps-controllers>tar-stream>b4a": true, - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx": true, - "browserify>browser-resolve": true + "browserify>assert": true, + "browserify>buffer": true, + "@segment/loosely-validate-event>component-type": true, + "@segment/loosely-validate-event>join-component": true } }, - "@metamask/snaps-controllers>tar-stream>b4a": { + "@sentry/browser>@sentry-internal/browser-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true - } - }, - "@metamask/snaps-controllers>tar-stream>streamx": { + "PerformanceEventTiming.prototype": true, + "PerformanceObserver": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "clearTimeout": true, + "performance": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, - "webpack>events": true - } - }, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { - "globals": { - "queueMicrotask": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-execution-environments": { + "@sentry/browser>@sentry-internal/feedback": { "globals": { - "document.getElementById": true + "FormData": true, + "HTMLFormElement": true, + "__SENTRY_DEBUG__": true, + "cancelAnimationFrame": true, + "clearTimeout": true, + "document.createElement": true, + "document.createElementNS": true, + "document.createTextNode": true, + "isSecureContext": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods": { + "@sentry/browser>@sentry-internal/replay-canvas": { + "globals": { + "Blob": true, + "HTMLCanvasElement": true, + "HTMLImageElement": true, + "ImageData": true, + "URL.createObjectURL": true, + "WeakRef": true, + "Worker": true, + "cancelAnimationFrame": true, + "console.error": true, + "createImageBitmap": true, + "document": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { + "@sentry/browser>@sentry-internal/replay": { "globals": { - "console.error": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSRule": true, + "CSSSupportsRule": true, + "Document": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLElement": true, + "HTMLFormElement": true, + "Headers": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.prototype.contains": true, + "PointerEvent": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "__RRWEB_EXCLUDE_IFRAME__": true, + "__RRWEB_EXCLUDE_SHADOW_DOM__": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, + "__rrMutationObserver": true, + "addEventListener": true, + "clearTimeout": true, + "console.debug": true, + "console.error": true, + "console.warn": true, + "customElements.get": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "location.origin": true, + "parent": true, + "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk": { + "@sentry/browser": { "globals": { - "fetch": true + "PerformanceObserver.supportedEntryTypes": true, + "Request": true, + "URL": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "addEventListener": true, + "console.error": true, + "indexedDB.open": true, + "performance.timeOrigin": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true - } - }, - "@metamask/snaps-sdk>@metamask/key-tree": { - "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/scure-bip39": true, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry-internal/feedback": true, + "@sentry/browser>@sentry-internal/replay-canvas": true, + "@sentry/browser>@sentry-internal/replay": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "@sentry/browser>@sentry/core": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Headers": true, + "Request": true, + "URL": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@sentry/utils": true } }, - "@metamask/snaps-utils": { + "@sentry/utils": { "globals": { - "File": true, - "FileReader": true, + "CustomEvent": true, + "DOMError": true, + "DOMException": true, + "EdgeRuntime": true, + "Element": true, + "ErrorEvent": true, + "Event": true, + "HTMLElement": true, + "Headers": true, + "Request": true, + "Response": true, "TextDecoder": true, "TextEncoder": true, "URL": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, + "clearTimeout": true, "console.error": true, - "console.log": true, - "console.warn": true, - "crypto": true, - "document.body.appendChild": true, - "document.createElement": true, - "fetch": true + "document": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, - "@metamask/snaps-utils>cron-parser": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>fast-xml-parser": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, - "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true, - "chalk": true, - "semver": true + "process": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { + "@solana/addresses": { "globals": { - "console.error": true + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@solana/addresses>@solana/assertions": true, + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/codecs-strings": true, + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { + "@solana/addresses>@solana/assertions": { "globals": { - "crypto.getRandomValues": true + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>@metamask/snaps-registry": { + "@solana/addresses>@solana/codecs-core": { "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>cron-parser": { + "@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, "packages": { - "browserify>browser-resolve": true, - "luxon": true + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/errors": true } }, - "@metamask/snaps-utils>fast-xml-parser": { + "@solana/addresses>@solana/errors": { "globals": { - "entityName": true, - "val": true + "btoa": true + } + }, + "@metamask/controller-utils>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true }, "packages": { - "@metamask/snaps-utils>fast-xml-parser>strnum": true + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true } }, - "@metamask/snaps-utils>marked": { + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { "globals": { "console.error": true, - "console.warn": true, - "define": true + "console.log": true + }, + "packages": { + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true } }, - "@metamask/snaps-utils>rfdc": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { "packages": { - "browserify>buffer": true + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/snaps-utils>validate-npm-package-name": { + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, "packages": { - "@metamask/snaps-utils>validate-npm-package-name>builtins": true + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true } }, - "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, "packages": { - "process": true, - "semver": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true } }, - "@metamask/test-bundler>@ethersproject/networks": { + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { "packages": { - "ethers>@ethersproject/logger": true + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "tslib": true } }, - "@metamask/transaction-controller": { + "@trezor/connect-web": { "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/rpc-errors": true, - "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/utils": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker": { + "@trezor/connect-web>@trezor/connect": { "packages": { - "@ethersproject/providers": true, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>@trezor/connect>@trezor/transport": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { "globals": { - "clearTimeout": true, - "setTimeout": true + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true }, "packages": { - "@swc/helpers>tslib": true + "process": true, + "tslib": true, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true } }, - "@metamask/user-operation-controller": { - "globals": { - "fetch": true - }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { "packages": { - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/transaction-controller": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "@metamask/user-operation-controller>@metamask/polling-controller": true, - "@metamask/user-operation-controller>@metamask/rpc-errors": true, - "@metamask/user-operation-controller>@metamask/utils": true, - "bn.js": true, - "lodash": true, - "superstruct": true, - "uuid": true, - "webpack>events": true + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "browserify>buffer": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/base-controller": { + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { "globals": { - "setTimeout": true + "console.warn": true }, "packages": { - "immer": true + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, + "browserify>buffer": true, + "ts-mixer": true } }, - "@metamask/user-operation-controller>@metamask/polling-controller": { + "@trezor/connect-web>@trezor/utils": { "globals": { + "AbortController": true, + "Intl.NumberFormat": true, + "clearInterval": true, "clearTimeout": true, "console.error": true, + "console.info": true, + "console.log": true, + "console.warn": true, + "setInterval": true, "setTimeout": true }, "packages": { - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "uuid": true - } - }, - "@metamask/user-operation-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": true + "@trezor/connect-web>@trezor/utils>bignumber.js": true, + "browserify>buffer": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": { + "@welldone-software/why-did-you-render": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Element": true, + "console.group": true, + "console.groupCollapsed": true, + "console.groupEnd": true, + "console.log": true, + "console.warn": true, + "define": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "lodash": true, + "react": true } }, - "@metamask/user-operation-controller>@metamask/utils": { + "@zxing/browser": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "HTMLElement": true, + "HTMLImageElement": true, + "HTMLVideoElement": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@zxing/library": true } }, - "@metamask/utils": { + "@zxing/library": { "globals": { + "HTMLImageElement": true, + "HTMLVideoElement": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL.createObjectURL": true, + "btoa": true, + "console.log": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@zxing/library>ts-custom-error": true } }, - "@metamask/utils>@scure/base": { + "@lavamoat/lavapack>readable-stream>abort-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true - } - }, - "@ngraveio/bc-ur": { - "packages": { - "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, - "@ngraveio/bc-ur>bignumber.js": true, - "@ngraveio/bc-ur>cbor-sync": true, - "@ngraveio/bc-ur>crc": true, - "@ngraveio/bc-ur>jsbi": true, - "addons-linter>sha.js": true, - "browserify>assert": true, - "browserify>buffer": true + "AbortController": true } }, - "@ngraveio/bc-ur>assert>object-is": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true + "currency-formatter>accounting": { + "globals": { + "define": true } }, - "@ngraveio/bc-ur>bignumber.js": { + "ethers>@ethersproject/json-wallets>aes-js": { "globals": { - "crypto": true, "define": true } }, - "@ngraveio/bc-ur>cbor-sync": { + "eth-lattice-keyring>gridplus-sdk>aes-js": { "globals": { "define": true - }, + } + }, + "chalk>ansi-styles": { "packages": { - "browserify>buffer": true + "chalk>ansi-styles>color-convert": true } }, - "@ngraveio/bc-ur>crc": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { "packages": { "browserify>buffer": true } }, - "@ngraveio/bc-ur>jsbi": { - "globals": { - "define": true + "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true } }, - "@noble/hashes": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "browserify>vm-browserify": true + } + }, + "browserify>assert": { "globals": { - "TextEncoder": true, - "crypto": true + "Buffer": true + }, + "packages": { + "react>object-assign": true, + "browserify>assert>util": true } }, - "@popperjs/core": { + "@metamask/name-controller>async-mutex": { "globals": { - "Element": true, - "HTMLElement": true, - "ShadowRoot": true, - "console.error": true, - "console.warn": true, - "document": true, - "navigator.userAgent": true + "clearTimeout": true, + "setTimeout": true + }, + "packages": { + "tslib": true } }, - "@reduxjs/toolkit": { + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { "globals": { - "AbortController": true, - "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, - "__REDUX_DEVTOOLS_EXTENSION__": true, - "console": true, - "queueMicrotask": true, - "requestAnimationFrame": true, + "clearTimeout": true, "setTimeout": true }, "packages": { - "@reduxjs/toolkit>reselect": true, - "immer": true, - "process": true, - "redux": true, - "redux-thunk": true + "tslib": true } }, - "@segment/loosely-validate-event": { + "string.prototype.matchall>es-abstract>available-typed-arrays": { "packages": { - "@segment/loosely-validate-event>component-type": true, - "@segment/loosely-validate-event>join-component": true, - "browserify>assert": true, - "browserify>buffer": true + "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true } }, - "@sentry/browser": { + "await-semaphore": { + "packages": { + "process": true, + "browserify>timers-browserify": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { "globals": { - "PerformanceObserver.supportedEntryTypes": true, - "Request": true, - "URL": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_RELEASE__": true, - "addEventListener": true, - "console.error": true, - "indexedDB.open": true, - "performance.timeOrigin": true, - "setTimeout": true - }, - "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry-internal/feedback": true, - "@sentry/browser>@sentry-internal/replay": true, - "@sentry/browser>@sentry-internal/replay-canvas": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry-internal/browser-utils": { - "globals": { - "PerformanceEventTiming.prototype": true, - "PerformanceObserver": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "addEventListener": true, - "clearTimeout": true, - "performance": true, - "removeEventListener": true, + "Blob": true, + "FormData": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/feedback": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { "globals": { + "Blob": true, "FormData": true, - "HTMLFormElement": true, - "__SENTRY_DEBUG__": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "document.createElement": true, - "document.createElementNS": true, - "document.createTextNode": true, - "isSecureContext": true, - "requestAnimationFrame": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/replay": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { "globals": { "Blob": true, - "CSSConditionRule": true, - "CSSGroupingRule": true, - "CSSMediaRule": true, - "CSSRule": true, - "CSSSupportsRule": true, - "Document": true, - "DragEvent": true, - "Element": true, "FormData": true, - "HTMLElement": true, - "HTMLFormElement": true, - "Headers": true, - "MouseEvent": true, - "MutationObserver": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.prototype.contains": true, - "PointerEvent": true, - "TextEncoder": true, - "URL": true, "URLSearchParams": true, - "Worker": true, - "__RRWEB_EXCLUDE_IFRAME__": true, - "__RRWEB_EXCLUDE_SHADOW_DOM__": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, - "__rrMutationObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.debug": true, - "console.error": true, + "XMLHttpRequest": true, + "btoa": true, "console.warn": true, - "customElements.get": true, "document": true, - "innerHeight": true, - "innerWidth": true, "location.href": true, - "location.origin": true, - "parent": true, + "navigator": true, "setTimeout": true }, "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>buffer": true, + "axios>form-data": true, + "process": true } }, - "@sentry/browser>@sentry-internal/replay-canvas": { + "@metamask/snaps-controllers>tar-stream>b4a": { "globals": { - "Blob": true, - "HTMLCanvasElement": true, - "HTMLImageElement": true, - "ImageData": true, - "URL.createObjectURL": true, - "WeakRef": true, - "Worker": true, - "cancelAnimationFrame": true, - "console.error": true, - "createImageBitmap": true, - "document": true + "TextDecoder": true, + "TextEncoder": true + } + }, + "@ensdomains/content-hash>multihashes>multibase>base-x": { + "packages": { + "koa>content-disposition>safe-buffer": true + } + }, + "base32-encode": { + "packages": { + "base32-encode>to-data-view": true + } + }, + "bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/notification-services-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/smart-transactions-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@ngraveio/bc-ur>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@trezor/connect-web>@trezor/utils>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bitwise": { + "packages": { + "browserify>buffer": true + } + }, + "blo": { + "globals": { + "btoa": true + } + }, + "bn.js": { + "globals": { + "Buffer": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>browser-resolve": true } }, - "@sentry/browser>@sentry/core": { + "eth-lattice-keyring>gridplus-sdk>borc": { "globals": { - "Headers": true, - "Request": true, - "URL": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_TRACING__": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true + "console": true }, "packages": { - "@sentry/utils": true + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, + "browserify>buffer": true, + "buffer>ieee754": true, + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true } }, - "@sentry/utils": { + "bowser": { "globals": { - "CustomEvent": true, - "DOMError": true, - "DOMException": true, - "EdgeRuntime": true, - "Element": true, - "ErrorEvent": true, - "Event": true, - "HTMLElement": true, - "Headers": true, - "Request": true, - "Response": true, - "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "__SENTRY_BROWSER_BUNDLE__": true, - "__SENTRY_DEBUG__": true, - "clearTimeout": true, - "console.error": true, - "document": true, - "setInterval": true, - "setTimeout": true + "define": true + } + }, + "@metamask/ppom-validator>elliptic>brorand": { + "globals": { + "crypto": true, + "msCrypto": true }, "packages": { - "process": true + "browserify>browser-resolve": true + } + }, + "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true + } + }, + "crypto-browserify>browserify-cipher": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "crypto-browserify>browserify-cipher>browserify-des": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true + } + }, + "crypto-browserify>browserify-cipher>browserify-des": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>browserify-des>des.js": true, + "pumpify>inherits": true + } + }, + "crypto-browserify>public-encrypt>browserify-rsa": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>randombytes": true + } + }, + "crypto-browserify>browserify-sign": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "@metamask/ppom-validator>elliptic": true, + "pumpify>inherits": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "stream-browserify": true + } + }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>buffer": true, + "browserify>browserify-zlib>pako": true, + "process": true, + "stream-browserify": true, + "browserify>util": true + } + }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, - "@solana/addresses": { - "globals": { - "Intl.Collator": true, - "TextEncoder": true, - "crypto.subtle.digest": true, - "crypto.subtle.exportKey": true - }, + "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { - "@solana/addresses>@solana/assertions": true, - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/codecs-strings": true, - "@solana/addresses>@solana/errors": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "ethereumjs-util>create-hash": true, + "koa>content-disposition>safe-buffer": true } }, - "@solana/addresses>@solana/assertions": { + "buffer": { "globals": { - "crypto": true, - "isSecureContext": true + "console": true }, "packages": { - "@solana/addresses>@solana/errors": true + "base64-js": true, + "buffer>ieee754": true } }, - "@solana/addresses>@solana/codecs-core": { + "terser>source-map-support>buffer-from": { "packages": { - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/codecs-strings": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true - }, + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { "packages": { - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/errors": { + "browserify>buffer": { "globals": { - "btoa": true + "console": true + }, + "packages": { + "base64-js": true, + "buffer>ieee754": true } }, - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": { "packages": { - "react-markdown>unist-util-visit": true + "process": true, + "semver": true } }, - "@storybook/addon-knobs>qs": { + "string.prototype.matchall>call-bind": { "packages": { - "string.prototype.matchall>side-channel": true + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>call-bind>set-function-length": true } }, - "@swc/helpers>tslib": { + "@ngraveio/bc-ur>cbor-sync": { "globals": { - "SuppressedError": true, "define": true + }, + "packages": { + "browserify>buffer": true } }, - "@trezor/connect-web": { + "chalk": { + "packages": { + "chalk>ansi-styles": true, + "chalk>supports-color": true + } + }, + "chart.js": { "globals": { - "URLSearchParams": true, - "__TREZOR_CONNECT_SRC": true, + "Intl.NumberFormat": true, + "MutationObserver": true, + "OffscreenCanvas": true, + "Path2D": true, + "ResizeObserver": true, "addEventListener": true, - "btoa": true, - "chrome": true, - "clearInterval": true, "clearTimeout": true, + "console.error": true, "console.warn": true, - "document.body": true, - "document.createElement": true, - "document.createTextNode": true, - "document.getElementById": true, - "document.querySelectorAll": true, - "location": true, - "navigator": true, - "open": true, - "origin": true, + "devicePixelRatio": true, + "document": true, "removeEventListener": true, - "setInterval": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect": true, - "@trezor/connect-web>@trezor/connect-common": true, - "@trezor/connect-web>@trezor/utils": true, - "webpack>events": true - } - }, - "@trezor/connect-web>@trezor/connect": { - "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, - "@trezor/connect-web>@trezor/connect>@trezor/transport": true, - "@trezor/connect-web>@trezor/utils": true + "chart.js>@kurkle/color": true } }, - "@trezor/connect-web>@trezor/connect-common": { - "globals": { - "console.warn": true, - "localStorage.getItem": true, - "localStorage.setItem": true, - "navigator": true, - "setTimeout": true, - "window": true - }, + "@ensdomains/content-hash>cids": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, - "@trezor/connect-web>@trezor/utils": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>cids>multihashes": true, + "@ensdomains/content-hash>cids>uint8arrays": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { - "globals": { - "innerHeight": true, - "innerWidth": true, - "location.hostname": true, - "location.origin": true, - "navigator.languages": true, - "navigator.platform": true, - "navigator.userAgent": true, - "screen.height": true, - "screen.width": true - }, + "ethereumjs-util>create-hash>cipher-base": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, - "process": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true, + "stream-browserify": true, + "browserify>string_decoder": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "classnames": { "globals": { + "classNames": "write", "define": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { + "@metamask/jazzicon>color>clone": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, "browserify>buffer": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "cockatiel": { "globals": { - "process": true, + "AbortController": true, + "AbortSignal": true, + "WeakRef": true, + "clearTimeout": true, + "performance": true, "setTimeout": true }, "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true - } - }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { - "globals": { - "console.log": true + "process": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { - "globals": { - "XMLHttpRequest": true - }, + "chalk>ansi-styles>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { - "globals": { - "console.warn": true - }, + "@metamask/jazzicon>color>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "browserify>buffer": true, - "ts-mixer": true + "@metamask/jazzicon>color>color-convert>color-name": true } }, - "@trezor/connect-web>@trezor/utils": { - "globals": { - "AbortController": true, - "Intl.NumberFormat": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.info": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "@metamask/jazzicon>color>color-string": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/utils>bignumber.js": true, - "browserify>buffer": true, - "webpack>events": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/utils>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/jazzicon>color": { + "packages": { + "@metamask/jazzicon>color>clone": true, + "@metamask/jazzicon>color>color-convert": true, + "@metamask/jazzicon>color>color-string": true } }, - "@welldone-software/why-did-you-render": { - "globals": { - "Element": true, - "console.group": true, - "console.groupCollapsed": true, - "console.groupEnd": true, - "console.log": true, - "console.warn": true, - "define": true, - "setTimeout": true - }, + "@metamask/snaps-controllers>concat-stream": { "packages": { - "lodash": true, - "react": true + "terser>source-map-support>buffer-from": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "readable-stream": true, + "browserify>concat-stream>typedarray": true } }, - "@zxing/browser": { + "copy-to-clipboard": { "globals": { - "HTMLElement": true, - "HTMLImageElement": true, - "HTMLVideoElement": true, - "clearTimeout": true, + "clipboardData": true, "console.error": true, "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true, + "document.createRange": true, + "document.execCommand": true, + "document.getSelection": true, + "navigator.userAgent": true, + "prompt": true }, "packages": { - "@zxing/library": true + "copy-to-clipboard>toggle-selection": true } }, - "@zxing/library": { + "@ethereumjs/tx>@ethereumjs/common>crc-32": { "globals": { - "HTMLImageElement": true, - "HTMLVideoElement": true, - "TextDecoder": true, - "TextEncoder": true, - "URL.createObjectURL": true, - "btoa": true, - "console.log": true, - "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "@zxing/library>ts-custom-error": true + "DO_NOT_EXPORT_CRC": true, + "define": true } }, - "addons-linter>sha.js": { + "@ngraveio/bc-ur>crc": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "await-semaphore": { + "crypto-browserify>create-ecdh": { "packages": { - "browserify>timers-browserify": true, - "process": true + "bn.js": true, + "browserify>buffer": true, + "@metamask/ppom-validator>elliptic": true } }, - "axios>form-data": { - "globals": { - "FormData": true + "ethereumjs-util>create-hash": { + "packages": { + "ethereumjs-util>create-hash>cipher-base": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>md5.js": true, + "ethereumjs-util>create-hash>ripemd160": true, + "addons-linter>sha.js": true } }, - "base32-encode": { + "crypto-browserify>create-hmac": { "packages": { - "base32-encode>to-data-view": true + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>create-hash": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true } }, - "blo": { - "globals": { - "btoa": true + "crypto-browserify": { + "packages": { + "crypto-browserify>browserify-cipher": true, + "crypto-browserify>browserify-sign": true, + "crypto-browserify>create-ecdh": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "crypto-browserify>diffie-hellman": true, + "crypto-browserify>pbkdf2": true, + "crypto-browserify>public-encrypt": true, + "crypto-browserify>randombytes": true, + "crypto-browserify>randomfill": true } }, - "bn.js": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "Buffer": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { "browserify>browser-resolve": true } }, - "bowser": { - "globals": { - "define": true - } - }, - "browserify>assert": { + "react-beautiful-dnd>css-box-model": { "globals": { - "Buffer": true + "getComputedStyle": true, + "pageXOffset": true, + "pageYOffset": true }, "packages": { - "browserify>assert>util": true, - "react>object-assign": true + "react-router-dom>tiny-invariant": true } }, - "browserify>assert>util": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true, - "process": true + "document.createElement": true, + "document.documentElement": true, + "getComputedStyle": true }, "packages": { - "browserify>assert>util>inherits": true, - "process": true - } - }, - "browserify>browserify-zlib": { - "packages": { - "browserify>assert": true, - "browserify>browserify-zlib>pako": true, - "browserify>buffer": true, - "browserify>util": true, - "process": true, - "stream-browserify": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true } }, - "browserify>buffer": { - "globals": { - "console": true - }, + "currency-formatter": { "packages": { - "base64-js": true, - "buffer>ieee754": true - } - }, - "browserify>punycode": { - "globals": { - "define": true + "currency-formatter>accounting": true, + "currency-formatter>locale-currency": true, + "react>object-assign": true } }, - "browserify>string_decoder": { + "debounce-stream": { "packages": { - "koa>content-disposition>safe-buffer": true + "debounce-stream>debounce": true, + "debounce-stream>duplexer": true, + "debounce-stream>through": true } }, - "browserify>timers-browserify": { + "debounce-stream>debounce": { "globals": { - "clearInterval": true, "clearTimeout": true, - "setInterval": true, "setTimeout": true - }, - "packages": { - "process": true - } - }, - "browserify>url": { - "packages": { - "@storybook/addon-knobs>qs": true, - "browserify>punycode": true } }, - "browserify>util": { + "nock>debug": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "browserify>util>is-arguments": true, - "browserify>util>is-typed-array": true, - "browserify>util>which-typed-array": true, - "koa>is-generator-function": true, - "process": true, - "pumpify>inherits": true - } - }, - "browserify>util>is-arguments": { - "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "nock>debug>ms": true, + "process": true } }, - "browserify>util>is-typed-array": { + "@metamask/eth-token-tracker>deep-equal": { "packages": { + "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, + "string.prototype.matchall>call-bind": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, + "string.prototype.matchall>get-intrinsic": true, + "browserify>util>is-arguments": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true, + "@metamask/eth-token-tracker>deep-equal>is-date-object": true, + "string.prototype.matchall>es-abstract>is-regex": true, + "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "@ngraveio/bc-ur>assert>object-is": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true, + "gulp>vinyl-fs>object.assign": true, + "string.prototype.matchall>regexp.prototype.flags": true, + "string.prototype.matchall>side-channel": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, + "@metamask/eth-token-tracker>deep-equal>which-collection": true, "browserify>util>which-typed-array": true } }, - "browserify>util>which-typed-array": { + "string.prototype.matchall>define-properties>define-data-property": { "packages": { - "browserify>util>which-typed-array>for-each": true, - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>es-abstract>gopd": true } }, - "browserify>util>which-typed-array>for-each": { + "string.prototype.matchall>define-properties": { "packages": { - "string.prototype.matchall>es-abstract>is-callable": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "browserify>vm-browserify": { - "globals": { - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true + "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, - "buffer": { - "globals": { - "console": true - }, + "crypto-browserify>diffie-hellman": { "packages": { - "base64-js": true, - "buffer>ieee754": true + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>diffie-hellman>miller-rabin": true, + "crypto-browserify>randombytes": true } }, - "chalk": { + "@material-ui/core>react-transition-group>dom-helpers": { "packages": { - "chalk>ansi-styles": true, - "chalk>supports-color": true + "@babel/runtime": true } }, - "chalk>ansi-styles": { + "debounce-stream>duplexer": { "packages": { - "chalk>ansi-styles>color-convert": true + "stream-browserify": true } }, - "chalk>ansi-styles>color-convert": { + "ethers>@ethersproject/signing-key>elliptic": { "packages": { - "jest-canvas-mock>moo-color>color-name": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chart.js": { - "globals": { - "Intl.NumberFormat": true, - "MutationObserver": true, - "OffscreenCanvas": true, - "Path2D": true, - "ResizeObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.error": true, - "console.warn": true, - "devicePixelRatio": true, - "document": true, - "removeEventListener": true, - "requestAnimationFrame": true, - "setTimeout": true - }, + "@metamask/ppom-validator>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true + } + }, + "eth-lattice-keyring>gridplus-sdk>elliptic": { "packages": { - "chart.js>@kurkle/color": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chart.js>@kurkle/color": { - "globals": { - "define": true + "string.prototype.matchall>call-bind>es-define-property": { + "packages": { + "string.prototype.matchall>get-intrinsic": true } }, - "classnames": { - "globals": { - "classNames": "write", - "define": true + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>has-symbols": true, + "browserify>util>is-arguments": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "eslint-plugin-react>array-includes>is-string": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "process": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "cockatiel": { + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { "globals": { - "AbortController": true, - "AbortSignal": true, - "WeakRef": true, - "clearTimeout": true, - "performance": true, - "setTimeout": true + "intToBuffer": true }, "packages": { - "process": true + "bn.js": true, + "buffer": true, + "eth-ens-namehash>js-sha3": true } }, - "copy-to-clipboard": { + "eth-ens-namehash": { "globals": { - "clipboardData": true, - "console.error": true, - "console.warn": true, - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true, - "document.createRange": true, - "document.execCommand": true, - "document.getSelection": true, - "navigator.userAgent": true, - "prompt": true + "name": "write" }, "packages": { - "copy-to-clipboard>toggle-selection": true + "browserify>buffer": true, + "eth-ens-namehash>idna-uts46-hx": true, + "eth-ens-namehash>js-sha3": true } }, - "copy-to-clipboard>toggle-selection": { + "eth-lattice-keyring": { "globals": { - "document.activeElement": true, - "document.getSelection": true + "addEventListener": true, + "browser": true, + "clearInterval": true, + "fetch": true, + "open": true, + "setInterval": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify": true, + "webpack>events": true, + "eth-lattice-keyring>gridplus-sdk": true, + "eth-lattice-keyring>rlp": true } }, - "crypto-browserify": { + "eth-method-registry": { "packages": { - "crypto-browserify>browserify-cipher": true, - "crypto-browserify>browserify-sign": true, - "crypto-browserify>create-ecdh": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>diffie-hellman": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt": true, - "crypto-browserify>randombytes": true, - "crypto-browserify>randomfill": true, - "ethereumjs-util>create-hash": true + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true } }, - "crypto-browserify>browserify-cipher": { + "@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "crypto-browserify>browserify-cipher>browserify-des": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true } }, - "crypto-browserify>browserify-cipher>browserify-des": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>browserify-des>des.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "pumpify>inherits": true + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>evp_bytestokey": { + "ethereumjs-util>ethereum-cryptography": { "packages": { - "ethereumjs-util>create-hash>md5.js": true, - "koa>content-disposition>safe-buffer": true + "browserify>buffer": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "ganache>secp256k1": true } }, - "crypto-browserify>browserify-sign": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "browserify>buffer": true, "crypto-browserify>create-hmac": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "ethereumjs-util>create-hash": true, - "pumpify>inherits": true, - "stream-browserify": true + "ethers>@ethersproject/sha2>hash.js": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true } }, - "crypto-browserify>create-ecdh": { + "ethereumjs-util": { "packages": { - "@metamask/ppom-validator>elliptic": true, + "browserify>assert": true, "bn.js": true, - "browserify>buffer": true + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>rlp": true } }, - "crypto-browserify>create-hmac": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { - "addons-linter>sha.js": true, + "browserify>assert": true, + "bn.js": true, + "browserify>buffer": true, "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true } }, - "crypto-browserify>diffie-hellman": { + "@metamask/keyring-controller>ethereumjs-wallet": { "packages": { - "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, "browserify>buffer": true, - "crypto-browserify>diffie-hellman>miller-rabin": true, - "crypto-browserify>randombytes": true + "crypto-browserify": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, + "crypto-browserify>randombytes": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true, + "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, + "uuid": true } }, - "crypto-browserify>diffie-hellman>miller-rabin": { + "ethers": { "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "bn.js": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "ethers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>pbkdf2": { - "globals": { - "crypto": true, - "process": true, - "queueMicrotask": true, - "setImmediate": true, - "setTimeout": true - }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>public-encrypt": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, "browserify>buffer": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>create-hash": true + "eth-ens-namehash>js-sha3": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": true } }, - "crypto-browserify>public-encrypt>browserify-rsa": { - "packages": { - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "webpack>events": { + "globals": { + "console": true } }, - "crypto-browserify>public-encrypt>parse-asn1": { - "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "crypto-browserify>browserify-cipher>evp_bytestokey": { + "packages": { + "ethereumjs-util>create-hash>md5.js": true, + "koa>content-disposition>safe-buffer": true } }, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "extension-port-stream": { "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "bn.js": true, "browserify>buffer": true, - "browserify>vm-browserify": true, - "pumpify>inherits": true + "extension-port-stream>readable-stream": true } }, - "crypto-browserify>randombytes": { + "fast-json-patch": { "globals": { - "crypto": true, - "msCrypto": true + "addEventListener": true, + "clearTimeout": true, + "removeEventListener": true, + "setTimeout": true + } + }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true }, "packages": { - "koa>content-disposition>safe-buffer": true, - "process": true + "@metamask/snaps-utils>fast-xml-parser>strnum": true } }, - "crypto-browserify>randomfill": { + "@metamask/notification-services-controller>firebase": { + "packages": { + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/messaging": true + } + }, + "react-focus-lock>focus-lock": { "globals": { - "crypto": true, - "msCrypto": true + "HTMLIFrameElement": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.DOCUMENT_NODE": true, + "Node.DOCUMENT_POSITION_CONTAINED_BY": true, + "Node.DOCUMENT_POSITION_CONTAINS": true, + "Node.ELEMENT_NODE": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "setTimeout": true }, "packages": { - "crypto-browserify>randombytes": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "tslib": true } }, - "currency-formatter": { + "browserify>util>which-typed-array>for-each": { "packages": { - "currency-formatter>accounting": true, - "currency-formatter>locale-currency": true, - "react>object-assign": true + "string.prototype.matchall>es-abstract>is-callable": true } }, - "currency-formatter>accounting": { + "axios>form-data": { + "globals": { + "FormData": true + } + }, + "fuse.js": { "globals": { + "console": true, "define": true } }, - "currency-formatter>locale-currency": { + "string.prototype.matchall>get-intrinsic": { "globals": { - "countryCode": true + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true, + "depcheck>is-core-module>hasown": true } }, - "debounce-stream": { + "string.prototype.matchall>es-abstract>gopd": { "packages": { - "debounce-stream>debounce": true, - "debounce-stream>duplexer": true, - "debounce-stream>through": true + "string.prototype.matchall>get-intrinsic": true } }, - "debounce-stream>debounce": { + "eth-lattice-keyring>gridplus-sdk": { "globals": { + "AbortController": true, + "Request": true, + "URL": true, + "__values": true, + "caches": true, "clearTimeout": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "@ethersproject/abi": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "@metamask/keyring-api>bech32": true, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, + "eth-lattice-keyring>gridplus-sdk>bitwise": true, + "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>borc": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>elliptic": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "ethers>@ethersproject/sha2>hash.js": true, + "eth-ens-namehash>js-sha3": true, + "lodash": true, + "eth-lattice-keyring>rlp": true, + "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>uuid": true } }, - "debounce-stream>duplexer": { + "string.prototype.matchall>es-abstract>has-property-descriptors": { "packages": { - "stream-browserify": true + "string.prototype.matchall>call-bind>es-define-property": true } }, - "debounce-stream>through": { + "koa>is-generator-function>has-tostringtag": { "packages": { - "process": true, - "stream-browserify": true + "string.prototype.matchall>has-symbols": true } }, - "depcheck>@vue/compiler-sfc>postcss>nanoid": { - "globals": { - "crypto.getRandomValues": true + "ethereumjs-util>create-hash>md5.js>hash-base": { + "packages": { + "pumpify>inherits": true, + "readable-stream": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethers>@ethersproject/sha2>hash.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, "depcheck>is-core-module>hasown": { @@ -4221,276 +4107,257 @@ "browserify>has>function-bind": true } }, - "dependency-tree>precinct>detective-postcss>postcss>nanoid": { + "@metamask/eth-trezor-keyring>hdkey": { + "packages": { + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "crypto-browserify": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true + } + }, + "he": { "globals": { - "crypto.getRandomValues": true + "define": true } }, - "eslint-plugin-react>array-includes>is-string": { - "packages": { - "koa>is-generator-function>has-tostringtag": true + "history": { + "globals": { + "console": true, + "define": true, + "document.defaultView": true, + "document.querySelector": true } }, - "eth-ens-namehash": { + "react-router-dom>history": { "globals": { - "name": "write" + "addEventListener": true, + "confirm": true, + "document": true, + "history": true, + "location": true, + "navigator.userAgent": true, + "removeEventListener": true }, "packages": { - "@metamask/ethjs>js-sha3": true, - "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true + "react-router-dom>history>resolve-pathname": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true, + "react-router-dom>history>value-equal": true } }, - "eth-ens-namehash>idna-uts46-hx": { - "globals": { - "define": true - }, + "@metamask/ppom-validator>elliptic>hmac-drbg": { "packages": { - "browserify>punycode": true + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "eth-keyring-controller>@metamask/browser-passworder": { - "globals": { - "crypto": true + "react-redux>hoist-non-react-statics": { + "packages": { + "prop-types>react-is": true } }, - "eth-lattice-keyring": { - "globals": { - "addEventListener": true, - "browser": true, - "clearInterval": true, - "fetch": true, - "open": true, - "setInterval": true - }, + "https-browserify": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify": true, - "eth-lattice-keyring>gridplus-sdk": true, - "eth-lattice-keyring>rlp": true, - "webpack>events": true + "stream-http": true, + "browserify>url": true } }, - "eth-lattice-keyring>gridplus-sdk": { + "@metamask/notification-services-controller>firebase>@firebase/app>idb": { "globals": { - "AbortController": true, - "Request": true, - "URL": true, - "__values": true, - "caches": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "console.warn": true, - "fetch": true, - "setTimeout": true + "DOMException": true, + "IDBCursor": true, + "IDBDatabase": true, + "IDBIndex": true, + "IDBObjectStore": true, + "IDBRequest": true, + "IDBTransaction": true, + "indexedDB.deleteDatabase": true, + "indexedDB.open": true + } + }, + "eth-ens-namehash>idna-uts46-hx": { + "globals": { + "define": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-sig-util": true, - "@metamask/ethjs>js-sha3": true, - "@metamask/keyring-api>bech32": true, - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc": true, - "eth-lattice-keyring>gridplus-sdk>bs58check": true, - "eth-lattice-keyring>gridplus-sdk>secp256k1": true, - "eth-lattice-keyring>gridplus-sdk>uuid": true, - "ethers>@ethersproject/sha2>hash.js": true, - "lodash": true + "browserify>punycode": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "string.prototype.matchall>internal-slot": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "string.prototype.matchall>call-bind>es-errors": true, + "depcheck>is-core-module>hasown": true, + "string.prototype.matchall>side-channel": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "browserify>util>is-arguments": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "string.prototype.matchall>es-abstract>is-array-buffer": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { - "globals": { - "console.warn": true, - "fetch": true - }, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true } }, - "eth-lattice-keyring>gridplus-sdk>aes-js": { - "globals": { - "define": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "packages": { + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "string.prototype.matchall>es-abstract>is-callable": { "globals": { - "crypto": true, - "define": true + "document": true } }, - "eth-lattice-keyring>gridplus-sdk>borc": { - "globals": { - "console": true - }, + "@metamask/eth-token-tracker>deep-equal>is-date-object": { "packages": { - "browserify>buffer": true, - "buffer>ieee754": true, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "koa>is-generator-function": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "@material-ui/core>@material-ui/styles>jss>is-in-browser": { "globals": { - "URL": true, - "URLSearchParams": true, - "location": true, - "navigator": true + "document": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { "packages": { - "@noble/hashes": true, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "string.prototype.matchall>es-abstract>is-regex": { "packages": { - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "string.prototype.matchall>es-abstract>is-shared-array-buffer": { "packages": { - "@metamask/ppom-validator>elliptic": true + "string.prototype.matchall>call-bind": true } }, - "eth-lattice-keyring>gridplus-sdk>uuid": { - "globals": { - "crypto": true + "eslint-plugin-react>array-includes>is-string": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>rlp": { - "globals": { - "TextEncoder": true + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "packages": { + "string.prototype.matchall>has-symbols": true } }, - "eth-method-registry": { + "browserify>util>is-typed-array": { "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true + "browserify>util>which-typed-array": true } }, - "ethereumjs-util": { + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { "packages": { - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "ethereumjs-util>create-hash": { + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "globals": { + "URL": true, + "URLSearchParams": true, + "location": true + } + }, + "@ensdomains/content-hash>js-base64": { + "globals": { + "Base64": "write", + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true, + "define": true + }, "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-util>create-hash>ripemd160": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "ethereumjs-util>create-hash>cipher-base": { + "eth-ens-namehash>js-sha3": { + "globals": { + "define": true + }, "packages": { - "browserify>string_decoder": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "stream-browserify": true + "process": true } }, - "ethereumjs-util>create-hash>md5.js": { + "@ngraveio/bc-ur>jsbi": { + "globals": { + "define": true + } + }, + "@metamask/message-manager>jsonschema": { "packages": { - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>url": true } }, - "ethereumjs-util>create-hash>md5.js>hash-base": { + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "readable-stream": true + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true } }, - "ethereumjs-util>create-hash>ripemd160": { + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { + "globals": { + "CSS": true + }, "packages": { - "browserify>buffer": true, - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "pumpify>inherits": true + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography": { + "@material-ui/core>@material-ui/styles>jss-plugin-global": { "packages": { - "browserify>buffer": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ganache>secp256k1": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "@material-ui/core>@material-ui/styles>jss-plugin-nested": { "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@babel/runtime": true, + "react-router-dom>tiny-warning": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { "packages": { - "browserify>buffer": true + "@material-ui/core>@material-ui/styles>jss": true, + "react-router-dom>tiny-warning": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, - "koa>content-disposition>safe-buffer": true + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { + "@material-ui/core>@material-ui/styles>jss": { + "globals": { + "CSS": true, + "document.createElement": true, + "document.querySelector": true + }, "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, + "react-router-dom>tiny-warning": true } }, "ethereumjs-util>ethereum-cryptography>keccak": { @@ -4499,527 +4366,496 @@ "readable-stream": true } }, - "ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, - "ethereumjs-wallet>randombytes": { + "currency-formatter>locale-currency": { "globals": { - "crypto.getRandomValues": true + "countryCode": true } }, - "ethers": { - "packages": { - "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/web": true, - "ethers>@ethersproject/wordlists": true + "localforage": { + "globals": { + "Blob": true, + "BlobBuilder": true, + "FileReader": true, + "IDBKeyRange": true, + "MSBlobBuilder": true, + "MozBlobBuilder": true, + "OIndexedDB": true, + "WebKitBlobBuilder": true, + "atob": true, + "btoa": true, + "console.error": true, + "console.info": true, + "console.warn": true, + "define": true, + "fetch": true, + "indexedDB": true, + "localStorage": true, + "mozIndexedDB": true, + "msIndexedDB": true, + "navigator.platform": true, + "navigator.userAgent": true, + "openDatabase": true, + "setTimeout": true, + "webkitIndexedDB": true } }, - "ethers>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "lodash": { + "globals": { + "clearTimeout": true, + "define": true, + "setTimeout": true } }, - "ethers>@ethersproject/abstract-signer": { - "packages": { - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "loglevel": { + "globals": { + "console": true, + "define": true, + "document.cookie": true, + "localStorage": true, + "log": "write", + "navigator": true } }, - "ethers>@ethersproject/address": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/rlp": true + "lottie-web": { + "globals": { + "Blob": true, + "Howl": true, + "OffscreenCanvas": true, + "URL.createObjectURL": true, + "Worker": true, + "XMLHttpRequest": true, + "bodymovin": "write", + "clearInterval": true, + "console": true, + "define": true, + "document.body": true, + "document.createElement": true, + "document.createElementNS": true, + "document.getElementsByClassName": true, + "document.getElementsByTagName": true, + "document.querySelectorAll": true, + "document.readyState": true, + "location.origin": true, + "location.pathname": true, + "navigator": true, + "requestAnimationFrame": true, + "setInterval": true, + "setTimeout": true } }, - "ethers>@ethersproject/base64": { + "luxon": { "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/bytes": true + "Intl": true } }, - "ethers>@ethersproject/basex": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/properties": true + "@metamask/snaps-utils>marked": { + "globals": { + "console.error": true, + "console.warn": true, + "define": true } }, - "ethers>@ethersproject/constants": { + "ethereumjs-util>create-hash>md5.js": { "packages": { - "@ethersproject/bignumber": true + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "ethers>@ethersproject/json-wallets": { + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets>aes-js": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/pbkdf2": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/json-wallets>aes-js": { - "globals": { - "define": true + "react-markdown>remark-parse>mdast-util-from-markdown": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, + "react-syntax-highlighter>refractor>parse-entities": true, + "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true } }, - "ethers>@ethersproject/json-wallets>scrypt-js": { + "react-markdown>remark-rehype>mdast-util-to-hast": { "globals": { - "define": true, - "setTimeout": true + "console.warn": true }, "packages": { - "browserify>timers-browserify": true - } - }, - "ethers>@ethersproject/keccak256": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ethjs>js-sha3": true + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, + "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/logger": { + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { "globals": { - "console": true + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "https-browserify": true, + "process": true, + "stream-http": true, + "browserify>url": true, + "browserify>util": true } }, - "ethers>@ethersproject/pbkdf2": { + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/sha2": true + "react-syntax-highlighter>refractor>parse-entities": true } }, - "ethers>@ethersproject/properties": { + "crypto-browserify>diffie-hellman>miller-rabin": { "packages": { - "ethers>@ethersproject/logger": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true } }, - "ethers>@ethersproject/providers": { + "@ensdomains/content-hash>cids>multibase": { "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers>@ethersproject/networks": true, - "ethers>@ethersproject/providers>@ethersproject/web": true, - "ethers>@ethersproject/providers>bech32": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "ethers>@ethersproject/providers>@ethersproject/networks": { + "@ensdomains/content-hash>multihashes>multibase": { "packages": { - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "@ensdomains/content-hash>multicodec": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@ensdomains/content-hash>multicodec>uint8arrays": true, + "sass-embedded>varint": true } }, - "ethers>@ethersproject/random": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "console.warn": true, + "crypto.subtle.digest": true } }, - "ethers>@ethersproject/rlp": { + "@ensdomains/content-hash>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>multibase": true, + "@ensdomains/content-hash>multihashes>varint": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/sha2": { + "@ensdomains/content-hash>cids>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2>hash.js": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>cids>multihashes>varint": true } }, - "ethers>@ethersproject/sha2>hash.js": { - "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/signing-key": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ppom-validator>elliptic": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "@metamask/approval-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/solidity": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true + "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/strings": { - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true + "@metamask/notification-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/transactions": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/signing-key": true + "@metamask/permission-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/rpc-methods>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/units": { - "packages": { - "@ethersproject/bignumber": true, - "ethers>@ethersproject/logger": true + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/web": { + "@metamask/snaps-controllers>nanoid": { "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/wordlists": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@metamask/snaps-controllers-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream": { - "packages": { - "browserify>buffer": true, - "extension-port-stream>readable-stream": true + "depcheck>@vue/compiler-sfc>postcss>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream": { + "dependency-tree>precinct>detective-postcss>postcss>nanoid": { "globals": { - "AbortController": true, - "AggregateError": true, - "Blob": true - }, - "packages": { - "browserify>buffer": true, - "browserify>string_decoder": true, - "extension-port-stream>readable-stream>abort-controller": true, - "process": true, - "webpack>events": true + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream>abort-controller": { + "node-fetch": { "globals": { - "AbortController": true + "Headers": true, + "Request": true, + "Response": true, + "fetch": true } }, - "fast-json-patch": { + "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { "globals": { - "addEventListener": true, - "clearTimeout": true, - "removeEventListener": true, - "setTimeout": true + "fetch": true } }, - "fuse.js": { + "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { "globals": { - "console": true, - "define": true + "fetch": true } }, - "ganache>secp256k1": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": { "packages": { - "@metamask/ppom-validator>elliptic": true + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true + } + }, + "string.prototype.matchall>es-abstract>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@ngraveio/bc-ur>assert>object-is": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true } }, "gulp>vinyl-fs>object.assign": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, "string.prototype.matchall>call-bind": true, "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>has-symbols": true + "string.prototype.matchall>has-symbols": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "he": { - "globals": { - "define": true + "@metamask/object-multiplex>once": { + "packages": { + "@metamask/object-multiplex>once>wrappy": true } }, - "history": { + "crypto-browserify>public-encrypt>parse-asn1": { + "packages": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "browserify>buffer": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "crypto-browserify>pbkdf2": true + } + }, + "react-syntax-highlighter>refractor>parse-entities": { "globals": { - "console": true, - "define": true, - "document.defaultView": true, - "document.querySelector": true + "document.createElement": true } }, - "https-browserify": { + "path-browserify": { "packages": { - "browserify>url": true, - "stream-http": true + "process": true } }, - "koa>content-disposition>safe-buffer": { + "serve-handler>path-to-regexp": { "packages": { - "browserify>buffer": true + "serve-handler>path-to-regexp>isarray": true } }, - "koa>is-generator-function": { + "crypto-browserify>pbkdf2": { + "globals": { + "crypto": true, + "process": true, + "queueMicrotask": true, + "setImmediate": true, + "setTimeout": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true + "ethereumjs-util>create-hash": true, + "process": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "koa>is-generator-function>has-tostringtag": { - "packages": { - "string.prototype.matchall>has-symbols": true + "@material-ui/core>popper.js": { + "globals": { + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator": true, + "requestAnimationFrame": true, + "setTimeout": true } }, - "localforage": { + "react-tippy>popper.js": { "globals": { - "Blob": true, - "BlobBuilder": true, - "FileReader": true, - "IDBKeyRange": true, - "MSBlobBuilder": true, - "MozBlobBuilder": true, - "OIndexedDB": true, - "WebKitBlobBuilder": true, - "atob": true, - "btoa": true, - "console.error": true, - "console.info": true, + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, "console.warn": true, "define": true, - "fetch": true, - "indexedDB": true, - "localStorage": true, - "mozIndexedDB": true, - "msIndexedDB": true, - "navigator.platform": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, "navigator.userAgent": true, - "openDatabase": true, - "setTimeout": true, - "webkitIndexedDB": true + "requestAnimationFrame": true, + "setTimeout": true } }, - "lodash": { + "process": { "globals": { "clearTimeout": true, - "define": true, "setTimeout": true } }, - "loglevel": { + "promise-to-callback": { + "packages": { + "promise-to-callback>is-fn": true, + "promise-to-callback>set-immediate-shim": true + } + }, + "prop-types": { "globals": { - "console": true, - "define": true, - "document.cookie": true, - "localStorage": true, - "log": "write", - "navigator": true + "console": true + }, + "packages": { + "react>object-assign": true, + "prop-types>react-is": true } }, - "lottie-web": { + "react-markdown>property-information": { + "packages": { + "watchify>xtend": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { "globals": { - "Blob": true, - "Howl": true, - "OffscreenCanvas": true, - "URL.createObjectURL": true, - "Worker": true, - "XMLHttpRequest": true, - "bodymovin": "write", - "clearInterval": true, - "console": true, - "define": true, - "document.body": true, - "document.createElement": true, - "document.createElementNS": true, - "document.getElementsByClassName": true, - "document.getElementsByTagName": true, - "document.querySelectorAll": true, - "document.readyState": true, - "location.origin": true, - "location.pathname": true, - "navigator": true, - "requestAnimationFrame": true, - "setInterval": true, + "process": true, "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true } }, - "luxon": { - "globals": { - "Intl": true + "crypto-browserify>public-encrypt": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "crypto-browserify>randombytes": true } }, - "nanoid": { + "browserify>punycode": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "define": true } }, - "nock>debug": { + "qrcode-generator": { "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "nock>debug>ms": true, - "process": true + "define": true } }, - "node-fetch": { + "qrcode.react": { "globals": { - "Headers": true, - "Request": true, - "Response": true, - "fetch": true + "Path2D": true, + "devicePixelRatio": true + }, + "packages": { + "react": true } }, - "path-browserify": { + "@storybook/addon-knobs>qs": { "packages": { - "process": true + "string.prototype.matchall>side-channel": true } }, - "process": { + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { "globals": { - "clearTimeout": true, - "setTimeout": true - } - }, - "promise-to-callback": { - "packages": { - "promise-to-callback>is-fn": true, - "promise-to-callback>set-immediate-shim": true + "queueMicrotask": true } }, - "promise-to-callback>set-immediate-shim": { + "react-beautiful-dnd>raf-schd": { "globals": { - "setTimeout.apply": true - }, - "packages": { - "browserify>timers-browserify": true + "cancelAnimationFrame": true, + "requestAnimationFrame": true } }, - "prop-types": { + "crypto-browserify>randombytes": { "globals": { - "console": true + "crypto": true, + "msCrypto": true }, "packages": { - "prop-types>react-is": true, - "react>object-assign": true - } - }, - "prop-types>react-is": { - "globals": { - "console": true + "process": true, + "koa>content-disposition>safe-buffer": true } }, - "qrcode-generator": { + "ethereumjs-wallet>randombytes": { "globals": { - "define": true + "crypto.getRandomValues": true } }, - "qrcode.react": { + "crypto-browserify>randomfill": { "globals": { - "Path2D": true, - "devicePixelRatio": true + "crypto": true, + "msCrypto": true }, "packages": { - "react": true + "process": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true } }, "react": { @@ -5027,8 +4863,8 @@ "console": true }, "packages": { - "prop-types": true, - "react>object-assign": true + "react>object-assign": true, + "prop-types": true } }, "react-beautiful-dnd": { @@ -5050,43 +4886,28 @@ }, "packages": { "@babel/runtime": true, - "react": true, "react-beautiful-dnd>css-box-model": true, "react-beautiful-dnd>memoize-one": true, "react-beautiful-dnd>raf-schd": true, - "react-beautiful-dnd>use-memo-one": true, + "react": true, "react-dom": true, "react-redux": true, - "redux": true + "redux": true, + "react-beautiful-dnd>use-memo-one": true } }, - "react-beautiful-dnd>css-box-model": { + "react-chartjs-2": { "globals": { - "getComputedStyle": true, - "pageXOffset": true, - "pageYOffset": true + "setTimeout": true }, "packages": { - "react-router-dom>tiny-invariant": true - } - }, - "react-beautiful-dnd>raf-schd": { - "globals": { - "cancelAnimationFrame": true, - "requestAnimationFrame": true - } - }, - "react-beautiful-dnd>use-memo-one": { - "packages": { + "chart.js": true, "react": true } }, - "react-chartjs-2": { - "globals": { - "setTimeout": true - }, + "react-focus-lock>react-clientside-effect": { "packages": { - "chart.js": true, + "@babel/runtime": true, "react": true } }, @@ -5131,22 +4952,28 @@ "trustedTypes": true }, "packages": { + "react>object-assign": true, "prop-types": true, "react": true, - "react-dom>scheduler": true, - "react>object-assign": true + "react-dom>scheduler": true } }, - "react-dom>scheduler": { + "react-responsive-carousel>react-easy-swipe": { "globals": { - "MessageChannel": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "console": true, - "navigator": true, - "performance": true, - "requestAnimationFrame": true, - "setTimeout": true + "addEventListener": true, + "define": true, + "document.addEventListener": true, + "document.removeEventListener": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-popper>react-fast-compare": { + "globals": { + "Element": true, + "console.warn": true } }, "react-focus-lock": { @@ -5160,666 +4987,726 @@ }, "packages": { "@babel/runtime": true, + "react-focus-lock>focus-lock": true, "prop-types": true, "react": true, - "react-focus-lock>focus-lock": true, "react-focus-lock>react-clientside-effect": true, "react-focus-lock>use-callback-ref": true, "react-focus-lock>use-sidecar": true } }, - "react-focus-lock>focus-lock": { + "react-idle-timer": { "globals": { - "HTMLIFrameElement": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.DOCUMENT_NODE": true, - "Node.DOCUMENT_POSITION_CONTAINED_BY": true, - "Node.DOCUMENT_POSITION_CONTAINS": true, - "Node.ELEMENT_NODE": true, - "console.error": true, + "clearTimeout": true, + "document": true, + "setTimeout": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-inspector": { + "globals": { + "Node": true, + "chromeDark": true, + "chromeLight": true + }, + "packages": { + "react": true + } + }, + "prop-types>react-is": { + "globals": { + "console": true + } + }, + "react-markdown>react-is": { + "globals": { + "console": true + } + }, + "react-redux>react-is": { + "globals": { + "console": true + } + }, + "react-markdown": { + "globals": { + "console.warn": true + }, + "packages": { + "react-markdown>comma-separated-tokens": true, + "prop-types": true, + "react-markdown>property-information": true, + "react": true, + "react-markdown>react-is": true, + "react-markdown>remark-parse": true, + "react-markdown>remark-rehype": true, + "react-markdown>space-separated-tokens": true, + "react-markdown>style-to-object": true, + "react-markdown>unified": true, + "react-markdown>unist-util-visit": true, + "react-markdown>vfile": true + } + }, + "react-popper": { + "globals": { + "document": true + }, + "packages": { + "@popperjs/core": true, + "react": true, + "react-popper>react-fast-compare": true, + "react-popper>warning": true + } + }, + "react-redux": { + "globals": { + "console": true, + "document": true + }, + "packages": { + "@babel/runtime": true, + "react-redux>hoist-non-react-statics": true, + "prop-types": true, + "react": true, + "react-dom": true, + "react-redux>react-is": true + } + }, + "react-responsive-carousel": { + "globals": { + "HTMLElement": true, + "addEventListener": true, + "clearTimeout": true, "console.warn": true, "document": true, - "getComputedStyle": true, + "getComputedStyle": true, + "removeEventListener": true, + "setTimeout": true + }, + "packages": { + "classnames": true, + "react": true, + "react-dom": true, + "react-responsive-carousel>react-easy-swipe": true + } + }, + "react-router-dom": { + "packages": { + "react-router-dom>history": true, + "prop-types": true, + "react": true, + "react-router-dom>react-router": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom-v5-compat": { + "globals": { + "FormData": true, + "URL": true, + "URLSearchParams": true, + "__reactRouterVersion": "write", + "addEventListener": true, + "confirm": true, + "define": true, + "document": true, + "history.scrollRestoration": true, + "location.href": true, + "removeEventListener": true, + "scrollTo": true, + "scrollY": true, + "sessionStorage.getItem": true, + "sessionStorage.setItem": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true + "react-router-dom-v5-compat>@remix-run/router": true, + "history": true, + "react": true, + "react-dom": true, + "react-router-dom": true, + "react-router-dom-v5-compat>react-router": true } }, - "react-focus-lock>react-clientside-effect": { + "react-router-dom>react-router": { "packages": { - "@babel/runtime": true, - "react": true + "react-router-dom>history": true, + "react-redux>hoist-non-react-statics": true, + "serve-handler>path-to-regexp": true, + "prop-types": true, + "react": true, + "prop-types>react-is": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true } }, - "react-focus-lock>use-callback-ref": { + "react-router-dom-v5-compat>react-router": { + "globals": { + "console.error": true, + "define": true + }, "packages": { + "react-router-dom-v5-compat>@remix-run/router": true, "react": true } }, - "react-focus-lock>use-sidecar": { + "react-simple-file-input": { "globals": { - "console.error": true + "File": true, + "FileReader": true, + "console.warn": true }, "packages": { - "@swc/helpers>tslib": true, - "react": true, - "react-focus-lock>use-sidecar>detect-node-es": true + "prop-types": true, + "react": true } }, - "react-idle-timer": { + "react-tippy": { "globals": { + "Element": true, + "MSStream": true, + "MutationObserver": true, + "addEventListener": true, "clearTimeout": true, + "console.error": true, + "console.warn": true, + "define": true, "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.maxTouchPoints": true, + "navigator.msMaxTouchPoints": true, + "navigator.userAgent": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "react-tippy>popper.js": true, + "react": true, + "react-dom": true } }, - "react-inspector": { + "react-toggle-button": { "globals": { - "Node": true, - "chromeDark": true, - "chromeLight": true + "clearTimeout": true, + "console.warn": true, + "define": true, + "performance": true, + "setTimeout": true }, "packages": { "react": true } }, - "react-markdown": { + "@material-ui/core>react-transition-group": { "globals": { - "console.warn": true + "Element": true, + "setTimeout": true }, "packages": { + "@material-ui/core>react-transition-group>dom-helpers": true, "prop-types": true, "react": true, - "react-markdown>comma-separated-tokens": true, - "react-markdown>property-information": true, - "react-markdown>react-is": true, - "react-markdown>remark-parse": true, - "react-markdown>remark-rehype": true, - "react-markdown>space-separated-tokens": true, - "react-markdown>style-to-object": true, - "react-markdown>unified": true, - "react-markdown>unist-util-visit": true, - "react-markdown>vfile": true + "react-dom": true } }, - "react-markdown>property-information": { + "readable-stream": { "packages": { - "watchify>xtend": true + "browserify>browser-resolve": true, + "browserify>buffer": true, + "webpack>events": true, + "pumpify>inherits": true, + "process": true, + "browserify>string_decoder": true, + "readable-stream>util-deprecate": true } }, - "react-markdown>react-is": { + "extension-port-stream>readable-stream": { "globals": { - "console": true - } - }, - "react-markdown>remark-parse": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, - "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true, - "react-syntax-highlighter>refractor>parse-entities": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { + "AbortController": true, + "AbortSignal": true, + "AggregateError": true, + "Blob": true, + "ERR_INVALID_ARG_TYPE": true, + "queueMicrotask": true + }, "packages": { - "react-syntax-highlighter>refractor>parse-entities": true + "@lavamoat/lavapack>readable-stream>abort-controller": true, + "browserify>buffer": true, + "webpack>events": true, + "process": true, + "browserify>string_decoder": true } }, - "react-markdown>remark-rehype": { + "@metamask/snaps-controllers>readable-web-to-node-stream": { "packages": { - "react-markdown>remark-rehype>mdast-util-to-hast": true + "readable-stream": true } }, - "react-markdown>remark-rehype>mdast-util-to-hast": { + "redux": { "globals": { - "console.warn": true + "console": true }, "packages": { - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, - "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, - "react-markdown>unist-util-visit": true + "@babel/runtime": true } }, - "react-markdown>style-to-object": { + "string.prototype.matchall>regexp.prototype.flags": { "packages": { - "react-markdown>style-to-object>inline-style-parser": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": true } }, - "react-markdown>unified": { + "react-markdown>remark-parse": { "packages": { - "mocha>yargs-unparser>is-plain-obj": true, - "react-markdown>unified>bail": true, - "react-markdown>unified>extend": true, - "react-markdown>unified>is-buffer": true, - "react-markdown>unified>trough": true, - "react-markdown>vfile": true + "react-markdown>remark-parse>mdast-util-from-markdown": true } }, - "react-markdown>unist-util-visit": { + "react-markdown>remark-rehype": { "packages": { - "react-markdown>unist-util-visit>unist-util-visit-parents": true + "react-markdown>remark-rehype>mdast-util-to-hast": true } }, - "react-markdown>unist-util-visit>unist-util-visit-parents": { + "react-markdown>vfile>replace-ext": { "packages": { - "react-markdown>unist-util-visit>unist-util-is": true + "path-browserify": true } }, - "react-markdown>vfile": { - "packages": { - "path-browserify": true, - "process": true, - "react-markdown>vfile>is-buffer": true, - "react-markdown>vfile>replace-ext": true, - "react-markdown>vfile>vfile-message": true + "reselect": { + "globals": { + "WeakRef": true, + "console.warn": true, + "unstable_autotrackMemoize": true } }, - "react-markdown>vfile>replace-ext": { + "@metamask/snaps-utils>rfdc": { "packages": { - "path-browserify": true + "browserify>buffer": true } }, - "react-markdown>vfile>vfile-message": { + "ethereumjs-util>create-hash>ripemd160": { "packages": { - "react-markdown>vfile>unist-util-stringify-position": true + "browserify>buffer": true, + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true } }, - "react-popper": { - "globals": { - "document": true - }, + "@keystonehq/metamask-airgapped-keyring>rlp": { "packages": { - "@popperjs/core": true, - "react": true, - "react-popper>react-fast-compare": true, - "react-popper>warning": true - } - }, - "react-popper>react-fast-compare": { - "globals": { - "Element": true, - "console.warn": true + "bn.js": true, + "browserify>buffer": true } }, - "react-popper>warning": { + "eth-lattice-keyring>rlp": { "globals": { - "console": true + "TextEncoder": true } }, - "react-redux": { - "globals": { - "console": true, - "document": true - }, + "ethereumjs-util>rlp": { "packages": { - "@babel/runtime": true, - "prop-types": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true, - "react-redux>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>hoist-non-react-statics": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { "packages": { - "prop-types>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>react-is": { + "wait-on>rxjs": { "globals": { - "console": true + "cancelAnimationFrame": true, + "clearInterval": true, + "clearTimeout": true, + "performance": true, + "requestAnimationFrame": true, + "setInterval.apply": true, + "setTimeout.apply": true } }, - "react-responsive-carousel": { + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, + "react-dom>scheduler": { "globals": { - "HTMLElement": true, - "addEventListener": true, + "MessageChannel": true, + "cancelAnimationFrame": true, "clearTimeout": true, - "console.warn": true, - "document": true, - "getComputedStyle": true, - "removeEventListener": true, + "console": true, + "navigator": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true - }, - "packages": { - "classnames": true, - "react": true, - "react-dom": true, - "react-responsive-carousel>react-easy-swipe": true } }, - "react-responsive-carousel>react-easy-swipe": { + "ethers>@ethersproject/json-wallets>scrypt-js": { "globals": { - "addEventListener": true, "define": true, - "document.addEventListener": true, - "document.removeEventListener": true + "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "browserify>timers-browserify": true } }, - "react-router-dom": { + "ganache>secp256k1": { "packages": { - "prop-types": true, - "react": true, - "react-router-dom>history": true, - "react-router-dom>react-router": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "@metamask/ppom-validator>elliptic": true } }, - "react-router-dom-v5-compat": { + "semver": { "globals": { - "FormData": true, - "URL": true, - "URLSearchParams": true, - "__reactRouterVersion": "write", - "addEventListener": true, - "confirm": true, - "define": true, - "document": true, - "history.scrollRestoration": true, - "location.href": true, - "removeEventListener": true, - "scrollTo": true, - "scrollY": true, - "sessionStorage.getItem": true, - "sessionStorage.setItem": true, - "setTimeout": true + "console.error": true }, "packages": { - "history": true, - "react": true, - "react-dom": true, - "react-router-dom": true, - "react-router-dom-v5-compat>@remix-run/router": true, - "react-router-dom-v5-compat>react-router": true + "process": true } }, - "react-router-dom-v5-compat>@remix-run/router": { - "globals": { - "AbortController": true, - "DOMException": true, - "FormData": true, - "Headers": true, - "Request": true, - "Response": true, - "URL": true, - "URLSearchParams": true, - "console": true, - "document.defaultView": true + "string.prototype.matchall>call-bind>set-function-length": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>gopd": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true } }, - "react-router-dom-v5-compat>react-router": { - "globals": { - "console.error": true, - "define": true - }, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": { "packages": { - "react": true, - "react-router-dom-v5-compat>@remix-run/router": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true } }, - "react-router-dom>history": { + "promise-to-callback>set-immediate-shim": { "globals": { - "addEventListener": true, - "confirm": true, - "document": true, - "history": true, - "location": true, - "navigator.userAgent": true, - "removeEventListener": true + "setTimeout.apply": true }, "packages": { - "react-router-dom>history>resolve-pathname": true, - "react-router-dom>history>value-equal": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "browserify>timers-browserify": true } }, - "react-router-dom>react-router": { + "addons-linter>sha.js": { "packages": { - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-redux>hoist-non-react-statics": true, - "react-router-dom>history": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true, - "serve-handler>path-to-regexp": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "react-router-dom>tiny-warning": { - "globals": { - "console": true + "string.prototype.matchall>side-channel": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>object-inspect": true } }, - "react-simple-file-input": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "File": true, - "FileReader": true, + "console.error": true, "console.warn": true }, "packages": { - "prop-types": true, - "react": true + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "ethers": true, + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true } }, - "react-syntax-highlighter>refractor>parse-entities": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { "globals": { - "document.createElement": true + "StopIteration": true + }, + "packages": { + "string.prototype.matchall>internal-slot": true } }, - "react-tippy": { + "stream-browserify": { + "packages": { + "webpack>events": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "stream-http": { "globals": { - "Element": true, - "MSStream": true, - "MutationObserver": true, - "addEventListener": true, + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, "clearTimeout": true, - "console.error": true, - "console.warn": true, - "define": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.maxTouchPoints": true, - "navigator.msMaxTouchPoints": true, - "navigator.userAgent": true, - "performance": true, - "requestAnimationFrame": true, + "fetch": true, + "location.protocol.search": true, "setTimeout": true }, "packages": { - "react": true, - "react-dom": true, - "react-tippy>popper.js": true + "browserify>buffer": true, + "stream-http>builtin-status-codes": true, + "pumpify>inherits": true, + "process": true, + "readable-stream": true, + "browserify>url": true, + "watchify>xtend": true } }, - "react-tippy>popper.js": { - "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.userAgent": true, - "requestAnimationFrame": true, - "setTimeout": true + "@metamask/snaps-controllers>tar-stream>streamx": { + "packages": { + "webpack>events": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true } }, - "react-toggle-button": { - "globals": { - "clearTimeout": true, - "console.warn": true, - "define": true, - "performance": true, - "setTimeout": true - }, + "browserify>string_decoder": { "packages": { - "react": true + "koa>content-disposition>safe-buffer": true } }, - "readable-stream": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": { "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>string_decoder": true, - "process": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true } }, - "readable-stream>util-deprecate": { - "globals": { - "console.trace": true, - "console.warn": true, - "localStorage": true + "react-markdown>style-to-object": { + "packages": { + "react-markdown>style-to-object>inline-style-parser": true } }, - "redux": { - "globals": { - "console": true - }, + "@metamask/snaps-controllers>tar-stream": { "packages": { - "@babel/runtime": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "browserify>browser-resolve": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true } }, - "semver": { + "debounce-stream>through": { + "packages": { + "process": true, + "stream-browserify": true + } + }, + "browserify>timers-browserify": { "globals": { - "console.error": true + "clearInterval": true, + "clearTimeout": true, + "setInterval": true, + "setTimeout": true }, "packages": { "process": true } }, - "serve-handler>path-to-regexp": { - "packages": { - "serve-handler>path-to-regexp>isarray": true + "react-router-dom>tiny-warning": { + "globals": { + "console": true } }, - "stream-browserify": { - "packages": { - "pumpify>inherits": true, - "readable-stream": true, - "webpack>events": true + "copy-to-clipboard>toggle-selection": { + "globals": { + "document.activeElement": true, + "document.getSelection": true + } + }, + "tslib": { + "globals": { + "SuppressedError": true, + "define": true } }, - "stream-http": { + "@metamask/eth-sig-util>tweetnacl": { "globals": { - "AbortController": true, - "Blob": true, - "MSStreamReader": true, - "ReadableStream": true, - "WritableStream": true, - "XDomainRequest": true, - "XMLHttpRequest": true, - "clearTimeout": true, - "fetch": true, - "location.protocol.search": true, - "setTimeout": true + "crypto": true, + "msCrypto": true, + "nacl": "write" }, "packages": { - "browserify>buffer": true, - "browserify>url": true, - "process": true, - "pumpify>inherits": true, - "readable-stream": true, - "stream-http>builtin-status-codes": true, - "watchify>xtend": true + "browserify>browser-resolve": true } }, - "string.prototype.matchall>call-bind": { - "packages": { - "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>call-bind>set-function-length": true, - "string.prototype.matchall>get-intrinsic": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>call-bind>es-define-property": { + "@ensdomains/content-hash>cids>uint8arrays": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>cids>multibase": true } }, - "string.prototype.matchall>call-bind>set-function-length": { + "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>gopd": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true, - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "string.prototype.matchall>define-properties": { + "react-markdown>unified": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true } }, - "string.prototype.matchall>define-properties>define-data-property": { + "react-markdown>unist-util-visit>unist-util-visit-parents": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true + "react-markdown>unist-util-visit>unist-util-is": true } }, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "react-markdown>unist-util-visit": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true + "react-markdown>unist-util-visit>unist-util-visit-parents": true } }, - "string.prototype.matchall>es-abstract>available-typed-arrays": { - "packages": { - "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true + "uri-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "browserify>url": { "packages": { - "string.prototype.matchall>has-symbols": true + "browserify>punycode": true, + "@storybook/addon-knobs>qs": true } }, - "string.prototype.matchall>es-abstract>gopd": { + "react-focus-lock>use-callback-ref": { "packages": { - "string.prototype.matchall>get-intrinsic": true + "react": true } }, - "string.prototype.matchall>es-abstract>has-property-descriptors": { + "react-beautiful-dnd>use-memo-one": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true + "react": true } }, - "string.prototype.matchall>es-abstract>is-array-buffer": { + "react-focus-lock>use-sidecar": { + "globals": { + "console.error": true + }, "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "react-focus-lock>use-sidecar>detect-node-es": true, + "react": true, + "tslib": true } }, - "string.prototype.matchall>es-abstract>is-callable": { + "readable-stream>util-deprecate": { "globals": { - "document": true + "console.trace": true, + "console.warn": true, + "localStorage": true } }, - "string.prototype.matchall>es-abstract>is-regex": { + "browserify>assert>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true, + "process": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "browserify>assert>util>inherits": true, + "process": true } }, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": { + "browserify>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true + }, "packages": { - "string.prototype.matchall>call-bind": true + "pumpify>inherits": true, + "browserify>util>is-arguments": true, + "koa>is-generator-function": true, + "browserify>util>is-typed-array": true, + "process": true, + "browserify>util>which-typed-array": true } }, - "string.prototype.matchall>es-abstract>object-inspect": { + "uuid": { "globals": { - "HTMLElement": true, - "WeakRef": true - }, - "packages": { - "browserify>browser-resolve": true + "crypto": true, + "msCrypto": true } }, - "string.prototype.matchall>get-intrinsic": { + "@metamask/eth-snap-keyring>uuid": { "globals": { - "AggregateError": true, - "FinalizationRegistry": true, - "WeakRef": true - }, - "packages": { - "browserify>has>function-bind": true, - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>has-proto": true, - "string.prototype.matchall>has-symbols": true + "crypto": true } }, - "string.prototype.matchall>internal-slot": { - "packages": { - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>side-channel": true + "@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": true + "eth-lattice-keyring>gridplus-sdk>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": { - "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "web3-stream-provider>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>side-channel": { + "@metamask/snaps-utils>validate-npm-package-name": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>object-inspect": true, - "string.prototype.matchall>get-intrinsic": true + "@metamask/snaps-utils>validate-npm-package-name>builtins": true } }, - "superstruct": { - "globals": { - "console.warn": true, - "define": true + "react-markdown>vfile>vfile-message": { + "packages": { + "react-markdown>vfile>unist-util-stringify-position": true } }, - "terser>source-map-support>buffer-from": { + "react-markdown>vfile": { "packages": { - "browserify>buffer": true + "react-markdown>vfile>is-buffer": true, + "path-browserify": true, + "process": true, + "react-markdown>vfile>replace-ext": true, + "react-markdown>vfile>vfile-message": true } }, - "uri-js": { + "browserify>vm-browserify": { "globals": { - "define": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true } }, - "uuid": { + "react-popper>warning": { "globals": { - "crypto": true, - "msCrypto": true + "console": true } }, - "wait-on>rxjs": { + "@ensdomains/content-hash>multihashes>web-encoding": { "globals": { - "cancelAnimationFrame": true, - "clearInterval": true, - "clearTimeout": true, - "performance": true, - "requestAnimationFrame": true, - "setInterval.apply": true, - "setTimeout.apply": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>util": true } }, "web3": { @@ -5832,14 +5719,14 @@ "setTimeout": true }, "packages": { - "browserify>util": true, "readable-stream": true, + "browserify>util": true, "web3-stream-provider>uuid": true } }, - "web3-stream-provider>uuid": { + "@metamask/controllers>web3": { "globals": { - "crypto": true + "XMLHttpRequest": true } }, "webextension-polyfill": { @@ -5851,9 +5738,30 @@ "define": true } }, - "webpack>events": { - "globals": { - "console": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, + "eslint-plugin-react>array-includes>is-string": true, + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + } + }, + "@metamask/eth-token-tracker>deep-equal>which-collection": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + } + }, + "browserify>util>which-typed-array": { + "packages": { + "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind": true, + "browserify>util>which-typed-array>for-each": true, + "string.prototype.matchall>es-abstract>gopd": true, + "koa>is-generator-function>has-tostringtag": true } } } diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index a604526f155d..ed5a08cbb4fd 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -5,144 +5,124 @@ "regeneratorRuntime": "write" } }, - "@ensdomains/content-hash": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { "globals": { - "console.warn": true + "WeakRef": true }, "packages": { - "@ensdomains/content-hash>cids": true, - "@ensdomains/content-hash>js-base64": true, - "@ensdomains/content-hash>multicodec": true, - "@ensdomains/content-hash>multihashes": true, - "browserify>buffer": true + "browserify": true } }, - "@ensdomains/content-hash>cids": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes": true, - "@ensdomains/content-hash>cids>uint8arrays": true, - "@ensdomains/content-hash>multicodec": true + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true } }, - "@ensdomains/content-hash>cids>multibase": { + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "SuppressedError": true + } + }, + "@ensdomains/content-hash": { + "globals": { + "console.warn": true }, "packages": { - "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true + "browserify>buffer": true, + "@ensdomains/content-hash>cids": true, + "@ensdomains/content-hash>js-base64": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>multihashes": true } }, - "@ensdomains/content-hash>cids>multihashes": { + "@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true, - "@ensdomains/content-hash>cids>multihashes>varint": true, - "@ensdomains/content-hash>cids>uint8arrays": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>cids>uint8arrays": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>cids>multibase": true + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true } }, - "@ensdomains/content-hash>js-base64": { - "globals": { - "Base64": "write", - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true, - "define": true - }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { "packages": { - "browserify>buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays": true, - "sass-embedded>varint": true + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "webpack>events": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays": { + "@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "Buffer": true, - "TextDecoder": true, "TextEncoder": true - }, - "packages": { - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { "globals": { - "TextDecoder": true, - "TextEncoder": true, - "console.warn": true, - "crypto.subtle.digest": true - } - }, - "@ensdomains/content-hash>multihashes": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase": true, - "@ensdomains/content-hash>multihashes>varint": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true, - "@ensdomains/content-hash>multihashes>web-encoding": true, - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, - "@ensdomains/content-hash>multihashes>multibase>base-x": { + "@ethereumjs/tx": { "packages": { - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, - "@ensdomains/content-hash>multihashes>web-encoding": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { "packages": { - "browserify>util": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@ethereumjs/tx": { + "eth-lattice-keyring>@ethereumjs/tx": { "packages": { + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/common": true, "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethersproject/providers": true, "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true, "browserify>insert-module-globals>is-buffer": true } }, - "@ethereumjs/tx>@ethereumjs/common": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, "browserify>buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/common>crc-32": { - "globals": { - "DO_NOT_EXPORT_CRC": true, - "define": true - } - }, - "@ethereumjs/tx>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true } }, "@ethereumjs/tx>@ethereumjs/util": { @@ -150,88 +130,84 @@ "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true, "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@ethereumjs/tx>@ethereumjs/util>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true } }, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "@metamask/smart-transactions-controller>@ethereumjs/util": { "globals": { - "Headers": true, - "TextDecoder": true, - "URL": true, - "btoa": true, + "console.warn": true, "fetch": true }, "packages": { - "browserify>browserify-zlib": true, - "browserify>buffer": true, - "browserify>url": true, - "browserify>util": true, - "https-browserify": true, - "process": true, - "stream-http": true + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack>events": true } }, - "@ethereumjs/tx>ethereum-cryptography": { + "@ethersproject/abi": { "globals": { - "TextDecoder": true, - "crypto": true + "console.log": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true, - "@metamask/message-signing-snap>@noble/curves": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { + "ethers>@ethersproject/abstract-signer": { "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils>@scure/base": true + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true } }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "ethers>@ethersproject/address": { + "packages": { + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/rlp": true } }, - "@ethersproject/abi": { + "ethers>@ethersproject/base64": { "globals": { - "console.log": true + "atob": true, + "btoa": true }, "packages": { - "@ethersproject/bignumber": true, + "@ethersproject/bytes": true + } + }, + "ethers>@ethersproject/basex": { + "packages": { "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "ethers>@ethersproject/properties": true } }, "@ethersproject/bignumber": { "packages": { "@ethersproject/bytes": true, - "bn.js": true, - "ethers>@ethersproject/logger": true + "ethers>@ethersproject/logger": true, + "bn.js": true } }, "@ethersproject/bytes": { @@ -239,17 +215,22 @@ "ethers>@ethersproject/logger": true } }, + "ethers>@ethersproject/constants": { + "packages": { + "@ethersproject/bignumber": true + } + }, "@ethersproject/contracts": { "globals": { "setTimeout": true }, "packages": { "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/abstract-provider": true, "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/transactions": true @@ -257,10 +238,10 @@ }, "@ethersproject/hash": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "ethers>@ethersproject/address": true, "ethers>@ethersproject/base64": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/properties": true, @@ -269,9 +250,9 @@ }, "@ethersproject/hdnode": { "packages": { + "ethers>@ethersproject/basex": true, "@ethersproject/bignumber": true, "@ethersproject/bytes": true, - "ethers>@ethersproject/basex": true, "ethers>@ethersproject/logger": true, "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, @@ -282,1239 +263,1125 @@ "ethers>@ethersproject/wordlists": true } }, - "@ethersproject/providers": { - "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "ethers>@ethersproject/json-wallets": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/networks": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/keccak256": true, "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/pbkdf2": true, "ethers>@ethersproject/properties": true, "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true - } - }, - "@ethersproject/providers>@ethersproject/random": { - "globals": { - "crypto.getRandomValues": true + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/json-wallets>aes-js": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true } }, - "@ethersproject/providers>@ethersproject/web": { - "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, + "ethers>@ethersproject/keccak256": { "packages": { "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "eth-ens-namehash>js-sha3": true } }, - "@ethersproject/wallet": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/transactions": true + "ethers>@ethersproject/logger": { + "globals": { + "console": true } }, - "@keystonehq/bc-ur-registry-eth": { + "ethers>@ethersproject/providers>@ethersproject/networks": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { - "globals": { - "define": true - }, + "@metamask/test-bundler>@ethersproject/networks": { "packages": { - "@ngraveio/bc-ur": true, - "@swc/helpers>tslib": true, - "browserify>buffer": true, - "buffer": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true + "ethers>@ethersproject/logger": true } }, - "@keystonehq/metamask-airgapped-keyring": { + "ethers>@ethersproject/pbkdf2": { "packages": { - "@ethereumjs/tx": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, - "@metamask/obs-store": true, - "browserify>buffer": true, - "ethereumjs-util>rlp": true, - "uuid": true, - "webpack>events": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/sha2": true } }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { + "ethers>@ethersproject/properties": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@keystonehq/bc-ur-registry-eth": true, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { - "globals": { - "TextEncoder": true + "ethers>@ethersproject/logger": true } }, - "@lavamoat/lavadome-react": { + "@ethersproject/providers": { "globals": { - "Document.prototype": true, - "DocumentFragment.prototype": true, - "Element.prototype": true, - "Node.prototype": true, + "WebSocket": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, "console.warn": true, - "document": true + "setInterval": true, + "setTimeout": true }, "packages": { - "react": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "@metamask/test-bundler>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/providers>bech32": true } }, - "@material-ui/core": { + "ethers>@ethersproject/providers": { "globals": { - "Image": true, - "_formatMuiErrorMessage": true, - "addEventListener": true, + "WebSocket": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, + "console.log": true, "console.warn": true, - "document": true, - "getComputedStyle": true, - "getSelection": true, - "innerHeight": true, - "innerWidth": true, - "matchMedia": true, - "navigator": true, - "performance.now": true, - "removeEventListener": true, - "requestAnimationFrame": true, "setInterval": true, "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles": true, - "@material-ui/core>@material-ui/system": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "@material-ui/core>popper.js": true, - "@material-ui/core>react-transition-group": true, - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/providers>@ethersproject/networks": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/providers>bech32": true } }, - "@material-ui/core>@material-ui/styles": { + "@ethersproject/providers>@ethersproject/random": { "globals": { - "console.error": true, - "console.warn": true, - "document.createComment": true, - "document.head": true - }, - "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, - "@material-ui/core>@material-ui/styles>jss-plugin-global": true, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, - "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, - "@material-ui/core>@material-ui/utils": true, - "@material-ui/core>clsx": true, - "prop-types": true, - "react": true, - "react-redux>hoist-non-react-statics": true + "crypto.getRandomValues": true } }, - "@material-ui/core>@material-ui/styles>jss": { - "globals": { - "CSS": true, - "document.createElement": true, - "document.querySelector": true - }, + "ethers>@ethersproject/random": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { + "ethers>@ethersproject/rlp": { "packages": { - "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { - "globals": { - "CSS": true - }, + "ethers>@ethersproject/sha2": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2>hash.js": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-global": { + "ethers>@ethersproject/signing-key": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/signing-key>elliptic": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-nested": { + "ethers>@ethersproject/solidity": { "packages": { - "@babel/runtime": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { + "ethers>@ethersproject/strings": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "react-router-dom>tiny-warning": true + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { + "ethers>@ethersproject/transactions": { "packages": { - "@material-ui/core>@material-ui/styles>jss": true, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true + "ethers>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/signing-key": true } }, - "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { - "globals": { - "document.createElement": true, - "document.documentElement": true, - "getComputedStyle": true - }, + "ethers>@ethersproject/units": { "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": true + "@ethersproject/bignumber": true, + "ethers>@ethersproject/logger": true } }, - "@material-ui/core>@material-ui/styles>jss>is-in-browser": { - "globals": { - "document": true + "@ethersproject/wallet": { + "packages": { + "ethers>@ethersproject/abstract-provider": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/transactions": true } }, - "@material-ui/core>@material-ui/system": { + "@ethersproject/providers>@ethersproject/web": { "globals": { - "console.error": true + "clearTimeout": true, + "fetch": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@material-ui/core>@material-ui/utils": true, - "prop-types": true - } - }, - "@material-ui/core>@material-ui/utils": { - "packages": { - "@babel/runtime": true, - "prop-types": true, - "prop-types>react-is": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>popper.js": { + "ethers>@ethersproject/providers>@ethersproject/web": { "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator": true, - "requestAnimationFrame": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true + }, + "packages": { + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@material-ui/core>react-transition-group": { + "ethers>@ethersproject/web": { "globals": { - "Element": true, + "clearTimeout": true, + "fetch": true, "setTimeout": true }, "packages": { - "@material-ui/core>react-transition-group>dom-helpers": true, - "prop-types": true, - "react": true, - "react-dom": true - } - }, - "@material-ui/core>react-transition-group>dom-helpers": { - "packages": { - "@babel/runtime": true + "ethers>@ethersproject/base64": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask-institutional/custody-controller": { + "ethers>@ethersproject/wordlists": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-keyring": true, - "@metamask/obs-store": true + "@ethersproject/bytes": true, + "@ethersproject/hash": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/strings": true } }, - "@metamask-institutional/custody-keyring": { + "@metamask/notification-services-controller>firebase>@firebase/app": { "globals": { - "console.error": true, - "console.log": true, + "FinalizationRegistry": true, "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, - "@metamask-institutional/sdk": true, - "@metamask-institutional/types": true, - "@metamask/obs-store": true, - "crypto-browserify": true, - "gulp-sass>lodash.clonedeep": true, - "webpack>events": true - } - }, - "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": { - "globals": { - "console.log": true, - "fetch": true - } - }, - "@metamask-institutional/extension": { - "globals": { - "console.log": true - }, - "packages": { - "@metamask-institutional/custody-controller": true, - "@metamask-institutional/sdk": true, - "@metamask-institutional/types": true, - "gulp-sass>lodash.clonedeep": true + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask-institutional/institutional-features": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { "packages": { - "@metamask-institutional/custody-keyring": true, - "@metamask/obs-store": true - } - }, - "@metamask-institutional/rpc-allowlist": { - "globals": { - "URL": true + "@metamask/notification-services-controller>firebase>@firebase/util": true } }, - "@metamask-institutional/sdk": { + "@metamask/notification-services-controller>firebase>@firebase/installations": { "globals": { - "URLSearchParams": true, - "console.debug": true, + "BroadcastChannel": true, + "Headers": true, + "btoa": true, "console.error": true, - "console.log": true, - "fetch": true + "crypto": true, + "fetch": true, + "msCrypto": true, + "navigator.onLine": true, + "setTimeout": true }, "packages": { - "@metamask-institutional/sdk>@metamask-institutional/simplecache": true, - "crypto-browserify": true, - "webpack>events": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true } }, - "@metamask-institutional/transaction-update": { + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { "globals": { - "clearInterval": true, - "console.info": true, - "console.log": true, - "setInterval": true + "console": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/sdk": true, - "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": true, - "@metamask/obs-store": true, - "webpack>events": true + "tslib": true } }, - "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": { + "@metamask/notification-services-controller>firebase>@firebase/messaging": { "globals": { - "WebSocket": true, - "clearTimeout": true, - "console.log": true, + "Headers": true, + "Notification.maxActions": true, + "Notification.permission": true, + "Notification.requestPermission": true, + "PushSubscription.prototype.hasOwnProperty": true, + "ServiceWorkerRegistration": true, + "URL": true, + "addEventListener": true, + "atob": true, + "btoa": true, + "clients.matchAll": true, + "clients.openWindow": true, + "console.warn": true, + "document": true, + "fetch": true, + "indexedDB": true, + "location.href": true, + "location.origin": true, + "navigator": true, + "origin.replace": true, + "registration.showNotification": true, "setTimeout": true }, "packages": { - "webpack>events": true - } - }, - "@metamask/abi-utils": { - "packages": { - "@metamask/abi-utils>@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, + "@metamask/notification-services-controller>firebase>@firebase/installations": true, + "@metamask/notification-services-controller>firebase>@firebase/util": true, + "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, + "tslib": true } }, - "@metamask/abi-utils>@metamask/utils": { + "@metamask/notification-services-controller>firebase>@firebase/util": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "atob": true, + "browser": true, + "btoa": true, + "chrome": true, + "console": true, + "document": true, + "indexedDB": true, + "navigator": true, + "process": true, + "self": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "process": true } }, - "@metamask/accounts-controller": { + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { "packages": { + "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/eth-snap-keyring": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/utils": true, + "@keystonehq/bc-ur-registry-eth": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "eth-lattice-keyring>rlp": true, "uuid": true } }, - "@metamask/address-book-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true - } - }, - "@metamask/announcement-controller": { + "@keystonehq/bc-ur-registry-eth": { "packages": { - "@metamask/announcement-controller>@metamask/base-controller": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, + "browserify>buffer": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "uuid": true } }, - "@metamask/announcement-controller>@metamask/base-controller": { + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { "globals": { - "setTimeout": true + "define": true }, "packages": { - "immer": true - } + "@ngraveio/bc-ur": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "buffer": true, + "browserify>buffer": true, + "tslib": true + } }, - "@metamask/approval-controller": { - "globals": { - "console.info": true - }, + "@keystonehq/metamask-airgapped-keyring": { "packages": { - "@metamask/approval-controller>nanoid": true, - "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "@ethereumjs/tx": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, + "@keystonehq/bc-ur-registry-eth": true, + "@metamask/obs-store": true, + "browserify>buffer": true, + "webpack>events": true, + "@keystonehq/metamask-airgapped-keyring>rlp": true, + "uuid": true } }, - "@metamask/approval-controller>nanoid": { + "chart.js>@kurkle/color": { "globals": { - "crypto.getRandomValues": true + "define": true } }, - "@metamask/assets-controllers": { + "@lavamoat/lavadome-react": { "globals": { - "AbortController": true, - "Headers": true, - "URL": true, - "URLSearchParams": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setInterval": true, - "setTimeout": true + "Document.prototype": true, + "DocumentFragment.prototype": true, + "Element.prototype": true, + "Node.prototype": true, + "console.warn": true, + "document": true }, "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, - "@metamask/assets-controllers>@metamask/polling-controller": true, - "@metamask/base-controller": true, - "@metamask/contract-metadata": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "bn.js": true, - "cockatiel": true, - "ethers>@ethersproject/address": true, - "lodash": true, - "single-call-balance-checker-abi": true, - "uuid": true + "react": true } }, - "@metamask/assets-controllers>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true } }, - "@metamask/base-controller": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { "globals": { - "setTimeout": true - }, - "packages": { - "immer": true + "console.warn": true } }, - "@metamask/browser-passworder": { - "globals": { - "CryptoKey": true, - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { "packages": { - "@metamask/browser-passworder>@metamask/utils": true, - "browserify>buffer": true + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true } }, - "@metamask/browser-passworder>@metamask/utils": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "console.warn": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethersproject/abi": true, + "ethers>@ethersproject/rlp": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, "browserify>buffer": true, - "nock>debug": true, "semver": true } }, - "@metamask/controller-utils": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true + "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true + "wait-on>rxjs": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { + "globals": { + "__ledgerLogsListen": "write", + "console.error": true + } + }, + "@material-ui/core": { "globals": { + "Image": true, + "_formatMuiErrorMessage": true, + "addEventListener": true, + "clearInterval": true, + "clearTimeout": true, "console.error": true, - "console.log": true + "console.warn": true, + "document": true, + "getComputedStyle": true, + "getSelection": true, + "innerHeight": true, + "innerWidth": true, + "matchMedia": true, + "navigator": true, + "performance.now": true, + "removeEventListener": true, + "requestAnimationFrame": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles": true, + "@material-ui/core>@material-ui/system": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>popper.js": true, + "prop-types": true, + "react": true, + "react-dom": true, + "prop-types>react-is": true, + "@material-ui/core>react-transition-group": true } }, - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { + "@material-ui/core>@material-ui/styles": { + "globals": { + "console.error": true, + "console.warn": true, + "document.createComment": true, + "document.head": true + }, "packages": { - "browserify>buffer": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "react-redux>hoist-non-react-statics": true, + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, + "@material-ui/core>@material-ui/styles>jss-plugin-global": true, + "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, + "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, + "@material-ui/core>@material-ui/styles>jss": true, + "prop-types": true, + "react": true } }, - "@metamask/controllers>web3": { + "@material-ui/core>@material-ui/system": { "globals": { - "XMLHttpRequest": true + "console.error": true + }, + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "prop-types": true } }, - "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { - "globals": { - "fetch": true + "@material-ui/core>@material-ui/utils": { + "packages": { + "@babel/runtime": true, + "prop-types": true, + "prop-types>react-is": true } }, - "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": { "globals": { + "console.log": true, "fetch": true } }, - "@metamask/ens-controller": { + "@metamask-institutional/custody-controller": { "packages": { - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/ens-controller>@metamask/base-controller": true, - "@metamask/ens-controller>@metamask/utils": true, - "punycode": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-keyring": true, + "@metamask/obs-store": true } }, - "@metamask/ens-controller>@metamask/base-controller": { + "@metamask-institutional/custody-keyring": { "globals": { - "setTimeout": true + "console.error": true, + "console.log": true, + "console.warn": true }, "packages": { - "immer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/types": true, + "@metamask/obs-store": true, + "crypto-browserify": true, + "webpack>events": true, + "gulp-sass>lodash.clonedeep": true } }, - "@metamask/ens-controller>@metamask/utils": { + "@metamask-institutional/extension": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "console.log": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask-institutional/custody-controller": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/types": true, + "gulp-sass>lodash.clonedeep": true } }, - "@metamask/eth-json-rpc-filters": { - "globals": { - "console.error": true - }, + "@metamask-institutional/institutional-features": { "packages": { - "@metamask/eth-query": true, - "@metamask/json-rpc-engine": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@metamask-institutional/custody-keyring": true, + "@metamask/obs-store": true } }, - "@metamask/eth-json-rpc-middleware": { + "@metamask-institutional/rpc-allowlist": { "globals": { - "URL": true, + "URL": true + } + }, + "@metamask-institutional/sdk": { + "globals": { + "URLSearchParams": true, + "console.debug": true, "console.error": true, - "setTimeout": true + "console.log": true, + "fetch": true }, "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true + "@metamask-institutional/sdk>@metamask-institutional/simplecache": true, + "crypto-browserify": true, + "webpack>events": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "@metamask-institutional/transaction-update": { + "globals": { + "clearInterval": true, + "console.info": true, + "console.log": true, + "setInterval": true + }, "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": true, - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": true, + "@metamask/obs-store": true, + "webpack>events": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/rpc-errors": { + "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": { + "globals": { + "WebSocket": true, + "clearTimeout": true, + "console.log": true, + "setTimeout": true + }, "packages": { - "@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "webpack>events": true } }, - "@metamask/eth-json-rpc-middleware>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/abi-utils": { "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-json-rpc-provider": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring": { - "globals": { - "addEventListener": true, - "console.error": true, - "document.createElement": true, - "document.head.appendChild": true, - "fetch": true, - "removeEventListener": true - }, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, - "@metamask/eth-sig-util": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "browserify>buffer": true, - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": { - "globals": { - "TextEncoder": true + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": { - "globals": { - "console.warn": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": true, - "browserify>buffer": true, - "ethers>@ethersproject/rlp": true, - "semver": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service": { + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { - "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, - "setTimeout": true - }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/errors": { - "globals": { - "console.warn": true + "@metamask/accounts-controller": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/base-controller": true, + "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/accounts-controller>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "uuid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools": { + "@metamask/address-book-controller": { "packages": { - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/cryptoassets-evm-signatures": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": true, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": true, - "@metamask/ppom-validator>crypto-js": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>@ledgerhq/live-env": { + "@metamask/announcement-controller": { + "packages": { + "@metamask/announcement-controller>@metamask/base-controller": true + } + }, + "@metamask/approval-controller": { "globals": { - "console.warn": true + "console.info": true }, "packages": { - "wait-on>rxjs": true + "@metamask/base-controller": true, + "@metamask/rpc-errors": true, + "nanoid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { + "@metamask/assets-controllers": { "globals": { - "Blob": true, - "FormData": true, + "AbortController": true, + "Headers": true, + "URL": true, "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.log": true, + "setInterval": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true - } - }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { - "packages": { - "@ethersproject/abi": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "ethers>@ethersproject/address": true, "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, "@ethersproject/providers": true, - "@ethersproject/providers>@ethersproject/web": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/wordlists": true + "@metamask/abi-utils": true, + "@metamask/base-controller": true, + "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/metamask-eth-abis": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, + "cockatiel": true, + "lodash": true, + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, + "single-call-balance-checker-abi": true, + "uuid": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/logs": { + "@metamask/base-controller": { "globals": { - "__ledgerLogsListen": "write", - "console.error": true + "setTimeout": true + }, + "packages": { + "immer": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { + "@metamask/announcement-controller>@metamask/base-controller": { "globals": { - "Blob": true, - "FormData": true, - "URLSearchParams": true, - "XMLHttpRequest": true, - "btoa": true, - "console.warn": true, - "document": true, - "location.href": true, - "navigator": true, "setTimeout": true }, "packages": { - "axios>form-data": true, - "browserify>buffer": true, - "process": true + "immer": true } }, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { + "@metamask/name-controller>@metamask/base-controller": { "globals": { - "crypto": true, - "define": true + "setTimeout": true + }, + "packages": { + "immer": true } }, - "@metamask/eth-query": { + "@metamask/rate-limit-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "watchify>xtend": true + "immer": true } }, - "@metamask/eth-sig-util": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true - } - }, - "@metamask/eth-sig-util>@metamask/utils": { + "@metamask/browser-passworder": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "CryptoKey": true, + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/browser-passworder>@metamask/utils": true, + "browserify>buffer": true } }, - "@metamask/eth-sig-util>tweetnacl": { + "eth-keyring-controller>@metamask/browser-passworder": { "globals": { - "crypto": true, - "msCrypto": true, - "nacl": "write" - }, - "packages": { - "browserify>browser-resolve": true + "crypto": true } }, - "@metamask/eth-snap-keyring": { + "@metamask/controller-utils": { "globals": { "URL": true, - "console.error": true + "console.error": true, + "fetch": true, + "setTimeout": true }, - "packages": { - "@ethereumjs/tx": true, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/eth-snap-keyring>uuid": true, - "@metamask/keyring-api": true, - "@metamask/utils>@metamask/superstruct": true, - "webpack>events": true - } - }, - "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true - } - }, - "@metamask/eth-snap-keyring>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@metamask/controller-utils>@metamask/ethjs-unit": true, + "@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "bn.js": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "eth-ens-namehash": true, + "eslint>fast-deep-equal": true } }, - "@metamask/eth-snap-keyring>uuid": { - "globals": { - "crypto": true + "@metamask/ens-controller": { + "packages": { + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/utils": true, + "punycode": true } }, - "@metamask/eth-token-tracker": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { "globals": { - "console.warn": true + "clearTimeout": true, + "console.error": true, + "setTimeout": true }, "packages": { - "@babel/runtime": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, - "@metamask/eth-token-tracker>deep-equal": true, - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, "@metamask/safe-event-emitter": true, - "bn.js": true, - "human-standard-token-abi": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true, + "pify": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker": { + "@metamask/network-controller>@metamask/eth-block-tracker": { "globals": { "clearTimeout": true, "console.error": true, "setTimeout": true }, "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": true, "@metamask/safe-event-emitter": true, - "pify": true + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, + "@metamask/ppom-validator>json-rpc-random-id": true } }, - "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring": { "globals": { - "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/scure-bip39": true, + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true } }, - "@metamask/eth-token-tracker>deep-equal": { + "@metamask/eth-json-rpc-filters": { + "globals": { + "console.error": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, - "@metamask/eth-token-tracker>deep-equal>is-date-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, - "@metamask/eth-token-tracker>deep-equal>which-collection": true, - "@ngraveio/bc-ur>assert>object-is": true, - "browserify>util>is-arguments": true, - "browserify>util>which-typed-array": true, - "gulp>vinyl-fs>object.assign": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true, - "string.prototype.matchall>es-abstract>is-regex": true, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>regexp.prototype.flags": true, - "string.prototype.matchall>side-channel": true + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/safe-event-emitter": true, + "@metamask/name-controller>async-mutex": true, + "pify": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "fetch": true, + "setTimeout": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true, - "browserify>util>is-arguments": true, - "eslint-plugin-react>array-includes>is-string": true, - "process": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>has-symbols": true + "@metamask/eth-json-rpc-provider": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true } }, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { + "@metamask/eth-json-rpc-middleware": { "globals": { - "StopIteration": true + "URL": true, + "console.error": true, + "setTimeout": true }, "packages": { - "string.prototype.matchall>internal-slot": true + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/eth-json-rpc-middleware>klona": true, + "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true } }, - "@metamask/eth-token-tracker>deep-equal>is-date-object": { + "@metamask/eth-json-rpc-provider": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "uuid": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "@metamask/eth-ledger-bridge-keyring": { + "globals": { + "addEventListener": true, + "console.error": true, + "document.createElement": true, + "document.head.appendChild": true, + "fetch": true, + "removeEventListener": true + }, "packages": { - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, - "eslint-plugin-react>array-includes>is-string": true, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { + "@metamask/controller-utils>@metamask/eth-query": { "packages": { - "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true + "@metamask/ppom-validator>json-rpc-random-id": true, + "watchify>xtend": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { + "@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/abi-utils": true, + "@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, - "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring": { - "globals": { - "setTimeout": true - }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, - "@metamask/eth-trezor-keyring>hdkey": true, - "@trezor/connect-web": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "webpack>events": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { + "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { - "@metamask/eth-sig-util": true, - "@swc/helpers>tslib": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/eth-trezor-keyring>hdkey": { + "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { - "browserify>assert": true, - "crypto-browserify": true, - "ethereumjs-util>create-hash>ripemd160": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true } }, - "@metamask/etherscan-link": { - "globals": { - "URL": true + "@metamask/keyring-controller>@metamask/eth-simple-keyring": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-sig-util": true, + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "crypto-browserify>randombytes": true } }, - "@metamask/ethjs": { + "@metamask/eth-snap-keyring": { "globals": { - "clearInterval": true, - "setInterval": true + "URL": true, + "console.error": true, + "console.info": true }, "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-provider-http": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/number-to-bn": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "@ethereumjs/tx": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, + "@metamask/keyring-api": true, + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "webpack>events": true, + "@metamask/eth-snap-keyring>uuid": true } }, - "@metamask/ethjs-contract": { + "@metamask/eth-token-tracker": { + "globals": { + "console.warn": true + }, "packages": { "@babel/runtime": true, - "@metamask/ethjs>@metamask/ethjs-filter": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>ethjs-abi": true, - "@metamask/ethjs>js-sha3": true, - "promise-to-callback": true + "@metamask/eth-token-tracker>@metamask/eth-block-tracker": true, + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true, + "@metamask/safe-event-emitter": true, + "bn.js": true, + "@metamask/eth-token-tracker>deep-equal": true, + "human-standard-token-abi": true } }, - "@metamask/ethjs-query": { + "@metamask/eth-trezor-keyring": { "globals": { - "console": true + "setTimeout": true }, "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format": true, - "@metamask/ethjs-query>@metamask/ethjs-rpc": true, - "promise-to-callback": true + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@trezor/connect-web": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/eth-trezor-keyring>hdkey": true } }, - "@metamask/ethjs-query>@metamask/ethjs-format": { - "packages": { - "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, - "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "@metamask/ethjs>@metamask/number-to-bn": true + "@metamask/etherscan-link": { + "globals": { + "URL": true } }, - "@metamask/ethjs-query>@metamask/ethjs-rpc": { + "eth-method-registry>@metamask/ethjs-contract": { "packages": { + "@babel/runtime": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": true, + "eth-ens-namehash>js-sha3": true, "promise-to-callback": true } }, - "@metamask/ethjs>@metamask/ethjs-filter": { + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { "globals": { "clearInterval": true, "setInterval": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { "packages": { - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": true + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, - "@metamask/ethjs>@metamask/ethjs-provider-http>xhr2": { + "eth-method-registry>@metamask/ethjs-query": { "globals": { - "XMLHttpRequest": true - } - }, - "@metamask/ethjs>@metamask/ethjs-unit": { - "packages": { - "@metamask/ethjs>@metamask/number-to-bn": true, - "bn.js": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "browserify>buffer": true - } - }, - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, - "@metamask/ethjs>@metamask/number-to-bn": { + "console": true + }, "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, - "bn.js": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "@metamask/ethjs>js-sha3": true, - "bn.js": true, - "browserify>buffer": true + "promise-to-callback": true } }, - "@metamask/ethjs>ethjs-abi>number-to-bn": { + "@metamask/controller-utils>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": true, "bn.js": true } }, - "@metamask/ethjs>js-sha3": { - "globals": { - "define": true - }, + "eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { "packages": { - "process": true + "browserify>buffer": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, "@metamask/gas-fee-controller": { @@ -1525,32 +1392,12 @@ }, "packages": { "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, "bn.js": true, "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true - } - }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1561,33 +1408,11 @@ "@metamask/jazzicon>mersenne-twister": true } }, - "@metamask/jazzicon>color": { - "packages": { - "@metamask/jazzicon>color>clone": true, - "@metamask/jazzicon>color>color-convert": true, - "@metamask/jazzicon>color>color-string": true - } - }, - "@metamask/jazzicon>color>clone": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask/jazzicon>color>color-convert": { - "packages": { - "@metamask/jazzicon>color>color-convert>color-name": true - } - }, - "@metamask/jazzicon>color>color-string": { - "packages": { - "jest-canvas-mock>moo-color>color-name": true - } - }, "@metamask/json-rpc-engine": { "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/json-rpc-engine>@metamask/utils": true } }, "@metamask/json-rpc-middleware-stream": { @@ -1597,39 +1422,28 @@ }, "packages": { "@metamask/safe-event-emitter": true, - "@metamask/utils": true, + "@metamask/json-rpc-middleware-stream>@metamask/utils": true, "readable-stream": true } }, - "@metamask/keyring-api": { + "@metamask/snaps-sdk>@metamask/key-tree": { "globals": { - "URL": true + "crypto.subtle": true }, "packages": { - "@metamask/keyring-api>@metamask/utils": true, - "@metamask/keyring-api>bech32": true, - "@metamask/keyring-api>uuid": true, - "@metamask/utils>@metamask/superstruct": true + "@metamask/scure-bip39": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/keyring-api>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-api": { "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-api>uuid": { - "globals": { - "crypto": true + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>bech32": true } }, "@metamask/keyring-controller": { @@ -1640,126 +1454,32 @@ "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, - "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/keyring-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "@metamask/utils": true - } - }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring": { - "globals": { - "TextEncoder": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": true, - "@metamask/scure-bip39": true, - "browserify>buffer": true - } - }, - "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/keyring-controller>ethereumjs-wallet": true } }, - "@metamask/keyring-controller>@metamask/eth-sig-util": { + "@metamask/eth-snap-keyring>@metamask/keyring-internal-snap-client": { "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "@metamask/keyring-snap-client": true } }, - "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/keyring-snap-client": { "packages": { + "@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring": { - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-sig-util": true, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "@metamask/keyring-snap-client>uuid": true } }, - "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { + "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "URL": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, - "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, - "browserify>buffer": true, - "crypto-browserify": true, - "crypto-browserify>randombytes": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "uuid": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>assert": true, - "browserify>buffer": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>bs58check": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethers>@ethersproject/sha2>hash.js": true, - "ganache>secp256k1": true, - "koa>content-disposition>safe-buffer": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { - "packages": { - "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>rlp": true + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": true, + "bitcoin-address-validation": true } }, "@metamask/logging-controller": { @@ -1786,53 +1506,20 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-sig-util": true, - "@metamask/message-manager>@metamask/utils": true, - "@metamask/message-manager>jsonschema": true, + "@metamask/utils": true, "browserify>buffer": true, - "uuid": true, - "webpack>events": true + "webpack>events": true, + "uuid": true } }, - "@metamask/message-manager>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/message-manager>jsonschema": { - "packages": { - "browserify>url": true - } - }, - "@metamask/message-signing-snap>@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true - } - }, - "@metamask/message-signing-snap>@noble/curves": { - "globals": { - "TextEncoder": true - }, + "@metamask/multichain": { "packages": { - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": true - } - }, - "@metamask/message-signing-snap>@noble/curves>@noble/hashes": { - "globals": { - "TextEncoder": true, - "crypto": true + "@metamask/multichain>@metamask/api-specs": true, + "@metamask/controller-utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils": true, + "lodash": true } }, "@metamask/name-controller": { @@ -1840,44 +1527,12 @@ "fetch": true }, "packages": { - "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/name-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true } }, - "@metamask/name-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/name-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/name-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@swc/helpers>tslib": true - } - }, "@metamask/network-controller": { "globals": { "btoa": true, @@ -1887,3183 +1542,3297 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, - "@metamask/eth-json-rpc-provider": true, - "@metamask/eth-query": true, "@metamask/network-controller>@metamask/eth-block-tracker": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-provider": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/network-controller>reselect": true, - "browserify>assert": true, - "browserify>util": true, + "@metamask/utils": true, + "eslint>fast-deep-equal": true, + "reselect": true, "uri-js": true, "uuid": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, + "@metamask/transaction-controller>@metamask/nonce-tracker": { "packages": { - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": true, - "@metamask/safe-event-emitter": true, - "pify": true + "@ethersproject/providers": true, + "browserify>assert": true, + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true } }, - "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { + "@metamask/notification-services-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Intl.NumberFormat": true, + "addEventListener": true, + "fetch": true, + "registration": true, + "removeEventListener": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/profile-sync-controller": true, + "@metamask/utils": true, + "@metamask/notification-services-controller>bignumber.js": true, + "@metamask/notification-services-controller>firebase": true, + "loglevel": true, + "uuid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, + "@metamask/controller-utils>@metamask/ethjs-unit>@metamask/number-to-bn": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "node-fetch": true + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/eth-json-rpc-provider": { + "@metamask/object-multiplex": { + "globals": { + "console.warn": true + }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true + "@metamask/object-multiplex>once": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { + "@metamask/obs-store": { "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/safe-event-emitter": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors": { + "@metamask/permission-controller": { + "globals": { + "console.error": true + }, "packages": { - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/permission-controller>@metamask/utils": true, + "deep-freeze-strict": true, + "immer": true, + "nanoid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/rpc-errors>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, + "@metamask/permission-log-controller": { "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/base-controller": true, + "@metamask/permission-log-controller>@metamask/utils": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "@metamask/phishing-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "console.error": true, + "fetch": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography": true, + "webpack-cli>fastest-levenshtein": true, + "punycode": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware": { + "@metamask/polling-controller": { "globals": { - "URL": true, + "clearTimeout": true, "console.error": true, "setTimeout": true }, "packages": { - "@metamask/eth-json-rpc-middleware>klona": true, - "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, - "@metamask/eth-sig-util": true, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/network-controller>@metamask/rpc-errors": true, - "bn.js": true, - "pify": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-middleware>@metamask/utils": { + "@metamask/post-message-stream": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/post-message-stream>@metamask/utils": true, + "readable-stream": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/ppom-validator": { + "globals": { + "URL": true, + "console.error": true, + "crypto": true + }, "packages": { - "@metamask/network-controller>@metamask/rpc-errors": true, - "@metamask/network-controller>@metamask/utils": true, - "@metamask/safe-event-emitter": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "await-semaphore": true, + "browserify>buffer": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/ppom-validator>elliptic": true, + "@metamask/ppom-validator>json-rpc-random-id": true } }, - "@metamask/network-controller>@metamask/rpc-errors": { + "@metamask/preferences-controller": { "packages": { - "@metamask/network-controller>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true } }, - "@metamask/network-controller>@metamask/utils": { + "@metamask/profile-sync-controller": { "globals": { + "Event": true, + "Headers": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/keyring-controller": true, + "@metamask/network-controller": true, + "@metamask/profile-sync-controller>@noble/ciphers": true, "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/network-controller>reselect": { - "globals": { - "WeakRef": true, - "console.warn": true, - "unstable_autotrackMemoize": true + "loglevel": true, + "@metamask/profile-sync-controller>siwe": true } }, - "@metamask/notification-controller": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, + "@metamask/selected-network-controller": true, + "@metamask/utils": true } }, - "@metamask/notification-controller>@metamask/base-controller": { + "@metamask/rate-limit-controller": { "globals": { "setTimeout": true }, "packages": { - "immer": true - } - }, - "@metamask/notification-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, - "@metamask/notification-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@metamask/rate-limit-controller>@metamask/base-controller": true, + "@metamask/rate-limit-controller>@metamask/rpc-errors": true, + "@metamask/rate-limit-controller>@metamask/utils": true } }, - "@metamask/notification-services-controller": { - "globals": { - "Intl.NumberFormat": true, - "addEventListener": true, - "fetch": true, - "registration": true, - "removeEventListener": true - }, + "@metamask/remote-feature-flag-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": true, - "@metamask/notification-services-controller>bignumber.js": true, - "@metamask/notification-services-controller>firebase": true, - "@metamask/profile-sync-controller": true, - "@metamask/utils": true, - "loglevel": true, + "cockatiel": true, "uuid": true } }, - "@metamask/notification-services-controller>@contentful/rich-text-html-renderer": { - "globals": { - "SuppressedError": true - } - }, - "@metamask/notification-services-controller>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/rpc-errors": { + "packages": { + "@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-services-controller>firebase": { + "@metamask/rate-limit-controller>@metamask/rpc-errors": { "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/messaging": true + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, + "@metamask/rpc-errors>fast-safe-stringify": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app": { + "@metamask/safe-event-emitter": { "globals": { - "FinalizationRegistry": true, - "console.warn": true + "setTimeout": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true - } - }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": { - "packages": { - "@metamask/notification-services-controller>firebase>@firebase/util": true + "webpack>events": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/logger": { + "@metamask/scure-bip39": { "globals": { - "console": true + "TextEncoder": true }, "packages": { - "@swc/helpers>tslib": true + "@metamask/scure-bip39>@noble/hashes": true, + "@metamask/utils>@scure/base": true } }, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": { - "globals": { - "DOMException": true, - "IDBCursor": true, - "IDBDatabase": true, - "IDBIndex": true, - "IDBObjectStore": true, - "IDBRequest": true, - "IDBTransaction": true, - "indexedDB.deleteDatabase": true, - "indexedDB.open": true + "@metamask/selected-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true } }, - "@metamask/notification-services-controller>firebase>@firebase/installations": { + "@metamask/signature-controller": { "globals": { - "BroadcastChannel": true, - "Headers": true, - "btoa": true, - "console.error": true, - "crypto": true, - "fetch": true, - "msCrypto": true, - "navigator.onLine": true, - "setTimeout": true + "fetch": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util": true, + "@metamask/keyring-controller": true, + "@metamask/logging-controller": true, + "@metamask/utils": true, + "browserify>buffer": true, + "webpack>events": true, + "@metamask/message-manager>jsonschema": true, + "uuid": true } }, - "@metamask/notification-services-controller>firebase>@firebase/messaging": { + "@metamask/smart-transactions-controller": { "globals": { - "Headers": true, - "Notification.maxActions": true, - "Notification.permission": true, - "Notification.requestPermission": true, - "PushSubscription.prototype.hasOwnProperty": true, - "ServiceWorkerRegistration": true, - "URL": true, - "addEventListener": true, - "atob": true, - "btoa": true, - "clients.matchAll": true, - "clients.openWindow": true, - "console.warn": true, - "document": true, + "URLSearchParams": true, + "clearInterval": true, + "console.error": true, + "console.log": true, "fetch": true, - "indexedDB": true, - "location.href": true, - "location.origin": true, - "navigator": true, - "origin.replace": true, - "registration.showNotification": true, - "setTimeout": true + "setInterval": true }, "packages": { - "@metamask/notification-services-controller>firebase>@firebase/app": true, - "@metamask/notification-services-controller>firebase>@firebase/app>@firebase/component": true, - "@metamask/notification-services-controller>firebase>@firebase/app>idb": true, - "@metamask/notification-services-controller>firebase>@firebase/installations": true, - "@metamask/notification-services-controller>firebase>@firebase/util": true, - "@swc/helpers>tslib": true + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "@ethersproject/bytes": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/polling-controller": true, + "@metamask/transaction-controller": true, + "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, + "fast-json-patch": true, + "lodash": true } }, - "@metamask/notification-services-controller>firebase>@firebase/util": { + "@metamask/snaps-controllers": { "globals": { - "atob": true, - "browser": true, - "btoa": true, - "chrome": true, - "console": true, - "document": true, - "indexedDB": true, - "navigator": true, - "process": true, - "self": true, + "DecompressionStream": true, + "URL": true, + "clearTimeout": true, + "document.getElementById": true, + "fetch.bind": true, "setTimeout": true }, "packages": { - "process": true + "@metamask/base-controller": true, + "@metamask/json-rpc-engine": true, + "@metamask/json-rpc-middleware-stream": true, + "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, + "@metamask/post-message-stream": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/snaps-registry": true, + "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/utils": true, + "@metamask/snaps-controllers>@xstate/fsm": true, + "@metamask/name-controller>async-mutex": true, + "browserify>browserify-zlib": true, + "@metamask/snaps-controllers>concat-stream": true, + "eslint>fast-deep-equal": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, + "immer": true, + "luxon": true, + "nanoid": true, + "readable-stream": true, + "@metamask/snaps-controllers>readable-web-to-node-stream": true, + "semver": true, + "@metamask/snaps-controllers>tar-stream": true } }, - "@metamask/object-multiplex": { + "@metamask/snaps-execution-environments": { "globals": { - "console.warn": true + "document.getElementById": true }, "packages": { - "@metamask/object-multiplex>once": true, - "readable-stream": true - } - }, - "@metamask/object-multiplex>once": { - "packages": { - "@metamask/object-multiplex>once>wrappy": true + "@metamask/post-message-stream": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true, + "@metamask/snaps-execution-environments>@metamask/utils": true } }, - "@metamask/obs-store": { + "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { - "@metamask/safe-event-emitter": true, - "readable-stream": true + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@noble/hashes": true } }, - "@metamask/permission-controller": { - "globals": { - "console.error": true - }, + "@metamask/snaps-rpc-methods": { "packages": { - "@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/permission-controller>@metamask/utils": true, - "@metamask/permission-controller>nanoid": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@noble/hashes": true, + "luxon": true } }, - "@metamask/permission-controller>@metamask/base-controller": { + "@metamask/snaps-sdk": { "globals": { - "setTimeout": true + "fetch": true }, "packages": { - "immer": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/permission-controller>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-sdk>@metamask/utils": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/snaps-utils": { "globals": { + "File": true, + "FileReader": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/utils": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/transaction-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/common": true, + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/abi": true, + "@ethersproject/contracts": true, + "@ethersproject/providers": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/network-controller": true, + "@metamask/transaction-controller>@metamask/nonce-tracker": true, + "@metamask/rpc-errors": true, + "@metamask/transaction-controller>@metamask/utils": true, + "@metamask/name-controller>async-mutex": true, + "bn.js": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "eth-method-registry": true, + "webpack>events": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true } }, - "@metamask/permission-controller>@metamask/rpc-errors": { + "@metamask/user-operation-controller": { + "globals": { + "fetch": true + }, "packages": { - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/polling-controller": true, + "@metamask/rpc-errors": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/transaction-controller": true, + "@metamask/user-operation-controller>@metamask/utils": true, + "bn.js": true, + "webpack>events": true, + "lodash": true, + "uuid": true } }, - "@metamask/permission-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>@metamask/utils": { + "@metamask/abi-utils>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/permission-controller>nanoid": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/permission-log-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/permission-log-controller>@metamask/base-controller": true, - "@metamask/permission-log-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/base-controller": { + "@metamask/accounts-controller>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/permission-log-controller>@metamask/utils": { + "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/phishing-controller": { + "@metamask/controller-utils>@metamask/utils": { "globals": { - "TextEncoder": true, - "URL": true, - "console.error": true, - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, - "punycode": true, - "webpack-cli>fastest-levenshtein": true + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/polling-controller": { + "@metamask/eth-token-tracker>@metamask/eth-block-tracker>@metamask/utils": { "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream": { + "@metamask/network-controller>@metamask/eth-block-tracker>@metamask/utils": { "globals": { - "MessageEvent.prototype": true, - "WorkerGlobalScope": true, - "addEventListener": true, - "browser": true, - "chrome": true, - "location.origin": true, - "postMessage": true, - "removeEventListener": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "readable-stream": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/post-message-stream>@metamask/utils": { + "@metamask/keyring-controller>@metamask/eth-hd-keyring>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/ppom-validator": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { "globals": { - "URL": true, - "console.error": true, - "crypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query>json-rpc-random-id": true, - "@metamask/ppom-validator>crypto-js": true, - "@metamask/ppom-validator>elliptic": true, - "await-semaphore": true, - "browserify>buffer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>crypto-js": { + "@metamask/eth-json-rpc-middleware>@metamask/utils": { "globals": { - "crypto": true, - "define": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true - } - }, - "@metamask/ppom-validator>elliptic": { - "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "@metamask/ppom-validator>elliptic>hmac-drbg": true, - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "bn.js": true, - "ethers>@ethersproject/sha2>hash.js": true, - "pumpify>inherits": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/ppom-validator>elliptic>brorand": { + "@metamask/eth-sig-util>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true - } - }, - "@metamask/ppom-validator>elliptic>hmac-drbg": { - "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true, - "ethers>@ethersproject/sha2>hash.js": true - } - }, - "@metamask/preferences-controller": { - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller": { + "@metamask/eth-ledger-bridge-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "Event": true, - "Headers": true, "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "URLSearchParams": true, - "addEventListener": true, - "console.error": true, - "dispatchEvent": true, - "fetch": true, - "removeEventListener": true, - "setTimeout": true + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/keyring-api": true, - "@metamask/keyring-controller": true, - "@metamask/message-signing-snap>@noble/ciphers": true, - "@metamask/profile-sync-controller>siwe": true, + "@metamask/utils>@metamask/superstruct": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "loglevel": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "console.error": true, - "console.warn": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random": true, - "ethers": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { "globals": { - "console.error": true, - "console.log": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, - "@noble/hashes": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "@metamask/keyring-controller>@metamask/eth-simple-keyring>@metamask/utils": { "globals": { - "crypto": true, - "msCrypto": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, - "browserify>browser-resolve": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "@metamask/eth-snap-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/queued-request-controller": { + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller": { + "@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/rate-limit-controller>@metamask/base-controller": true, - "@metamask/rate-limit-controller>@metamask/rpc-errors": true, - "@metamask/rate-limit-controller>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/base-controller": { + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "immer": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors": { + "@metamask/keyring-api>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": true, - "@metamask/rpc-errors>fast-safe-stringify": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/keyring-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/rate-limit-controller>@metamask/utils": { + "@metamask/keyring-api>@metamask/keyring-utils>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/rpc-errors": { + "@metamask/name-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/permission-controller>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/rpc-methods>nanoid": { + "@metamask/permission-log-controller>@metamask/utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/safe-event-emitter": { + "@metamask/post-message-stream>@metamask/utils": { "globals": { - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "webpack>events": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39": { + "@metamask/rate-limit-controller>@metamask/utils": { "globals": { + "TextDecoder": true, "TextEncoder": true }, "packages": { - "@metamask/scure-bip39>@noble/hashes": true, - "@metamask/utils>@scure/base": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/scure-bip39>@noble/hashes": { + "@metamask/rpc-errors>@metamask/utils": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@metamask/selected-network-controller": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/base-controller": true, - "@metamask/network-controller>@metamask/swappable-obj-proxy": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller": { + "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { - "fetch": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/keyring-controller": true, - "@metamask/logging-controller": true, - "@metamask/message-manager>jsonschema": true, - "@metamask/signature-controller>@metamask/eth-sig-util": true, - "@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "uuid": true, - "webpack>events": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util": { + "@metamask/snaps-controllers>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/abi-utils": true, - "@metamask/eth-sig-util>tweetnacl": true, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, "@metamask/utils>@scure/base": true, - "browserify>buffer": true + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": { + "@metamask/snaps-execution-environments>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/smart-transactions-controller": { + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { "globals": { - "URLSearchParams": true, - "clearInterval": true, - "console.error": true, - "console.log": true, - "fetch": true, - "setInterval": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethersproject/bytes": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/polling-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, - "@metamask/smart-transactions-controller>bignumber.js": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "fast-json-patch": true, - "lodash": true - } - }, - "@metamask/smart-transactions-controller>@ethereumjs/tx": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, - "@metamask/smart-transactions-controller>@ethereumjs/util": true - } - }, - "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { - "packages": { - "@metamask/smart-transactions-controller>@ethereumjs/util": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@ethereumjs/util": { - "globals": { - "console.warn": true, - "fetch": true - }, - "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, - "@metamask/smart-transactions-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "@metamask/snaps-rpc-methods>@metamask/utils": { "globals": { - "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { - "packages": { - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "@metamask/snaps-sdk>@metamask/utils": { "globals": { + "TextDecoder": true, "TextEncoder": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { - "globals": { - "console.warn": true }, "packages": { - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker": { - "packages": { - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "@metamask/snaps-utils>@metamask/utils": { "globals": { - "clearTimeout": true, - "setTimeout": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@swc/helpers>tslib": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors": { - "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": true + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/transaction-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/utils": { + "@metamask/user-operation-controller>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "browserify>buffer": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/smart-transactions-controller>bignumber.js": { + "@ngraveio/bc-ur": { + "packages": { + "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, + "browserify>assert": true, + "@ngraveio/bc-ur>bignumber.js": true, + "browserify>buffer": true, + "@ngraveio/bc-ur>cbor-sync": true, + "@ngraveio/bc-ur>crc": true, + "@ngraveio/bc-ur>jsbi": true, + "addons-linter>sha.js": true + } + }, + "@metamask/profile-sync-controller>@noble/ciphers": { "globals": { - "crypto": true, - "define": true + "TextDecoder": true, + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "DecompressionStream": true, - "URL": true, - "clearTimeout": true, - "document.getElementById": true, - "fetch.bind": true, - "setTimeout": true + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/json-rpc-engine": true, - "@metamask/json-rpc-middleware-stream": true, - "@metamask/object-multiplex": true, - "@metamask/post-message-stream": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, - "@metamask/snaps-controllers>@xstate/fsm": true, - "@metamask/snaps-controllers>concat-stream": true, - "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/snaps-controllers>readable-web-to-node-stream": true, - "@metamask/snaps-controllers>tar-stream": true, - "@metamask/snaps-rpc-methods": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, - "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/utils": true, - "browserify>browserify-zlib": true, - "eslint>fast-deep-equal": true, - "immer": true, - "readable-stream": true, - "semver": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true } }, - "@metamask/snaps-controllers-flask>nanoid": { + "@noble/hashes": { "globals": { - "crypto.getRandomValues": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "@metamask/scure-bip39>@noble/hashes": { "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>concat-stream": { - "packages": { - "browserify>buffer": true, - "browserify>concat-stream>typedarray": true, - "pumpify>inherits": true, - "readable-stream": true, - "terser>source-map-support>buffer-from": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>nanoid": { + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": { "globals": { - "crypto.getRandomValues": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>readable-web-to-node-stream": { - "packages": { - "readable-stream": true + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>tar-stream": { - "packages": { - "@metamask/snaps-controllers>tar-stream>b4a": true, - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx": true, - "browserify>browser-resolve": true + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>tar-stream>b4a": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "crypto": true } }, - "@metamask/snaps-controllers>tar-stream>streamx": { - "packages": { - "@metamask/snaps-controllers>tar-stream>fast-fifo": true, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, - "webpack>events": true + "@popperjs/core": { + "globals": { + "Element": true, + "HTMLElement": true, + "ShadowRoot": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator.userAgent": true } }, - "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { "globals": { - "queueMicrotask": true + "console.log": true } }, - "@metamask/snaps-execution-environments": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { "globals": { - "document.getElementById": true + "XMLHttpRequest": true }, "packages": { - "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true } }, - "@metamask/snaps-rpc-methods": { + "@reduxjs/toolkit": { + "globals": { + "AbortController": true, + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, + "__REDUX_DEVTOOLS_EXTENSION__": true, + "console": true, + "queueMicrotask": true, + "requestAnimationFrame": true, + "setTimeout": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "immer": true, + "process": true, + "redux": true, + "redux-thunk": true, + "@reduxjs/toolkit>reselect": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { + "react-router-dom-v5-compat>@remix-run/router": { "globals": { - "console.error": true + "AbortController": true, + "DOMException": true, + "FormData": true, + "Headers": true, + "Request": true, + "Response": true, + "URL": true, + "URLSearchParams": true, + "console": true, + "document.defaultView": true + } + }, + "@metamask/utils>@scure/base": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { + "packages": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true, + "@metamask/utils>@scure/base": true + } + }, + "@segment/loosely-validate-event": { + "packages": { + "browserify>assert": true, + "browserify>buffer": true, + "@segment/loosely-validate-event>component-type": true, + "@segment/loosely-validate-event>join-component": true + } + }, + "@sentry/browser>@sentry-internal/browser-utils": { + "globals": { + "PerformanceEventTiming.prototype": true, + "PerformanceObserver": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "clearTimeout": true, + "performance": true, + "removeEventListener": true, + "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { + "@sentry/browser>@sentry-internal/feedback": { "globals": { - "crypto.getRandomValues": true + "FormData": true, + "HTMLFormElement": true, + "__SENTRY_DEBUG__": true, + "cancelAnimationFrame": true, + "clearTimeout": true, + "document.createElement": true, + "document.createElementNS": true, + "document.createTextNode": true, + "isSecureContext": true, + "requestAnimationFrame": true, + "setTimeout": true + }, + "packages": { + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk": { + "@sentry/browser>@sentry-internal/replay-canvas": { "globals": { - "fetch": true + "Blob": true, + "HTMLCanvasElement": true, + "HTMLImageElement": true, + "ImageData": true, + "URL.createObjectURL": true, + "WeakRef": true, + "Worker": true, + "cancelAnimationFrame": true, + "console.error": true, + "createImageBitmap": true, + "document": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk>@metamask/key-tree": { + "@sentry/browser>@sentry-internal/replay": { + "globals": { + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSRule": true, + "CSSSupportsRule": true, + "Document": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLElement": true, + "HTMLFormElement": true, + "Headers": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.prototype.contains": true, + "PointerEvent": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "__RRWEB_EXCLUDE_IFRAME__": true, + "__RRWEB_EXCLUDE_SHADOW_DOM__": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, + "__rrMutationObserver": true, + "addEventListener": true, + "clearTimeout": true, + "console.debug": true, + "console.error": true, + "console.warn": true, + "customElements.get": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "location.origin": true, + "parent": true, + "setTimeout": true + }, "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/scure-bip39": true, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true } }, - "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "@sentry/browser": { + "globals": { + "PerformanceObserver.supportedEntryTypes": true, + "Request": true, + "URL": true, + "XMLHttpRequest.prototype": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "addEventListener": true, + "console.error": true, + "indexedDB.open": true, + "performance.timeOrigin": true, + "setTimeout": true + }, + "packages": { + "@sentry/browser>@sentry-internal/browser-utils": true, + "@sentry/browser>@sentry-internal/feedback": true, + "@sentry/browser>@sentry-internal/replay-canvas": true, + "@sentry/browser>@sentry-internal/replay": true, + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry/core": { + "globals": { + "Headers": true, + "Request": true, + "URL": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@sentry/utils": true + } + }, + "@sentry/utils": { "globals": { + "CustomEvent": true, + "DOMError": true, + "DOMException": true, + "EdgeRuntime": true, + "Element": true, + "ErrorEvent": true, + "Event": true, + "HTMLElement": true, + "Headers": true, + "Request": true, + "Response": true, "TextDecoder": true, - "TextEncoder": true + "TextEncoder": true, + "URL": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, + "clearTimeout": true, + "console.error": true, + "document": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "process": true + } + }, + "@solana/addresses": { + "globals": { + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true + }, + "packages": { + "@solana/addresses>@solana/assertions": true, + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/codecs-strings": true, + "@solana/addresses>@solana/errors": true + } + }, + "@solana/addresses>@solana/assertions": { + "globals": { + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@solana/addresses>@solana/errors": true + } + }, + "@solana/addresses>@solana/codecs-core": { + "packages": { + "@solana/addresses>@solana/errors": true + } + }, + "@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, + "packages": { + "@solana/addresses>@solana/codecs-core": true, + "@solana/addresses>@solana/errors": true + } + }, + "@solana/addresses>@solana/errors": { + "globals": { + "btoa": true + } + }, + "@metamask/controller-utils>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true + } + }, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@noble/hashes": true, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true + } + }, + "@trezor/connect-web>@trezor/connect-common": { + "globals": { + "console.warn": true, + "localStorage.getItem": true, + "localStorage.setItem": true, + "navigator": true, + "setTimeout": true, + "window": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "tslib": true + } + }, + "@trezor/connect-web": { + "globals": { + "URLSearchParams": true, + "__TREZOR_CONNECT_SRC": true, + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "location": true, + "navigator": true, + "open": true, + "origin": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>@trezor/connect-common": true, + "@trezor/connect-web>@trezor/connect": true, + "@trezor/connect-web>@trezor/utils": true, + "webpack>events": true, + "tslib": true + } + }, + "@trezor/connect-web>@trezor/connect": { + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "@trezor/connect-web>@trezor/connect>@trezor/transport": true, + "@trezor/connect-web>@trezor/utils": true, + "tslib": true + } + }, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { + "globals": { + "innerHeight": true, + "innerWidth": true, + "location.hostname": true, + "location.origin": true, + "navigator.languages": true, + "navigator.platform": true, + "navigator.userAgent": true, + "screen.height": true, + "screen.width": true + }, + "packages": { + "process": true, + "tslib": true, + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { + "packages": { + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, + "browserify>buffer": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, + "tslib": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { + "globals": { + "console.warn": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, + "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "ts-mixer": true } }, - "@metamask/snaps-utils": { + "@trezor/connect-web>@trezor/utils": { "globals": { - "File": true, - "FileReader": true, - "TextDecoder": true, - "TextEncoder": true, - "URL": true, + "AbortController": true, + "Intl.NumberFormat": true, + "clearInterval": true, + "clearTimeout": true, "console.error": true, + "console.info": true, "console.log": true, "console.warn": true, - "crypto": true, - "document.body.appendChild": true, - "document.createElement": true, - "fetch": true + "setInterval": true, + "setTimeout": true }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/snaps-sdk": true, - "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, - "@metamask/snaps-utils>cron-parser": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>fast-xml-parser": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, - "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@noble/hashes": true, - "chalk": true, - "semver": true + "@trezor/connect-web>@trezor/utils>bignumber.js": true, + "browserify>buffer": true, + "webpack>events": true, + "tslib": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { + "@welldone-software/why-did-you-render": { "globals": { - "console.error": true + "Element": true, + "console.group": true, + "console.groupCollapsed": true, + "console.groupEnd": true, + "console.log": true, + "console.warn": true, + "define": true, + "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "lodash": true, + "react": true } }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { + "@zxing/browser": { "globals": { - "crypto.getRandomValues": true + "HTMLElement": true, + "HTMLImageElement": true, + "HTMLVideoElement": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true + }, + "packages": { + "@zxing/library": true } }, - "@metamask/snaps-utils>@metamask/snaps-registry": { + "@zxing/library": { + "globals": { + "HTMLImageElement": true, + "HTMLVideoElement": true, + "TextDecoder": true, + "TextEncoder": true, + "URL.createObjectURL": true, + "btoa": true, + "console.log": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true + }, "packages": { - "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, - "@metamask/utils>@metamask/superstruct": true, - "@noble/hashes": true + "@zxing/library>ts-custom-error": true } }, - "@metamask/snaps-utils>cron-parser": { - "packages": { - "browserify>browser-resolve": true, - "luxon": true + "@lavamoat/lavapack>readable-stream>abort-controller": { + "globals": { + "AbortController": true } }, - "@metamask/snaps-utils>fast-xml-parser": { + "currency-formatter>accounting": { "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-utils>fast-xml-parser>strnum": true + "define": true } }, - "@metamask/snaps-utils>marked": { + "ethers>@ethersproject/json-wallets>aes-js": { "globals": { - "console.error": true, - "console.warn": true, "define": true } }, - "@metamask/snaps-utils>rfdc": { - "packages": { - "browserify>buffer": true + "eth-lattice-keyring>gridplus-sdk>aes-js": { + "globals": { + "define": true } }, - "@metamask/snaps-utils>validate-npm-package-name": { + "chalk>ansi-styles": { "packages": { - "@metamask/snaps-utils>validate-npm-package-name>builtins": true + "chalk>ansi-styles>color-convert": true } }, - "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { "packages": { - "process": true, - "semver": true + "browserify>buffer": true } }, - "@metamask/test-bundler>@ethersproject/networks": { + "string.prototype.matchall>es-abstract>array-buffer-byte-length": { "packages": { - "ethers>@ethersproject/logger": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true } }, - "@metamask/transaction-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/common": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "@ethersproject/abi": true, - "@ethersproject/contracts": true, - "@ethersproject/providers": true, - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/metamask-eth-abis": true, - "@metamask/name-controller>async-mutex": true, - "@metamask/network-controller": true, - "@metamask/rpc-errors": true, - "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/utils": true, "bn.js": true, "browserify>buffer": true, - "eth-method-registry": true, - "fast-json-patch": true, - "lodash": true, - "uuid": true, - "webpack>events": true - } - }, - "@metamask/transaction-controller>@metamask/nonce-tracker": { - "packages": { - "@ethersproject/providers": true, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": true, - "browserify>assert": true + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "browserify>vm-browserify": true } }, - "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { + "browserify>assert": { "globals": { - "clearTimeout": true, - "setTimeout": true + "Buffer": true }, "packages": { - "@swc/helpers>tslib": true + "react>object-assign": true, + "browserify>assert>util": true } }, - "@metamask/user-operation-controller": { + "@metamask/name-controller>async-mutex": { "globals": { - "fetch": true + "clearTimeout": true, + "setTimeout": true }, "packages": { - "@metamask/controller-utils": true, - "@metamask/eth-query": true, - "@metamask/gas-fee-controller": true, - "@metamask/transaction-controller": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "@metamask/user-operation-controller>@metamask/polling-controller": true, - "@metamask/user-operation-controller>@metamask/rpc-errors": true, - "@metamask/user-operation-controller>@metamask/utils": true, - "bn.js": true, - "lodash": true, - "superstruct": true, - "uuid": true, - "webpack>events": true + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/base-controller": { + "@metamask/transaction-controller>@metamask/nonce-tracker>async-mutex": { "globals": { + "clearTimeout": true, "setTimeout": true }, "packages": { - "immer": true + "tslib": true } }, - "@metamask/user-operation-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, + "string.prototype.matchall>es-abstract>available-typed-arrays": { "packages": { - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/user-operation-controller>@metamask/base-controller": true, - "uuid": true + "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true } }, - "@metamask/user-operation-controller>@metamask/rpc-errors": { + "await-semaphore": { "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": true + "process": true, + "browserify>timers-browserify": true } }, - "@metamask/user-operation-controller>@metamask/rpc-errors>@metamask/utils": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>axios": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Blob": true, + "FormData": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "axios>form-data": true, + "process": true } }, - "@metamask/user-operation-controller>@metamask/utils": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>axios": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Blob": true, + "FormData": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "axios>form-data": true, + "process": true } }, - "@metamask/utils": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>axios": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "Blob": true, + "FormData": true, + "URLSearchParams": true, + "XMLHttpRequest": true, + "btoa": true, + "console.warn": true, + "document": true, + "location.href": true, + "navigator": true, + "setTimeout": true }, "packages": { - "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, "browserify>buffer": true, - "nock>debug": true, - "semver": true + "axios>form-data": true, + "process": true } }, - "@metamask/utils>@scure/base": { + "@metamask/snaps-controllers>tar-stream>b4a": { "globals": { "TextDecoder": true, "TextEncoder": true } }, - "@ngraveio/bc-ur": { + "@ensdomains/content-hash>multihashes>multibase>base-x": { "packages": { - "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, - "@ngraveio/bc-ur>bignumber.js": true, - "@ngraveio/bc-ur>cbor-sync": true, - "@ngraveio/bc-ur>crc": true, - "@ngraveio/bc-ur>jsbi": true, - "addons-linter>sha.js": true, - "browserify>assert": true, - "browserify>buffer": true + "koa>content-disposition>safe-buffer": true } }, - "@ngraveio/bc-ur>assert>object-is": { + "base32-encode": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true + "base32-encode>to-data-view": true } }, - "@ngraveio/bc-ur>bignumber.js": { + "bignumber.js": { "globals": { "crypto": true, "define": true } }, - "@ngraveio/bc-ur>cbor-sync": { + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>bignumber.js": { "globals": { + "crypto": true, "define": true - }, - "packages": { - "browserify>buffer": true } }, - "@ngraveio/bc-ur>crc": { - "packages": { - "browserify>buffer": true + "@metamask/notification-services-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true } }, - "@ngraveio/bc-ur>jsbi": { + "@metamask/smart-transactions-controller>bignumber.js": { "globals": { + "crypto": true, "define": true } }, - "@noble/hashes": { + "@ngraveio/bc-ur>bignumber.js": { "globals": { - "TextEncoder": true, - "crypto": true + "crypto": true, + "define": true } }, - "@popperjs/core": { + "@trezor/connect-web>@trezor/utils>bignumber.js": { "globals": { - "Element": true, - "HTMLElement": true, - "ShadowRoot": true, - "console.error": true, - "console.warn": true, - "document": true, - "navigator.userAgent": true + "crypto": true, + "define": true } }, - "@reduxjs/toolkit": { + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { "globals": { - "AbortController": true, - "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, - "__REDUX_DEVTOOLS_EXTENSION__": true, - "console": true, - "queueMicrotask": true, - "requestAnimationFrame": true, - "setTimeout": true - }, - "packages": { - "@reduxjs/toolkit>reselect": true, - "immer": true, - "process": true, - "redux": true, - "redux-thunk": true + "crypto": true, + "define": true } }, - "@segment/loosely-validate-event": { + "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bitwise": { "packages": { - "@segment/loosely-validate-event>component-type": true, - "@segment/loosely-validate-event>join-component": true, - "browserify>assert": true, "browserify>buffer": true } }, - "@sentry/browser": { + "blo": { "globals": { - "PerformanceObserver.supportedEntryTypes": true, - "Request": true, - "URL": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_RELEASE__": true, - "addEventListener": true, - "console.error": true, - "indexedDB.open": true, - "performance.timeOrigin": true, - "setTimeout": true - }, - "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry-internal/feedback": true, - "@sentry/browser>@sentry-internal/replay": true, - "@sentry/browser>@sentry-internal/replay-canvas": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "btoa": true } }, - "@sentry/browser>@sentry-internal/browser-utils": { + "bn.js": { "globals": { - "PerformanceEventTiming.prototype": true, - "PerformanceObserver": true, - "XMLHttpRequest.prototype": true, - "__SENTRY_DEBUG__": true, - "addEventListener": true, - "clearTimeout": true, - "performance": true, - "removeEventListener": true, - "setTimeout": true + "Buffer": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>browser-resolve": true } }, - "@sentry/browser>@sentry-internal/feedback": { + "eth-lattice-keyring>gridplus-sdk>borc": { "globals": { - "FormData": true, - "HTMLFormElement": true, - "__SENTRY_DEBUG__": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "document.createElement": true, - "document.createElementNS": true, - "document.createTextNode": true, - "isSecureContext": true, - "requestAnimationFrame": true, - "setTimeout": true + "console": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, + "browserify>buffer": true, + "buffer>ieee754": true, + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true } }, - "@sentry/browser>@sentry-internal/replay": { + "bowser": { "globals": { - "Blob": true, - "CSSConditionRule": true, - "CSSGroupingRule": true, - "CSSMediaRule": true, - "CSSRule": true, - "CSSSupportsRule": true, - "Document": true, - "DragEvent": true, - "Element": true, - "FormData": true, - "HTMLElement": true, - "HTMLFormElement": true, - "Headers": true, - "MouseEvent": true, - "MutationObserver": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.prototype.contains": true, - "PointerEvent": true, - "TextEncoder": true, - "URL": true, - "URLSearchParams": true, - "Worker": true, - "__RRWEB_EXCLUDE_IFRAME__": true, - "__RRWEB_EXCLUDE_SHADOW_DOM__": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_EXCLUDE_REPLAY_WORKER__": true, - "__rrMutationObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.debug": true, - "console.error": true, - "console.warn": true, - "customElements.get": true, - "document": true, - "innerHeight": true, - "innerWidth": true, - "location.href": true, - "location.origin": true, - "parent": true, - "setTimeout": true - }, - "packages": { - "@sentry/browser>@sentry-internal/browser-utils": true, - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "define": true } }, - "@sentry/browser>@sentry-internal/replay-canvas": { + "@metamask/ppom-validator>elliptic>brorand": { "globals": { - "Blob": true, - "HTMLCanvasElement": true, - "HTMLImageElement": true, - "ImageData": true, - "URL.createObjectURL": true, - "WeakRef": true, - "Worker": true, - "cancelAnimationFrame": true, - "console.error": true, - "createImageBitmap": true, - "document": true + "crypto": true, + "msCrypto": true }, "packages": { - "@sentry/browser>@sentry/core": true, - "@sentry/utils": true + "browserify>browser-resolve": true + } + }, + "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true + } + }, + "crypto-browserify>browserify-cipher": { + "packages": { + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "crypto-browserify>browserify-cipher>browserify-des": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true + } + }, + "crypto-browserify>browserify-cipher>browserify-des": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>create-hash>cipher-base": true, + "crypto-browserify>browserify-cipher>browserify-des>des.js": true, + "pumpify>inherits": true + } + }, + "crypto-browserify>public-encrypt>browserify-rsa": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>randombytes": true + } + }, + "crypto-browserify>browserify-sign": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "@metamask/ppom-validator>elliptic": true, + "pumpify>inherits": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "stream-browserify": true + } + }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>buffer": true, + "browserify>browserify-zlib>pako": true, + "process": true, + "stream-browserify": true, + "browserify>util": true } }, - "@sentry/browser>@sentry/core": { - "globals": { - "Headers": true, - "Request": true, - "URL": true, - "__SENTRY_DEBUG__": true, - "__SENTRY_TRACING__": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { "packages": { - "@sentry/utils": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true } }, - "@sentry/utils": { - "globals": { - "CustomEvent": true, - "DOMError": true, - "DOMException": true, - "EdgeRuntime": true, - "Element": true, - "ErrorEvent": true, - "Event": true, - "HTMLElement": true, - "Headers": true, - "Request": true, - "Response": true, - "TextDecoder": true, - "TextEncoder": true, - "URL": true, - "__SENTRY_BROWSER_BUNDLE__": true, - "__SENTRY_DEBUG__": true, - "clearTimeout": true, - "console.error": true, - "document": true, - "setInterval": true, - "setTimeout": true - }, + "ethereumjs-util>ethereum-cryptography>bs58check": { "packages": { - "process": true + "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, + "ethereumjs-util>create-hash": true, + "koa>content-disposition>safe-buffer": true } }, - "@solana/addresses": { + "buffer": { "globals": { - "Intl.Collator": true, - "TextEncoder": true, - "crypto.subtle.digest": true, - "crypto.subtle.exportKey": true + "console": true }, "packages": { - "@solana/addresses>@solana/assertions": true, - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/codecs-strings": true, - "@solana/addresses>@solana/errors": true + "base64-js": true, + "buffer>ieee754": true } }, - "@solana/addresses>@solana/assertions": { - "globals": { - "crypto": true, - "isSecureContext": true - }, + "terser>source-map-support>buffer-from": { "packages": { - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/codecs-core": { + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { "packages": { - "@solana/addresses>@solana/errors": true + "browserify>buffer": true } }, - "@solana/addresses>@solana/codecs-strings": { + "browserify>buffer": { "globals": { - "TextDecoder": true, - "TextEncoder": true, - "atob": true, - "btoa": true + "console": true }, "packages": { - "@solana/addresses>@solana/codecs-core": true, - "@solana/addresses>@solana/errors": true - } - }, - "@solana/addresses>@solana/errors": { - "globals": { - "btoa": true + "base64-js": true, + "buffer>ieee754": true } }, - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": { "packages": { - "react-markdown>unist-util-visit": true + "process": true, + "semver": true } }, - "@storybook/addon-knobs>qs": { + "string.prototype.matchall>call-bind": { "packages": { - "string.prototype.matchall>side-channel": true + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>call-bind>set-function-length": true } }, - "@swc/helpers>tslib": { + "@ngraveio/bc-ur>cbor-sync": { "globals": { - "SuppressedError": true, "define": true + }, + "packages": { + "browserify>buffer": true } }, - "@trezor/connect-web": { + "chalk": { + "packages": { + "chalk>ansi-styles": true, + "chalk>supports-color": true + } + }, + "chart.js": { "globals": { - "URLSearchParams": true, - "__TREZOR_CONNECT_SRC": true, + "Intl.NumberFormat": true, + "MutationObserver": true, + "OffscreenCanvas": true, + "Path2D": true, + "ResizeObserver": true, "addEventListener": true, - "btoa": true, - "chrome": true, - "clearInterval": true, "clearTimeout": true, + "console.error": true, "console.warn": true, - "document.body": true, - "document.createElement": true, - "document.createTextNode": true, - "document.getElementById": true, - "document.querySelectorAll": true, - "location": true, - "navigator": true, - "open": true, - "origin": true, + "devicePixelRatio": true, + "document": true, "removeEventListener": true, - "setInterval": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect": true, - "@trezor/connect-web>@trezor/connect-common": true, - "@trezor/connect-web>@trezor/utils": true, - "webpack>events": true - } - }, - "@trezor/connect-web>@trezor/connect": { - "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, - "@trezor/connect-web>@trezor/connect>@trezor/transport": true, - "@trezor/connect-web>@trezor/utils": true + "chart.js>@kurkle/color": true } }, - "@trezor/connect-web>@trezor/connect-common": { - "globals": { - "console.warn": true, - "localStorage.getItem": true, - "localStorage.setItem": true, - "navigator": true, - "setTimeout": true, - "window": true - }, + "@ensdomains/content-hash>cids": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": true, - "@trezor/connect-web>@trezor/utils": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>cids>multihashes": true, + "@ensdomains/content-hash>cids>uint8arrays": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils": { - "globals": { - "innerHeight": true, - "innerWidth": true, - "location.hostname": true, - "location.origin": true, - "navigator.languages": true, - "navigator.platform": true, - "navigator.userAgent": true, - "screen.height": true, - "screen.width": true - }, + "ethereumjs-util>create-hash>cipher-base": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": true, - "process": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true, + "stream-browserify": true, + "browserify>string_decoder": true } }, - "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "classnames": { "globals": { + "classNames": "write", "define": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf": { + "@metamask/jazzicon>color>clone": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": true, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": true, "browserify>buffer": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { + "cockatiel": { "globals": { - "process": true, + "AbortController": true, + "AbortSignal": true, + "WeakRef": true, + "clearTimeout": true, + "performance": true, "setTimeout": true }, "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true - } - }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": { - "globals": { - "console.log": true + "process": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": { - "globals": { - "XMLHttpRequest": true - }, + "chalk>ansi-styles>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, - "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils": { - "globals": { - "console.warn": true - }, + "@metamask/jazzicon>color>color-convert": { "packages": { - "@trezor/connect-web>@trezor/connect>@trezor/schema-utils>@sinclair/typebox": true, - "browserify>buffer": true, - "ts-mixer": true + "@metamask/jazzicon>color>color-convert>color-name": true } }, - "@trezor/connect-web>@trezor/utils": { - "globals": { - "AbortController": true, - "Intl.NumberFormat": true, - "clearInterval": true, - "clearTimeout": true, - "console.error": true, - "console.info": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "@metamask/jazzicon>color>color-string": { "packages": { - "@swc/helpers>tslib": true, - "@trezor/connect-web>@trezor/utils>bignumber.js": true, - "browserify>buffer": true, - "webpack>events": true + "jest-canvas-mock>moo-color>color-name": true } }, - "@trezor/connect-web>@trezor/utils>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/jazzicon>color": { + "packages": { + "@metamask/jazzicon>color>clone": true, + "@metamask/jazzicon>color>color-convert": true, + "@metamask/jazzicon>color>color-string": true } }, - "@welldone-software/why-did-you-render": { - "globals": { - "Element": true, - "console.group": true, - "console.groupCollapsed": true, - "console.groupEnd": true, - "console.log": true, - "console.warn": true, - "define": true, - "setTimeout": true - }, + "@metamask/snaps-controllers>concat-stream": { "packages": { - "lodash": true, - "react": true + "terser>source-map-support>buffer-from": true, + "browserify>buffer": true, + "pumpify>inherits": true, + "readable-stream": true, + "browserify>concat-stream>typedarray": true } }, - "@zxing/browser": { + "copy-to-clipboard": { "globals": { - "HTMLElement": true, - "HTMLImageElement": true, - "HTMLVideoElement": true, - "clearTimeout": true, + "clipboardData": true, "console.error": true, "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true, + "document.createRange": true, + "document.execCommand": true, + "document.getSelection": true, + "navigator.userAgent": true, + "prompt": true }, "packages": { - "@zxing/library": true + "copy-to-clipboard>toggle-selection": true } }, - "@zxing/library": { + "@ethereumjs/tx>@ethereumjs/common>crc-32": { "globals": { - "HTMLImageElement": true, - "HTMLVideoElement": true, - "TextDecoder": true, - "TextEncoder": true, - "URL.createObjectURL": true, - "btoa": true, - "console.log": true, - "console.warn": true, - "document": true, - "navigator": true, - "setTimeout": true - }, - "packages": { - "@zxing/library>ts-custom-error": true + "DO_NOT_EXPORT_CRC": true, + "define": true } }, - "addons-linter>sha.js": { + "@ngraveio/bc-ur>crc": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "browserify>buffer": true } }, - "await-semaphore": { + "crypto-browserify>create-ecdh": { "packages": { - "browserify>timers-browserify": true, - "process": true + "bn.js": true, + "browserify>buffer": true, + "@metamask/ppom-validator>elliptic": true } }, - "axios>form-data": { - "globals": { - "FormData": true + "ethereumjs-util>create-hash": { + "packages": { + "ethereumjs-util>create-hash>cipher-base": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>md5.js": true, + "ethereumjs-util>create-hash>ripemd160": true, + "addons-linter>sha.js": true } }, - "base32-encode": { + "crypto-browserify>create-hmac": { "packages": { - "base32-encode>to-data-view": true + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>create-hash": true, + "pumpify>inherits": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "bignumber.js": { - "globals": { - "crypto": true, - "define": true + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true } }, - "blo": { - "globals": { - "btoa": true + "crypto-browserify": { + "packages": { + "crypto-browserify>browserify-cipher": true, + "crypto-browserify>browserify-sign": true, + "crypto-browserify>create-ecdh": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>create-hmac": true, + "crypto-browserify>diffie-hellman": true, + "crypto-browserify>pbkdf2": true, + "crypto-browserify>public-encrypt": true, + "crypto-browserify>randombytes": true, + "crypto-browserify>randomfill": true } }, - "bn.js": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "Buffer": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { "browserify>browser-resolve": true } }, - "bowser": { - "globals": { - "define": true - } - }, - "browserify>assert": { + "react-beautiful-dnd>css-box-model": { "globals": { - "Buffer": true + "getComputedStyle": true, + "pageXOffset": true, + "pageYOffset": true }, "packages": { - "browserify>assert>util": true, - "react>object-assign": true + "react-router-dom>tiny-invariant": true } }, - "browserify>assert>util": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { "globals": { - "console.error": true, - "console.log": true, - "console.trace": true, - "process": true + "document.createElement": true, + "document.documentElement": true, + "getComputedStyle": true }, "packages": { - "browserify>assert>util>inherits": true, - "process": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true } }, - "browserify>browserify-zlib": { + "currency-formatter": { "packages": { - "browserify>assert": true, - "browserify>browserify-zlib>pako": true, - "browserify>buffer": true, - "browserify>util": true, - "process": true, - "stream-browserify": true + "currency-formatter>accounting": true, + "currency-formatter>locale-currency": true, + "react>object-assign": true } }, - "browserify>buffer": { - "globals": { - "console": true - }, + "debounce-stream": { "packages": { - "base64-js": true, - "buffer>ieee754": true + "debounce-stream>debounce": true, + "debounce-stream>duplexer": true, + "debounce-stream>through": true } }, - "browserify>punycode": { + "debounce-stream>debounce": { "globals": { - "define": true - } - }, - "browserify>string_decoder": { - "packages": { - "koa>content-disposition>safe-buffer": true + "clearTimeout": true, + "setTimeout": true } }, - "browserify>timers-browserify": { + "nock>debug": { "globals": { - "clearInterval": true, - "clearTimeout": true, - "setInterval": true, - "setTimeout": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { + "nock>debug>ms": true, "process": true } }, - "browserify>url": { + "@metamask/eth-token-tracker>deep-equal": { "packages": { - "@storybook/addon-knobs>qs": true, - "browserify>punycode": true + "string.prototype.matchall>es-abstract>array-buffer-byte-length": true, + "string.prototype.matchall>call-bind": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": true, + "string.prototype.matchall>get-intrinsic": true, + "browserify>util>is-arguments": true, + "string.prototype.matchall>es-abstract>is-array-buffer": true, + "@metamask/eth-token-tracker>deep-equal>is-date-object": true, + "string.prototype.matchall>es-abstract>is-regex": true, + "string.prototype.matchall>es-abstract>is-shared-array-buffer": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "@ngraveio/bc-ur>assert>object-is": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true, + "gulp>vinyl-fs>object.assign": true, + "string.prototype.matchall>regexp.prototype.flags": true, + "string.prototype.matchall>side-channel": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": true, + "@metamask/eth-token-tracker>deep-equal>which-collection": true, + "browserify>util>which-typed-array": true } }, - "browserify>util": { - "globals": { - "console.error": true, - "console.log": true, - "console.trace": true - }, + "string.prototype.matchall>define-properties>define-data-property": { "packages": { - "browserify>util>is-arguments": true, - "browserify>util>is-typed-array": true, - "browserify>util>which-typed-array": true, - "koa>is-generator-function": true, - "process": true, - "pumpify>inherits": true + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>gopd": true } }, - "browserify>util>is-arguments": { + "string.prototype.matchall>define-properties": { "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "browserify>util>is-typed-array": { + "crypto-browserify>browserify-cipher>browserify-des>des.js": { "packages": { - "browserify>util>which-typed-array": true + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, - "browserify>util>which-typed-array": { + "crypto-browserify>diffie-hellman": { "packages": { - "browserify>util>which-typed-array>for-each": true, - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>available-typed-arrays": true, - "string.prototype.matchall>es-abstract>gopd": true + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify>diffie-hellman>miller-rabin": true, + "crypto-browserify>randombytes": true } }, - "browserify>util>which-typed-array>for-each": { + "@material-ui/core>react-transition-group>dom-helpers": { "packages": { - "string.prototype.matchall>es-abstract>is-callable": true + "@babel/runtime": true } }, - "browserify>vm-browserify": { - "globals": { - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true + "debounce-stream>duplexer": { + "packages": { + "stream-browserify": true + } + }, + "ethers>@ethersproject/signing-key>elliptic": { + "packages": { + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "buffer": { - "globals": { - "console": true - }, + "@metamask/ppom-validator>elliptic": { "packages": { - "base64-js": true, - "buffer>ieee754": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chalk": { + "eth-lattice-keyring>gridplus-sdk>elliptic": { "packages": { - "chalk>ansi-styles": true, - "chalk>supports-color": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true, + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>hmac-drbg": true, + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "chalk>ansi-styles": { + "string.prototype.matchall>call-bind>es-define-property": { "packages": { - "chalk>ansi-styles>color-convert": true + "string.prototype.matchall>get-intrinsic": true } }, - "chalk>ansi-styles>color-convert": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator": { "packages": { - "jest-canvas-mock>moo-color>color-name": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>has-symbols": true, + "browserify>util>is-arguments": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "eslint-plugin-react>array-includes>is-string": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "process": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": true } }, - "chart.js": { + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { "globals": { - "Intl.NumberFormat": true, - "MutationObserver": true, - "OffscreenCanvas": true, - "Path2D": true, - "ResizeObserver": true, - "addEventListener": true, - "clearTimeout": true, - "console.error": true, - "console.warn": true, - "devicePixelRatio": true, - "document": true, - "removeEventListener": true, - "requestAnimationFrame": true, - "setTimeout": true + "intToBuffer": true }, "packages": { - "chart.js>@kurkle/color": true + "bn.js": true, + "buffer": true, + "eth-ens-namehash>js-sha3": true } }, - "chart.js>@kurkle/color": { + "eth-ens-namehash": { "globals": { - "define": true + "name": "write" + }, + "packages": { + "browserify>buffer": true, + "eth-ens-namehash>idna-uts46-hx": true, + "eth-ens-namehash>js-sha3": true } }, - "classnames": { + "eth-lattice-keyring": { "globals": { - "classNames": "write", - "define": true + "addEventListener": true, + "browser": true, + "clearInterval": true, + "fetch": true, + "open": true, + "setInterval": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "bn.js": true, + "browserify>buffer": true, + "crypto-browserify": true, + "webpack>events": true, + "eth-lattice-keyring>gridplus-sdk": true, + "eth-lattice-keyring>rlp": true } }, - "cockatiel": { - "globals": { - "AbortController": true, - "AbortSignal": true, - "WeakRef": true, - "clearTimeout": true, - "performance": true, - "setTimeout": true - }, + "eth-method-registry": { "packages": { - "process": true + "eth-method-registry>@metamask/ethjs-contract": true, + "eth-method-registry>@metamask/ethjs-query": true } }, - "copy-to-clipboard": { + "@ethereumjs/tx>ethereum-cryptography": { "globals": { - "clipboardData": true, - "console.error": true, - "console.warn": true, - "document.body.appendChild": true, - "document.body.removeChild": true, - "document.createElement": true, - "document.createRange": true, - "document.execCommand": true, - "document.getSelection": true, - "navigator.userAgent": true, - "prompt": true + "TextDecoder": true, + "crypto": true }, "packages": { - "copy-to-clipboard>toggle-selection": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true } }, - "copy-to-clipboard>toggle-selection": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { "globals": { - "document.activeElement": true, - "document.getSelection": true - } - }, - "crypto-browserify": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "crypto-browserify>browserify-cipher": true, - "crypto-browserify>browserify-sign": true, - "crypto-browserify>create-ecdh": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>diffie-hellman": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt": true, - "crypto-browserify>randombytes": true, - "crypto-browserify>randomfill": true, - "ethereumjs-util>create-hash": true + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, "packages": { - "crypto-browserify>browserify-cipher>browserify-des": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, - "crypto-browserify>browserify-cipher>browserify-des": { + "ethereumjs-util>ethereum-cryptography": { "packages": { "browserify>buffer": true, - "crypto-browserify>browserify-cipher>browserify-des>des.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "pumpify>inherits": true + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "ganache>secp256k1": true } }, - "crypto-browserify>browserify-cipher>browserify-des>des.js": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": { "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "browserify>buffer": true, + "crypto-browserify>create-hmac": true, + "ethers>@ethersproject/sha2>hash.js": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true } }, - "crypto-browserify>browserify-cipher>evp_bytestokey": { + "ethereumjs-util": { "packages": { - "ethereumjs-util>create-hash>md5.js": true, - "koa>content-disposition>safe-buffer": true + "browserify>assert": true, + "bn.js": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>rlp": true } }, - "crypto-browserify>browserify-sign": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { - "@metamask/ppom-validator>elliptic": true, + "browserify>assert": true, "bn.js": true, "browserify>buffer": true, - "crypto-browserify>create-hmac": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, "ethereumjs-util>create-hash": true, - "pumpify>inherits": true, - "stream-browserify": true + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "browserify>insert-module-globals>is-buffer": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true } }, - "crypto-browserify>create-ecdh": { + "@metamask/keyring-controller>ethereumjs-wallet": { "packages": { - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>buffer": true + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "browserify>buffer": true, + "crypto-browserify": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": true, + "crypto-browserify>randombytes": true, + "ethers>@ethersproject/json-wallets>scrypt-js": true, + "@metamask/keyring-controller>ethereumjs-wallet>utf8": true, + "uuid": true } }, - "crypto-browserify>create-hmac": { + "ethers": { "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "ethers>@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "ethers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true + } + }, + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/evm-tools>ethers": { + "packages": { + "@ethersproject/abi": true, + "ethers>@ethersproject/abstract-signer": true, + "ethers>@ethersproject/address": true, + "ethers>@ethersproject/base64": true, + "ethers>@ethersproject/basex": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "ethers>@ethersproject/constants": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hdnode": true, + "ethers>@ethersproject/json-wallets": true, + "ethers>@ethersproject/keccak256": true, + "ethers>@ethersproject/logger": true, + "ethers>@ethersproject/properties": true, + "@ethersproject/providers": true, + "ethers>@ethersproject/random": true, + "ethers>@ethersproject/rlp": true, + "ethers>@ethersproject/sha2": true, + "ethers>@ethersproject/signing-key": true, + "ethers>@ethersproject/solidity": true, + "ethers>@ethersproject/strings": true, + "ethers>@ethersproject/transactions": true, + "ethers>@ethersproject/units": true, + "@ethersproject/wallet": true, + "@ethersproject/providers>@ethersproject/web": true, + "ethers>@ethersproject/wordlists": true } }, - "crypto-browserify>diffie-hellman": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, "browserify>buffer": true, - "crypto-browserify>diffie-hellman>miller-rabin": true, - "crypto-browserify>randombytes": true + "eth-ens-namehash>js-sha3": true, + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": true } }, - "crypto-browserify>diffie-hellman>miller-rabin": { - "packages": { - "@metamask/ppom-validator>elliptic>brorand": true, - "bn.js": true + "webpack>events": { + "globals": { + "console": true } }, - "crypto-browserify>pbkdf2": { - "globals": { - "crypto": true, - "process": true, - "queueMicrotask": true, - "setImmediate": true, - "setTimeout": true - }, + "crypto-browserify>browserify-cipher>evp_bytestokey": { "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>create-hash>ripemd160": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "ethereumjs-util>create-hash>md5.js": true, + "koa>content-disposition>safe-buffer": true } }, - "crypto-browserify>public-encrypt": { + "extension-port-stream": { "packages": { - "bn.js": true, "browserify>buffer": true, - "crypto-browserify>public-encrypt>browserify-rsa": true, - "crypto-browserify>public-encrypt>parse-asn1": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>create-hash": true + "extension-port-stream>readable-stream": true } }, - "crypto-browserify>public-encrypt>browserify-rsa": { - "packages": { - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify>randombytes": true + "fast-json-patch": { + "globals": { + "addEventListener": true, + "clearTimeout": true, + "removeEventListener": true, + "setTimeout": true } }, - "crypto-browserify>public-encrypt>parse-asn1": { + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "crypto-browserify>pbkdf2": true, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes": true + "@metamask/snaps-utils>fast-xml-parser>strnum": true } }, - "crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "@metamask/notification-services-controller>firebase": { "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "bn.js": true, - "browserify>buffer": true, - "browserify>vm-browserify": true, - "pumpify>inherits": true + "@metamask/notification-services-controller>firebase>@firebase/app": true, + "@metamask/notification-services-controller>firebase>@firebase/messaging": true } }, - "crypto-browserify>randombytes": { + "react-focus-lock>focus-lock": { "globals": { - "crypto": true, - "msCrypto": true + "HTMLIFrameElement": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.DOCUMENT_NODE": true, + "Node.DOCUMENT_POSITION_CONTAINED_BY": true, + "Node.DOCUMENT_POSITION_CONTAINS": true, + "Node.ELEMENT_NODE": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "setTimeout": true }, "packages": { - "koa>content-disposition>safe-buffer": true, - "process": true + "tslib": true } }, - "crypto-browserify>randomfill": { - "globals": { - "crypto": true, - "msCrypto": true - }, + "browserify>util>which-typed-array>for-each": { "packages": { - "crypto-browserify>randombytes": true, - "koa>content-disposition>safe-buffer": true, - "process": true + "string.prototype.matchall>es-abstract>is-callable": true } }, - "currency-formatter": { - "packages": { - "currency-formatter>accounting": true, - "currency-formatter>locale-currency": true, - "react>object-assign": true + "axios>form-data": { + "globals": { + "FormData": true } }, - "currency-formatter>accounting": { + "fuse.js": { "globals": { + "console": true, "define": true } }, - "currency-formatter>locale-currency": { + "string.prototype.matchall>get-intrinsic": { "globals": { - "countryCode": true + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true, + "depcheck>is-core-module>hasown": true } }, - "debounce-stream": { + "string.prototype.matchall>es-abstract>gopd": { "packages": { - "debounce-stream>debounce": true, - "debounce-stream>duplexer": true, - "debounce-stream>through": true + "string.prototype.matchall>get-intrinsic": true } }, - "debounce-stream>debounce": { + "eth-lattice-keyring>gridplus-sdk": { "globals": { + "AbortController": true, + "Request": true, + "URL": true, + "__values": true, + "caches": true, "clearTimeout": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "fetch": true, "setTimeout": true - } - }, - "debounce-stream>duplexer": { + }, "packages": { - "stream-browserify": true + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "@ethersproject/abi": true, + "eth-lattice-keyring>gridplus-sdk>aes-js": true, + "@metamask/keyring-api>bech32": true, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, + "eth-lattice-keyring>gridplus-sdk>bitwise": true, + "bn.js": true, + "eth-lattice-keyring>gridplus-sdk>borc": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "browserify>buffer": true, + "@ethereumjs/tx>@ethereumjs/common>crc-32": true, + "eth-lattice-keyring>gridplus-sdk>elliptic": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "ethers>@ethersproject/sha2>hash.js": true, + "eth-ens-namehash>js-sha3": true, + "lodash": true, + "eth-lattice-keyring>rlp": true, + "ganache>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>uuid": true } }, - "debounce-stream>through": { + "string.prototype.matchall>es-abstract>has-property-descriptors": { "packages": { - "process": true, - "stream-browserify": true + "string.prototype.matchall>call-bind>es-define-property": true } }, - "depcheck>@vue/compiler-sfc>postcss>nanoid": { - "globals": { - "crypto.getRandomValues": true + "koa>is-generator-function>has-tostringtag": { + "packages": { + "string.prototype.matchall>has-symbols": true } }, - "depcheck>is-core-module>hasown": { + "ethereumjs-util>create-hash>md5.js>hash-base": { "packages": { - "browserify>has>function-bind": true + "pumpify>inherits": true, + "readable-stream": true, + "koa>content-disposition>safe-buffer": true } }, - "dependency-tree>precinct>detective-postcss>postcss>nanoid": { - "globals": { - "crypto.getRandomValues": true + "ethers>@ethersproject/sha2>hash.js": { + "packages": { + "pumpify>inherits": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true } }, - "eslint-plugin-react>array-includes>is-string": { + "depcheck>is-core-module>hasown": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "browserify>has>function-bind": true } }, - "eth-ens-namehash": { - "globals": { - "name": "write" - }, + "@metamask/eth-trezor-keyring>hdkey": { "packages": { - "@metamask/ethjs>js-sha3": true, - "browserify>buffer": true, - "eth-ens-namehash>idna-uts46-hx": true + "browserify>assert": true, + "ethereumjs-util>ethereum-cryptography>bs58check": true, + "crypto-browserify": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "ganache>secp256k1": true } }, - "eth-ens-namehash>idna-uts46-hx": { + "he": { "globals": { "define": true - }, - "packages": { - "browserify>punycode": true } }, - "eth-keyring-controller>@metamask/browser-passworder": { + "history": { "globals": { - "crypto": true + "console": true, + "define": true, + "document.defaultView": true, + "document.querySelector": true } }, - "eth-lattice-keyring": { + "react-router-dom>history": { "globals": { "addEventListener": true, - "browser": true, - "clearInterval": true, - "fetch": true, - "open": true, - "setInterval": true - }, - "packages": { - "@ethereumjs/tx": true, - "@ethereumjs/tx>@ethereumjs/util": true, - "bn.js": true, - "browserify>buffer": true, - "crypto-browserify": true, - "eth-lattice-keyring>gridplus-sdk": true, - "eth-lattice-keyring>rlp": true, - "webpack>events": true - } - }, - "eth-lattice-keyring>gridplus-sdk": { - "globals": { - "AbortController": true, - "Request": true, - "URL": true, - "__values": true, - "caches": true, - "clearTimeout": true, - "console.error": true, - "console.log": true, - "console.warn": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/common>crc-32": true, - "@ethersproject/abi": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "@metamask/eth-sig-util": true, - "@metamask/ethjs>js-sha3": true, - "@metamask/keyring-api>bech32": true, - "@metamask/ppom-validator>elliptic": true, - "bn.js": true, - "browserify>buffer": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, - "eth-lattice-keyring>gridplus-sdk>aes-js": true, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc": true, - "eth-lattice-keyring>gridplus-sdk>bs58check": true, - "eth-lattice-keyring>gridplus-sdk>secp256k1": true, - "eth-lattice-keyring>gridplus-sdk>uuid": true, - "ethers>@ethersproject/sha2>hash.js": true, - "lodash": true - } - }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "confirm": true, + "document": true, + "history": true, + "location": true, + "navigator.userAgent": true, + "removeEventListener": true + }, "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "react-router-dom>history>resolve-pathname": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true, + "react-router-dom>history>value-equal": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "@metamask/ppom-validator>elliptic>hmac-drbg": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true + "ethers>@ethersproject/sha2>hash.js": true, + "@metamask/ppom-validator>elliptic>minimalistic-assert": true, + "@metamask/ppom-validator>elliptic>minimalistic-crypto-utils": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "react-redux>hoist-non-react-statics": { "packages": { - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": true, - "webpack>events": true + "prop-types>react-is": true } }, - "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/util": { - "globals": { - "console.warn": true, - "fetch": true - }, + "https-browserify": { "packages": { - "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/eth-ledger-bridge-keyring>@ethereumjs/rlp": true, - "webpack>events": true + "stream-http": true, + "browserify>url": true } }, - "eth-lattice-keyring>gridplus-sdk>aes-js": { + "@metamask/notification-services-controller>firebase>@firebase/app>idb": { "globals": { - "define": true + "DOMException": true, + "IDBCursor": true, + "IDBDatabase": true, + "IDBIndex": true, + "IDBObjectStore": true, + "IDBRequest": true, + "IDBTransaction": true, + "indexedDB.deleteDatabase": true, + "indexedDB.open": true } }, - "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "eth-ens-namehash>idna-uts46-hx": { "globals": { - "crypto": true, "define": true - } - }, - "eth-lattice-keyring>gridplus-sdk>borc": { - "globals": { - "console": true }, "packages": { - "browserify>buffer": true, - "buffer>ieee754": true, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true + "browserify>punycode": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { - "globals": { - "crypto": true, - "define": true + "string.prototype.matchall>internal-slot": { + "packages": { + "string.prototype.matchall>call-bind>es-errors": true, + "depcheck>is-core-module>hasown": true, + "string.prototype.matchall>side-channel": true } }, - "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { - "globals": { - "URL": true, - "URLSearchParams": true, - "location": true, - "navigator": true + "browserify>util>is-arguments": { + "packages": { + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check": { + "string.prototype.matchall>es-abstract>is-array-buffer": { "packages": { - "@noble/hashes": true, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": { "packages": { - "eth-lattice-keyring>gridplus-sdk>bs58check>bs58>base-x": true + "string.prototype.matchall>es-abstract>unbox-primitive>has-bigints": true } }, - "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": { "packages": { - "@metamask/ppom-validator>elliptic": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "eth-lattice-keyring>gridplus-sdk>uuid": { + "string.prototype.matchall>es-abstract>is-callable": { "globals": { - "crypto": true + "document": true } }, - "eth-lattice-keyring>rlp": { - "globals": { - "TextEncoder": true + "@metamask/eth-token-tracker>deep-equal>is-date-object": { + "packages": { + "koa>is-generator-function>has-tostringtag": true } }, - "eth-method-registry": { + "koa>is-generator-function": { "packages": { - "@metamask/ethjs-contract": true, - "@metamask/ethjs-query": true + "koa>is-generator-function>has-tostringtag": true } }, - "ethereumjs-util": { - "packages": { - "bn.js": true, - "browserify>assert": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography": true, - "ethereumjs-util>rlp": true + "@material-ui/core>@material-ui/styles>jss>is-in-browser": { + "globals": { + "document": true } }, - "ethereumjs-util>create-hash": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": { "packages": { - "addons-linter>sha.js": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>create-hash>md5.js": true, - "ethereumjs-util>create-hash>ripemd160": true, - "pumpify>inherits": true + "koa>is-generator-function>has-tostringtag": true } }, - "ethereumjs-util>create-hash>cipher-base": { + "string.prototype.matchall>es-abstract>is-regex": { "packages": { - "browserify>string_decoder": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "stream-browserify": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "ethereumjs-util>create-hash>md5.js": { + "string.prototype.matchall>es-abstract>is-shared-array-buffer": { "packages": { - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "string.prototype.matchall>call-bind": true } }, - "ethereumjs-util>create-hash>md5.js>hash-base": { + "eslint-plugin-react>array-includes>is-string": { "packages": { - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true, - "readable-stream": true + "koa>is-generator-function>has-tostringtag": true } }, - "ethereumjs-util>create-hash>ripemd160": { + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { "packages": { - "browserify>buffer": true, - "ethereumjs-util>create-hash>md5.js>hash-base": true, - "pumpify>inherits": true + "string.prototype.matchall>has-symbols": true } }, - "ethereumjs-util>ethereum-cryptography": { + "browserify>util>is-typed-array": { "packages": { - "browserify>buffer": true, - "crypto-browserify>randombytes": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ganache>secp256k1": true + "browserify>util>which-typed-array": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": { "packages": { - "browserify>buffer": true, - "crypto-browserify>browserify-cipher>evp_bytestokey": true, - "ethereumjs-util>create-hash>cipher-base": true, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, - "koa>content-disposition>safe-buffer": true, - "pumpify>inherits": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true } }, - "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "globals": { + "URL": true, + "URLSearchParams": true, + "location": true + } + }, + "@ensdomains/content-hash>js-base64": { + "globals": { + "Base64": "write", + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true, + "define": true + }, "packages": { "browserify>buffer": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check": { + "eth-ens-namehash>js-sha3": { + "globals": { + "define": true + }, "packages": { - "ethereumjs-util>create-hash": true, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": true, - "koa>content-disposition>safe-buffer": true + "process": true } }, - "ethereumjs-util>ethereum-cryptography>bs58check>bs58": { - "packages": { - "@ensdomains/content-hash>multihashes>multibase>base-x": true + "@ngraveio/bc-ur>jsbi": { + "globals": { + "define": true } }, - "ethereumjs-util>ethereum-cryptography>keccak": { + "@metamask/message-manager>jsonschema": { "packages": { - "browserify>buffer": true, - "readable-stream": true + "browserify>url": true } }, - "ethereumjs-util>rlp": { + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { "packages": { - "bn.js": true, - "browserify>buffer": true + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true } }, - "ethereumjs-wallet>randombytes": { + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { "globals": { - "crypto.getRandomValues": true + "CSS": true + }, + "packages": { + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethers": { + "@material-ui/core>@material-ui/styles>jss-plugin-global": { "packages": { - "@ethersproject/abi": true, - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/contracts": true, - "@ethersproject/hash": true, - "@ethersproject/hdnode": true, - "@ethersproject/wallet": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/json-wallets": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/signing-key": true, - "ethers>@ethersproject/solidity": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true, - "ethers>@ethersproject/units": true, - "ethers>@ethersproject/web": true, - "ethers>@ethersproject/wordlists": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethers>@ethersproject/abstract-provider": { + "@material-ui/core>@material-ui/styles>jss-plugin-nested": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "@babel/runtime": true, + "react-router-dom>tiny-warning": true } }, - "ethers>@ethersproject/abstract-signer": { + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { "packages": { - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "@material-ui/core>@material-ui/styles>jss": true, + "react-router-dom>tiny-warning": true } }, - "ethers>@ethersproject/address": { + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/rlp": true + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true, + "@material-ui/core>@material-ui/styles>jss": true } }, - "ethers>@ethersproject/base64": { + "@material-ui/core>@material-ui/styles>jss": { "globals": { - "atob": true, - "btoa": true + "CSS": true, + "document.createElement": true, + "document.querySelector": true }, "packages": { - "@ethersproject/bytes": true + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, + "react-router-dom>tiny-warning": true } }, - "ethers>@ethersproject/basex": { + "ethereumjs-util>ethereum-cryptography>keccak": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/properties": true + "browserify>buffer": true, + "readable-stream": true } }, - "ethers>@ethersproject/constants": { - "packages": { - "@ethersproject/bignumber": true + "currency-formatter>locale-currency": { + "globals": { + "countryCode": true } }, - "ethers>@ethersproject/json-wallets": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hdnode": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/json-wallets>aes-js": true, - "ethers>@ethersproject/json-wallets>scrypt-js": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/pbkdf2": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "localforage": { + "globals": { + "Blob": true, + "BlobBuilder": true, + "FileReader": true, + "IDBKeyRange": true, + "MSBlobBuilder": true, + "MozBlobBuilder": true, + "OIndexedDB": true, + "WebKitBlobBuilder": true, + "atob": true, + "btoa": true, + "console.error": true, + "console.info": true, + "console.warn": true, + "define": true, + "fetch": true, + "indexedDB": true, + "localStorage": true, + "mozIndexedDB": true, + "msIndexedDB": true, + "navigator.platform": true, + "navigator.userAgent": true, + "openDatabase": true, + "setTimeout": true, + "webkitIndexedDB": true } }, - "ethers>@ethersproject/json-wallets>aes-js": { + "lodash": { "globals": { - "define": true + "clearTimeout": true, + "define": true, + "setTimeout": true } }, - "ethers>@ethersproject/json-wallets>scrypt-js": { + "loglevel": { + "globals": { + "console": true, + "define": true, + "document.cookie": true, + "localStorage": true, + "log": "write", + "navigator": true + } + }, + "lottie-web": { "globals": { + "Blob": true, + "Howl": true, + "OffscreenCanvas": true, + "URL.createObjectURL": true, + "Worker": true, + "XMLHttpRequest": true, + "bodymovin": "write", + "clearInterval": true, + "console": true, "define": true, + "document.body": true, + "document.createElement": true, + "document.createElementNS": true, + "document.getElementsByClassName": true, + "document.getElementsByTagName": true, + "document.querySelectorAll": true, + "document.readyState": true, + "location.origin": true, + "location.pathname": true, + "navigator": true, + "requestAnimationFrame": true, + "setInterval": true, "setTimeout": true - }, - "packages": { - "browserify>timers-browserify": true } }, - "ethers>@ethersproject/keccak256": { - "packages": { - "@ethersproject/bytes": true, - "@metamask/ethjs>js-sha3": true + "luxon": { + "globals": { + "Intl": true } }, - "ethers>@ethersproject/logger": { + "@metamask/snaps-utils>marked": { "globals": { - "console": true + "console.error": true, + "console.warn": true, + "define": true } }, - "ethers>@ethersproject/pbkdf2": { + "ethereumjs-util>create-hash>md5.js": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/sha2": true + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "ethers>@ethersproject/properties": { + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { "packages": { - "ethers>@ethersproject/logger": true + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/providers": { - "globals": { - "WebSocket": true, - "clearInterval": true, - "clearTimeout": true, - "console.log": true, - "console.warn": true, - "setInterval": true, - "setTimeout": true - }, + "react-markdown>remark-parse>mdast-util-from-markdown": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/abstract-provider": true, - "ethers>@ethersproject/abstract-signer": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/basex": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/providers>@ethersproject/networks": true, - "ethers>@ethersproject/providers>@ethersproject/web": true, - "ethers>@ethersproject/providers>bech32": true, - "ethers>@ethersproject/random": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true, - "ethers>@ethersproject/transactions": true + "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, + "react-syntax-highlighter>refractor>parse-entities": true, + "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true } }, - "ethers>@ethersproject/providers>@ethersproject/networks": { + "react-markdown>remark-rehype>mdast-util-to-hast": { + "globals": { + "console.warn": true + }, "packages": { - "ethers>@ethersproject/logger": true + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, + "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, + "react-markdown>unist-util-visit": true } }, - "ethers>@ethersproject/providers>@ethersproject/web": { + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true }, "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "https-browserify": true, + "process": true, + "stream-http": true, + "browserify>url": true, + "browserify>util": true } }, - "ethers>@ethersproject/random": { + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "react-syntax-highlighter>refractor>parse-entities": true } }, - "ethers>@ethersproject/rlp": { + "crypto-browserify>diffie-hellman>miller-rabin": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true + "bn.js": true, + "@metamask/ppom-validator>elliptic>brorand": true } }, - "ethers>@ethersproject/sha2": { + "@ensdomains/content-hash>cids>multibase": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2>hash.js": true + "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true } }, - "ethers>@ethersproject/sha2>hash.js": { + "@ensdomains/content-hash>multihashes>multibase": { "packages": { - "@metamask/ppom-validator>elliptic>minimalistic-assert": true, - "pumpify>inherits": true + "@ensdomains/content-hash>multihashes>multibase>base-x": true, + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/signing-key": { + "@ensdomains/content-hash>multicodec": { "packages": { - "@ethersproject/bytes": true, - "@metamask/ppom-validator>elliptic": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true + "@ensdomains/content-hash>multicodec>uint8arrays": true, + "sass-embedded>varint": true } }, - "ethers>@ethersproject/solidity": { + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "console.warn": true, + "crypto.subtle.digest": true + } + }, + "@ensdomains/content-hash>multihashes": { "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/sha2": true, - "ethers>@ethersproject/strings": true + "browserify>buffer": true, + "@ensdomains/content-hash>multihashes>multibase": true, + "@ensdomains/content-hash>multihashes>varint": true, + "@ensdomains/content-hash>multihashes>web-encoding": true } }, - "ethers>@ethersproject/strings": { + "@ensdomains/content-hash>cids>multihashes": { "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/logger": true + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>cids>multihashes>varint": true + } + }, + "nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/transactions": { - "packages": { - "@ethersproject/bignumber": true, - "@ethersproject/bytes": true, - "ethers>@ethersproject/address": true, - "ethers>@ethersproject/constants": true, - "ethers>@ethersproject/keccak256": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/rlp": true, - "ethers>@ethersproject/signing-key": true + "@metamask/approval-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/units": { - "packages": { - "@ethersproject/bignumber": true, - "ethers>@ethersproject/logger": true + "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/web": { + "@metamask/notification-controller>nanoid": { "globals": { - "clearTimeout": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethersproject/bytes": true, - "ethers>@ethersproject/base64": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "crypto.getRandomValues": true } }, - "ethers>@ethersproject/wordlists": { - "packages": { - "@ethersproject/bytes": true, - "@ethersproject/hash": true, - "ethers>@ethersproject/logger": true, - "ethers>@ethersproject/properties": true, - "ethers>@ethersproject/strings": true + "@metamask/permission-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream": { - "packages": { - "browserify>buffer": true, - "extension-port-stream>readable-stream": true + "@metamask/rpc-methods>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream": { + "@metamask/rpc-methods-flask>nanoid": { "globals": { - "AbortController": true, - "AggregateError": true, - "Blob": true - }, - "packages": { - "browserify>buffer": true, - "browserify>string_decoder": true, - "extension-port-stream>readable-stream>abort-controller": true, - "process": true, - "webpack>events": true + "crypto.getRandomValues": true } }, - "extension-port-stream>readable-stream>abort-controller": { + "@metamask/snaps-controllers>nanoid": { "globals": { - "AbortController": true + "crypto.getRandomValues": true } }, - "fast-json-patch": { + "@metamask/snaps-controllers-flask>nanoid": { "globals": { - "addEventListener": true, - "clearTimeout": true, - "removeEventListener": true, - "setTimeout": true + "crypto.getRandomValues": true } }, - "fuse.js": { + "depcheck>@vue/compiler-sfc>postcss>nanoid": { "globals": { - "console": true, - "define": true + "crypto.getRandomValues": true } }, - "ganache>secp256k1": { - "packages": { - "@metamask/ppom-validator>elliptic": true + "dependency-tree>precinct>detective-postcss>postcss>nanoid": { + "globals": { + "crypto.getRandomValues": true } }, - "gulp>vinyl-fs>object.assign": { - "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>has-symbols": true + "node-fetch": { + "globals": { + "Headers": true, + "Request": true, + "Response": true, + "fetch": true } }, - "he": { + "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { "globals": { - "define": true + "fetch": true } }, - "history": { + "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { "globals": { - "console": true, - "define": true, - "document.defaultView": true, - "document.querySelector": true + "fetch": true } }, - "https-browserify": { + "eth-method-registry>@metamask/ethjs-contract>ethjs-abi>number-to-bn": { "packages": { - "browserify>url": true, - "stream-http": true + "bn.js": true, + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": true } }, - "koa>content-disposition>safe-buffer": { + "string.prototype.matchall>es-abstract>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, "packages": { - "browserify>buffer": true + "browserify>browser-resolve": true } }, - "koa>is-generator-function": { + "@ngraveio/bc-ur>assert>object-is": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true } }, - "koa>is-generator-function>has-tostringtag": { + "gulp>vinyl-fs>object.assign": { "packages": { - "string.prototype.matchall>has-symbols": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>has-symbols": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "localforage": { - "globals": { - "Blob": true, - "BlobBuilder": true, - "FileReader": true, - "IDBKeyRange": true, - "MSBlobBuilder": true, - "MozBlobBuilder": true, - "OIndexedDB": true, - "WebKitBlobBuilder": true, - "atob": true, - "btoa": true, - "console.error": true, - "console.info": true, - "console.warn": true, - "define": true, - "fetch": true, - "indexedDB": true, - "localStorage": true, - "mozIndexedDB": true, - "msIndexedDB": true, - "navigator.platform": true, - "navigator.userAgent": true, - "openDatabase": true, - "setTimeout": true, - "webkitIndexedDB": true + "@metamask/object-multiplex>once": { + "packages": { + "@metamask/object-multiplex>once>wrappy": true } }, - "lodash": { - "globals": { - "clearTimeout": true, - "define": true, - "setTimeout": true + "crypto-browserify>public-encrypt>parse-asn1": { + "packages": { + "crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes": true, + "browserify>buffer": true, + "crypto-browserify>browserify-cipher>evp_bytestokey": true, + "crypto-browserify>pbkdf2": true } }, - "loglevel": { + "react-syntax-highlighter>refractor>parse-entities": { "globals": { - "console": true, - "define": true, - "document.cookie": true, - "localStorage": true, - "log": "write", - "navigator": true + "document.createElement": true } }, - "lottie-web": { - "globals": { - "Blob": true, - "Howl": true, - "OffscreenCanvas": true, - "URL.createObjectURL": true, - "Worker": true, - "XMLHttpRequest": true, - "bodymovin": "write", - "clearInterval": true, - "console": true, - "define": true, - "document.body": true, - "document.createElement": true, - "document.createElementNS": true, - "document.getElementsByClassName": true, - "document.getElementsByTagName": true, - "document.querySelectorAll": true, - "document.readyState": true, - "location.origin": true, - "location.pathname": true, - "navigator": true, - "requestAnimationFrame": true, - "setInterval": true, - "setTimeout": true + "path-browserify": { + "packages": { + "process": true } }, - "luxon": { - "globals": { - "Intl": true + "serve-handler>path-to-regexp": { + "packages": { + "serve-handler>path-to-regexp>isarray": true } }, - "nanoid": { + "crypto-browserify>pbkdf2": { "globals": { "crypto": true, - "msCrypto": true, - "navigator": true + "process": true, + "queueMicrotask": true, + "setImmediate": true, + "setTimeout": true + }, + "packages": { + "ethereumjs-util>create-hash": true, + "process": true, + "ethereumjs-util>create-hash>ripemd160": true, + "koa>content-disposition>safe-buffer": true, + "addons-linter>sha.js": true } }, - "nock>debug": { + "@material-ui/core>popper.js": { "globals": { - "console": true, + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, "document": true, - "localStorage": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, "navigator": true, - "process": true - }, - "packages": { - "nock>debug>ms": true, - "process": true + "requestAnimationFrame": true, + "setTimeout": true } }, - "node-fetch": { + "react-tippy>popper.js": { "globals": { - "Headers": true, - "Request": true, - "Response": true, - "fetch": true - } - }, - "path-browserify": { - "packages": { - "process": true + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.userAgent": true, + "requestAnimationFrame": true, + "setTimeout": true } }, "process": { @@ -5078,26 +4847,51 @@ "promise-to-callback>set-immediate-shim": true } }, - "promise-to-callback>set-immediate-shim": { + "prop-types": { "globals": { - "setTimeout.apply": true + "console": true }, "packages": { - "browserify>timers-browserify": true + "react>object-assign": true, + "prop-types>react-is": true } }, - "prop-types": { + "react-markdown>property-information": { + "packages": { + "watchify>xtend": true + } + }, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs": { "globals": { - "console": true + "process": true, + "setTimeout": true }, "packages": { - "prop-types>react-is": true, - "react>object-assign": true + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/aspromise": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/base64": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/codegen": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/eventemitter": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/fetch": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/float": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/inquire": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/path": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/pool": true, + "@trezor/connect-web>@trezor/connect>@trezor/protobuf>protobufjs>@protobufjs/utf8": true } }, - "prop-types>react-is": { + "crypto-browserify>public-encrypt": { + "packages": { + "bn.js": true, + "crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "crypto-browserify>public-encrypt>parse-asn1": true, + "crypto-browserify>randombytes": true + } + }, + "browserify>punycode": { "globals": { - "console": true + "define": true } }, "qrcode-generator": { @@ -5114,13 +4908,55 @@ "react": true } }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "globals": { + "queueMicrotask": true + } + }, + "react-beautiful-dnd>raf-schd": { + "globals": { + "cancelAnimationFrame": true, + "requestAnimationFrame": true + } + }, + "crypto-browserify>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "process": true, + "koa>content-disposition>safe-buffer": true + } + }, + "ethereumjs-wallet>randombytes": { + "globals": { + "crypto.getRandomValues": true + } + }, + "crypto-browserify>randomfill": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "process": true, + "crypto-browserify>randombytes": true, + "koa>content-disposition>safe-buffer": true + } + }, "react": { "globals": { "console": true }, "packages": { - "prop-types": true, - "react>object-assign": true + "react>object-assign": true, + "prop-types": true } }, "react-beautiful-dnd": { @@ -5142,43 +4978,28 @@ }, "packages": { "@babel/runtime": true, - "react": true, "react-beautiful-dnd>css-box-model": true, "react-beautiful-dnd>memoize-one": true, "react-beautiful-dnd>raf-schd": true, - "react-beautiful-dnd>use-memo-one": true, + "react": true, "react-dom": true, "react-redux": true, - "redux": true + "redux": true, + "react-beautiful-dnd>use-memo-one": true } }, - "react-beautiful-dnd>css-box-model": { + "react-chartjs-2": { "globals": { - "getComputedStyle": true, - "pageXOffset": true, - "pageYOffset": true + "setTimeout": true }, "packages": { - "react-router-dom>tiny-invariant": true - } - }, - "react-beautiful-dnd>raf-schd": { - "globals": { - "cancelAnimationFrame": true, - "requestAnimationFrame": true - } - }, - "react-beautiful-dnd>use-memo-one": { - "packages": { + "chart.js": true, "react": true } }, - "react-chartjs-2": { - "globals": { - "setTimeout": true - }, + "react-focus-lock>react-clientside-effect": { "packages": { - "chart.js": true, + "@babel/runtime": true, "react": true } }, @@ -5223,22 +5044,28 @@ "trustedTypes": true }, "packages": { + "react>object-assign": true, "prop-types": true, "react": true, - "react-dom>scheduler": true, - "react>object-assign": true + "react-dom>scheduler": true } }, - "react-dom>scheduler": { + "react-responsive-carousel>react-easy-swipe": { "globals": { - "MessageChannel": true, - "cancelAnimationFrame": true, - "clearTimeout": true, - "console": true, - "navigator": true, - "performance": true, - "requestAnimationFrame": true, - "setTimeout": true + "addEventListener": true, + "define": true, + "document.addEventListener": true, + "document.removeEventListener": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-popper>react-fast-compare": { + "globals": { + "Element": true, + "console.warn": true } }, "react-focus-lock": { @@ -5252,642 +5079,726 @@ }, "packages": { "@babel/runtime": true, + "react-focus-lock>focus-lock": true, "prop-types": true, "react": true, - "react-focus-lock>focus-lock": true, "react-focus-lock>react-clientside-effect": true, "react-focus-lock>use-callback-ref": true, "react-focus-lock>use-sidecar": true } }, - "react-focus-lock>focus-lock": { + "react-idle-timer": { + "globals": { + "clearTimeout": true, + "document": true, + "setTimeout": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-inspector": { + "globals": { + "Node": true, + "chromeDark": true, + "chromeLight": true + }, + "packages": { + "react": true + } + }, + "prop-types>react-is": { + "globals": { + "console": true + } + }, + "react-markdown>react-is": { + "globals": { + "console": true + } + }, + "react-redux>react-is": { + "globals": { + "console": true + } + }, + "react-markdown": { + "globals": { + "console.warn": true + }, + "packages": { + "react-markdown>comma-separated-tokens": true, + "prop-types": true, + "react-markdown>property-information": true, + "react": true, + "react-markdown>react-is": true, + "react-markdown>remark-parse": true, + "react-markdown>remark-rehype": true, + "react-markdown>space-separated-tokens": true, + "react-markdown>style-to-object": true, + "react-markdown>unified": true, + "react-markdown>unist-util-visit": true, + "react-markdown>vfile": true + } + }, + "react-popper": { + "globals": { + "document": true + }, + "packages": { + "@popperjs/core": true, + "react": true, + "react-popper>react-fast-compare": true, + "react-popper>warning": true + } + }, + "react-redux": { + "globals": { + "console": true, + "document": true + }, + "packages": { + "@babel/runtime": true, + "react-redux>hoist-non-react-statics": true, + "prop-types": true, + "react": true, + "react-dom": true, + "react-redux>react-is": true + } + }, + "react-responsive-carousel": { + "globals": { + "HTMLElement": true, + "addEventListener": true, + "clearTimeout": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "removeEventListener": true, + "setTimeout": true + }, + "packages": { + "classnames": true, + "react": true, + "react-dom": true, + "react-responsive-carousel>react-easy-swipe": true + } + }, + "react-router-dom": { + "packages": { + "react-router-dom>history": true, + "prop-types": true, + "react": true, + "react-router-dom>react-router": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom-v5-compat": { "globals": { - "HTMLIFrameElement": true, - "Node.DOCUMENT_FRAGMENT_NODE": true, - "Node.DOCUMENT_NODE": true, - "Node.DOCUMENT_POSITION_CONTAINED_BY": true, - "Node.DOCUMENT_POSITION_CONTAINS": true, - "Node.ELEMENT_NODE": true, - "console.error": true, - "console.warn": true, + "FormData": true, + "URL": true, + "URLSearchParams": true, + "__reactRouterVersion": "write", + "addEventListener": true, + "confirm": true, + "define": true, "document": true, - "getComputedStyle": true, + "history.scrollRestoration": true, + "location.href": true, + "removeEventListener": true, + "scrollTo": true, + "scrollY": true, + "sessionStorage.getItem": true, + "sessionStorage.setItem": true, "setTimeout": true }, "packages": { - "@swc/helpers>tslib": true + "react-router-dom-v5-compat>@remix-run/router": true, + "history": true, + "react": true, + "react-dom": true, + "react-router-dom": true, + "react-router-dom-v5-compat>react-router": true } }, - "react-focus-lock>react-clientside-effect": { + "react-router-dom>react-router": { "packages": { - "@babel/runtime": true, - "react": true + "react-router-dom>history": true, + "react-redux>hoist-non-react-statics": true, + "serve-handler>path-to-regexp": true, + "prop-types": true, + "react": true, + "prop-types>react-is": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true } }, - "react-focus-lock>use-callback-ref": { + "react-router-dom-v5-compat>react-router": { + "globals": { + "console.error": true, + "define": true + }, "packages": { + "react-router-dom-v5-compat>@remix-run/router": true, "react": true } }, - "react-focus-lock>use-sidecar": { + "react-simple-file-input": { "globals": { - "console.error": true + "File": true, + "FileReader": true, + "console.warn": true }, "packages": { - "@swc/helpers>tslib": true, - "react": true, - "react-focus-lock>use-sidecar>detect-node-es": true + "prop-types": true, + "react": true } }, - "react-idle-timer": { + "react-tippy": { "globals": { + "Element": true, + "MSStream": true, + "MutationObserver": true, + "addEventListener": true, "clearTimeout": true, + "console.error": true, + "console.warn": true, + "define": true, "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.maxTouchPoints": true, + "navigator.msMaxTouchPoints": true, + "navigator.userAgent": true, + "performance": true, + "requestAnimationFrame": true, "setTimeout": true }, "packages": { - "prop-types": true, - "react": true + "react-tippy>popper.js": true, + "react": true, + "react-dom": true } }, - "react-inspector": { + "react-toggle-button": { "globals": { - "Node": true, - "chromeDark": true, - "chromeLight": true + "clearTimeout": true, + "console.warn": true, + "define": true, + "performance": true, + "setTimeout": true }, "packages": { "react": true } }, - "react-markdown": { + "@material-ui/core>react-transition-group": { "globals": { - "console.warn": true + "Element": true, + "setTimeout": true }, "packages": { + "@material-ui/core>react-transition-group>dom-helpers": true, "prop-types": true, "react": true, - "react-markdown>comma-separated-tokens": true, - "react-markdown>property-information": true, - "react-markdown>react-is": true, - "react-markdown>remark-parse": true, - "react-markdown>remark-rehype": true, - "react-markdown>space-separated-tokens": true, - "react-markdown>style-to-object": true, - "react-markdown>unified": true, - "react-markdown>unist-util-visit": true, - "react-markdown>vfile": true + "react-dom": true } }, - "react-markdown>property-information": { + "readable-stream": { "packages": { - "watchify>xtend": true + "browserify>browser-resolve": true, + "browserify>buffer": true, + "webpack>events": true, + "pumpify>inherits": true, + "process": true, + "browserify>string_decoder": true, + "readable-stream>util-deprecate": true } }, - "react-markdown>react-is": { + "extension-port-stream>readable-stream": { "globals": { - "console": true - } - }, - "react-markdown>remark-parse": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown": { - "packages": { - "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, - "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true, - "react-syntax-highlighter>refractor>parse-entities": true - } - }, - "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { + "AbortController": true, + "AbortSignal": true, + "AggregateError": true, + "Blob": true, + "ERR_INVALID_ARG_TYPE": true, + "queueMicrotask": true + }, "packages": { - "react-syntax-highlighter>refractor>parse-entities": true + "@lavamoat/lavapack>readable-stream>abort-controller": true, + "browserify>buffer": true, + "webpack>events": true, + "process": true, + "browserify>string_decoder": true } }, - "react-markdown>remark-rehype": { + "@metamask/snaps-controllers>readable-web-to-node-stream": { "packages": { - "react-markdown>remark-rehype>mdast-util-to-hast": true + "readable-stream": true } }, - "react-markdown>remark-rehype>mdast-util-to-hast": { + "redux": { "globals": { - "console.warn": true + "console": true }, "packages": { - "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, - "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, - "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, - "react-markdown>unist-util-visit": true + "@babel/runtime": true } }, - "react-markdown>style-to-object": { + "string.prototype.matchall>regexp.prototype.flags": { "packages": { - "react-markdown>style-to-object>inline-style-parser": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": true } }, - "react-markdown>unified": { + "react-markdown>remark-parse": { "packages": { - "mocha>yargs-unparser>is-plain-obj": true, - "react-markdown>unified>bail": true, - "react-markdown>unified>extend": true, - "react-markdown>unified>is-buffer": true, - "react-markdown>unified>trough": true, - "react-markdown>vfile": true + "react-markdown>remark-parse>mdast-util-from-markdown": true } }, - "react-markdown>unist-util-visit": { + "react-markdown>remark-rehype": { "packages": { - "react-markdown>unist-util-visit>unist-util-visit-parents": true + "react-markdown>remark-rehype>mdast-util-to-hast": true } }, - "react-markdown>unist-util-visit>unist-util-visit-parents": { + "react-markdown>vfile>replace-ext": { "packages": { - "react-markdown>unist-util-visit>unist-util-is": true + "path-browserify": true } }, - "react-markdown>vfile": { - "packages": { - "path-browserify": true, - "process": true, - "react-markdown>vfile>is-buffer": true, - "react-markdown>vfile>replace-ext": true, - "react-markdown>vfile>vfile-message": true + "reselect": { + "globals": { + "WeakRef": true, + "console.warn": true, + "unstable_autotrackMemoize": true } }, - "react-markdown>vfile>replace-ext": { + "@metamask/snaps-utils>rfdc": { "packages": { - "path-browserify": true + "browserify>buffer": true } }, - "react-markdown>vfile>vfile-message": { + "ethereumjs-util>create-hash>ripemd160": { "packages": { - "react-markdown>vfile>unist-util-stringify-position": true + "browserify>buffer": true, + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true } }, - "react-popper": { - "globals": { - "document": true - }, + "@keystonehq/metamask-airgapped-keyring>rlp": { "packages": { - "@popperjs/core": true, - "react": true, - "react-popper>react-fast-compare": true, - "react-popper>warning": true - } - }, - "react-popper>react-fast-compare": { - "globals": { - "Element": true, - "console.warn": true + "bn.js": true, + "browserify>buffer": true } }, - "react-popper>warning": { + "eth-lattice-keyring>rlp": { "globals": { - "console": true + "TextEncoder": true } }, - "react-redux": { - "globals": { - "console": true, - "document": true - }, + "ethereumjs-util>rlp": { "packages": { - "@babel/runtime": true, - "prop-types": true, - "react": true, - "react-dom": true, - "react-redux>hoist-non-react-statics": true, - "react-redux>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>hoist-non-react-statics": { + "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { "packages": { - "prop-types>react-is": true + "bn.js": true, + "browserify>buffer": true } }, - "react-redux>react-is": { + "wait-on>rxjs": { "globals": { - "console": true + "cancelAnimationFrame": true, + "clearInterval": true, + "clearTimeout": true, + "performance": true, + "requestAnimationFrame": true, + "setInterval.apply": true, + "setTimeout.apply": true } }, - "react-responsive-carousel": { - "globals": { - "addEventListener": true, - "removeEventListener": true + "koa>content-disposition>safe-buffer": { + "packages": { + "browserify>buffer": true } }, - "react-router-dom": { - "packages": { - "prop-types": true, - "react": true, - "react-router-dom>history": true, - "react-router-dom>react-router": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "react-dom>scheduler": { + "globals": { + "MessageChannel": true, + "cancelAnimationFrame": true, + "clearTimeout": true, + "console": true, + "navigator": true, + "performance": true, + "requestAnimationFrame": true, + "setTimeout": true } }, - "react-router-dom-v5-compat": { + "ethers>@ethersproject/json-wallets>scrypt-js": { "globals": { - "FormData": true, - "URL": true, - "URLSearchParams": true, - "__reactRouterVersion": "write", - "addEventListener": true, - "confirm": true, "define": true, - "document": true, - "history.scrollRestoration": true, - "location.href": true, - "removeEventListener": true, - "scrollTo": true, - "scrollY": true, - "sessionStorage.getItem": true, - "sessionStorage.setItem": true, "setTimeout": true }, "packages": { - "history": true, - "react": true, - "react-dom": true, - "react-router-dom": true, - "react-router-dom-v5-compat>@remix-run/router": true, - "react-router-dom-v5-compat>react-router": true + "browserify>timers-browserify": true } }, - "react-router-dom-v5-compat>@remix-run/router": { - "globals": { - "AbortController": true, - "DOMException": true, - "FormData": true, - "Headers": true, - "Request": true, - "Response": true, - "URL": true, - "URLSearchParams": true, - "console": true, - "document.defaultView": true + "ganache>secp256k1": { + "packages": { + "@metamask/ppom-validator>elliptic": true } }, - "react-router-dom-v5-compat>react-router": { + "semver": { "globals": { - "console.error": true, - "define": true + "console.error": true }, "packages": { - "react": true, - "react-router-dom-v5-compat>@remix-run/router": true + "process": true } }, - "react-router-dom>history": { + "string.prototype.matchall>call-bind>set-function-length": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>gopd": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true + } + }, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true + } + }, + "promise-to-callback>set-immediate-shim": { "globals": { - "addEventListener": true, - "confirm": true, - "document": true, - "history": true, - "location": true, - "navigator.userAgent": true, - "removeEventListener": true + "setTimeout.apply": true }, "packages": { - "react-router-dom>history>resolve-pathname": true, - "react-router-dom>history>value-equal": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true + "browserify>timers-browserify": true } }, - "react-router-dom>react-router": { + "addons-linter>sha.js": { "packages": { - "prop-types": true, - "prop-types>react-is": true, - "react": true, - "react-redux>hoist-non-react-statics": true, - "react-router-dom>history": true, - "react-router-dom>tiny-invariant": true, - "react-router-dom>tiny-warning": true, - "serve-handler>path-to-regexp": true + "pumpify>inherits": true, + "koa>content-disposition>safe-buffer": true } }, - "react-router-dom>tiny-warning": { - "globals": { - "console": true + "string.prototype.matchall>side-channel": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>object-inspect": true } }, - "react-simple-file-input": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "File": true, - "FileReader": true, + "console.error": true, "console.warn": true }, "packages": { - "prop-types": true, - "react": true + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "ethers": true, + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true } }, - "react-syntax-highlighter>refractor>parse-entities": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>stop-iteration-iterator": { "globals": { - "document.createElement": true + "StopIteration": true + }, + "packages": { + "string.prototype.matchall>internal-slot": true } }, - "react-tippy": { + "stream-browserify": { + "packages": { + "webpack>events": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "stream-http": { "globals": { - "Element": true, - "MSStream": true, - "MutationObserver": true, - "addEventListener": true, + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, "clearTimeout": true, - "console.error": true, - "console.warn": true, - "define": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.maxTouchPoints": true, - "navigator.msMaxTouchPoints": true, - "navigator.userAgent": true, - "performance": true, - "requestAnimationFrame": true, + "fetch": true, + "location.protocol.search": true, "setTimeout": true }, "packages": { - "react": true, - "react-dom": true, - "react-tippy>popper.js": true + "browserify>buffer": true, + "stream-http>builtin-status-codes": true, + "pumpify>inherits": true, + "process": true, + "readable-stream": true, + "browserify>url": true, + "watchify>xtend": true } }, - "react-tippy>popper.js": { - "globals": { - "MSInputMethodContext": true, - "Node.DOCUMENT_POSITION_FOLLOWING": true, - "cancelAnimationFrame": true, - "console.warn": true, - "define": true, - "devicePixelRatio": true, - "document": true, - "getComputedStyle": true, - "innerHeight": true, - "innerWidth": true, - "navigator.userAgent": true, - "requestAnimationFrame": true, - "setTimeout": true + "@metamask/snaps-controllers>tar-stream>streamx": { + "packages": { + "webpack>events": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true } }, - "react-toggle-button": { - "globals": { - "clearTimeout": true, - "console.warn": true, - "define": true, - "performance": true, - "setTimeout": true - }, + "browserify>string_decoder": { "packages": { - "react": true + "koa>content-disposition>safe-buffer": true } }, - "readable-stream": { + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>strip-hex-prefix": { "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>string_decoder": true, - "process": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format>is-hex-prefixed": true } }, - "readable-stream>util-deprecate": { - "globals": { - "console.trace": true, - "console.warn": true, - "localStorage": true + "react-markdown>style-to-object": { + "packages": { + "react-markdown>style-to-object>inline-style-parser": true } }, - "redux": { - "globals": { - "console": true - }, + "@metamask/snaps-controllers>tar-stream": { "packages": { - "@babel/runtime": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "browserify>browser-resolve": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true } }, - "semver": { + "debounce-stream>through": { + "packages": { + "process": true, + "stream-browserify": true + } + }, + "browserify>timers-browserify": { "globals": { - "console.error": true + "clearInterval": true, + "clearTimeout": true, + "setInterval": true, + "setTimeout": true }, "packages": { "process": true } }, - "serve-handler>path-to-regexp": { - "packages": { - "serve-handler>path-to-regexp>isarray": true + "react-router-dom>tiny-warning": { + "globals": { + "console": true } }, - "stream-browserify": { - "packages": { - "pumpify>inherits": true, - "readable-stream": true, - "webpack>events": true + "copy-to-clipboard>toggle-selection": { + "globals": { + "document.activeElement": true, + "document.getSelection": true + } + }, + "tslib": { + "globals": { + "SuppressedError": true, + "define": true } }, - "stream-http": { + "@metamask/eth-sig-util>tweetnacl": { "globals": { - "AbortController": true, - "Blob": true, - "MSStreamReader": true, - "ReadableStream": true, - "WritableStream": true, - "XDomainRequest": true, - "XMLHttpRequest": true, - "clearTimeout": true, - "fetch": true, - "location.protocol.search": true, - "setTimeout": true + "crypto": true, + "msCrypto": true, + "nacl": "write" }, "packages": { - "browserify>buffer": true, - "browserify>url": true, - "process": true, - "pumpify>inherits": true, - "readable-stream": true, - "stream-http>builtin-status-codes": true, - "watchify>xtend": true + "browserify>browser-resolve": true } }, - "string.prototype.matchall>call-bind": { - "packages": { - "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>call-bind>set-function-length": true, - "string.prototype.matchall>get-intrinsic": true + "@trezor/connect-web>@trezor/connect-common>@trezor/env-utils>ua-parser-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>call-bind>es-define-property": { + "@ensdomains/content-hash>cids>uint8arrays": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>cids>multibase": true } }, - "string.prototype.matchall>call-bind>set-function-length": { + "@ensdomains/content-hash>multicodec>uint8arrays": { + "globals": { + "Buffer": true, + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>gopd": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true, - "string.prototype.matchall>get-intrinsic": true + "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true } }, - "string.prototype.matchall>define-properties": { + "react-markdown>unified": { "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true } }, - "string.prototype.matchall>define-properties>define-data-property": { + "react-markdown>unist-util-visit>unist-util-visit-parents": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true + "react-markdown>unist-util-visit>unist-util-is": true } }, - "string.prototype.matchall>es-abstract>array-buffer-byte-length": { + "react-markdown>unist-util-visit": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>is-array-buffer": true + "react-markdown>unist-util-visit>unist-util-visit-parents": true } }, - "string.prototype.matchall>es-abstract>available-typed-arrays": { - "packages": { - "string.prototype.matchall>es-abstract>typed-array-length>possible-typed-array-names": true + "uri-js": { + "globals": { + "define": true } }, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "browserify>url": { "packages": { - "string.prototype.matchall>has-symbols": true + "browserify>punycode": true, + "@storybook/addon-knobs>qs": true } }, - "string.prototype.matchall>es-abstract>gopd": { + "react-focus-lock>use-callback-ref": { "packages": { - "string.prototype.matchall>get-intrinsic": true + "react": true } }, - "string.prototype.matchall>es-abstract>has-property-descriptors": { + "react-beautiful-dnd>use-memo-one": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true + "react": true } }, - "string.prototype.matchall>es-abstract>is-array-buffer": { + "react-focus-lock>use-sidecar": { + "globals": { + "console.error": true + }, "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>get-intrinsic": true + "react-focus-lock>use-sidecar>detect-node-es": true, + "react": true, + "tslib": true } }, - "string.prototype.matchall>es-abstract>is-callable": { + "readable-stream>util-deprecate": { "globals": { - "document": true + "console.trace": true, + "console.warn": true, + "localStorage": true } }, - "string.prototype.matchall>es-abstract>is-regex": { + "browserify>assert>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true, + "process": true + }, "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "browserify>assert>util>inherits": true, + "process": true } }, - "string.prototype.matchall>es-abstract>is-shared-array-buffer": { + "browserify>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true + }, "packages": { - "string.prototype.matchall>call-bind": true + "pumpify>inherits": true, + "browserify>util>is-arguments": true, + "koa>is-generator-function": true, + "browserify>util>is-typed-array": true, + "process": true, + "browserify>util>which-typed-array": true } }, - "string.prototype.matchall>es-abstract>object-inspect": { + "uuid": { "globals": { - "HTMLElement": true, - "WeakRef": true - }, - "packages": { - "browserify>browser-resolve": true + "crypto": true, + "msCrypto": true } }, - "string.prototype.matchall>get-intrinsic": { + "@metamask/eth-snap-keyring>uuid": { "globals": { - "AggregateError": true, - "FinalizationRegistry": true, - "WeakRef": true - }, - "packages": { - "browserify>has>function-bind": true, - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>has-proto": true, - "string.prototype.matchall>has-symbols": true + "crypto": true } }, - "string.prototype.matchall>internal-slot": { - "packages": { - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>side-channel": true + "@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": true + "eth-lattice-keyring>gridplus-sdk>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": { - "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "web3-stream-provider>uuid": { + "globals": { + "crypto": true } }, - "string.prototype.matchall>side-channel": { + "@metamask/snaps-utils>validate-npm-package-name": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>object-inspect": true, - "string.prototype.matchall>get-intrinsic": true + "@metamask/snaps-utils>validate-npm-package-name>builtins": true } }, - "superstruct": { - "globals": { - "console.warn": true, - "define": true + "react-markdown>vfile>vfile-message": { + "packages": { + "react-markdown>vfile>unist-util-stringify-position": true } }, - "terser>source-map-support>buffer-from": { + "react-markdown>vfile": { "packages": { - "browserify>buffer": true + "react-markdown>vfile>is-buffer": true, + "path-browserify": true, + "process": true, + "react-markdown>vfile>replace-ext": true, + "react-markdown>vfile>vfile-message": true } }, - "uri-js": { + "browserify>vm-browserify": { "globals": { - "define": true + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true } }, - "uuid": { + "react-popper>warning": { "globals": { - "crypto": true, - "msCrypto": true + "console": true } }, - "wait-on>rxjs": { + "@ensdomains/content-hash>multihashes>web-encoding": { "globals": { - "cancelAnimationFrame": true, - "clearInterval": true, - "clearTimeout": true, - "performance": true, - "requestAnimationFrame": true, - "setInterval.apply": true, - "setTimeout.apply": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>util": true } }, "web3": { @@ -5900,14 +5811,14 @@ "setTimeout": true }, "packages": { - "browserify>util": true, "readable-stream": true, + "browserify>util": true, "web3-stream-provider>uuid": true } }, - "web3-stream-provider>uuid": { + "@metamask/controllers>web3": { "globals": { - "crypto": true + "XMLHttpRequest": true } }, "webextension-polyfill": { @@ -5919,9 +5830,30 @@ "define": true } }, - "webpack>events": { - "globals": { - "console": true + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-bigint": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-boolean-object": true, + "@metamask/eth-token-tracker>deep-equal>which-boxed-primitive>is-number-object": true, + "eslint-plugin-react>array-includes>is-string": true, + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true + } + }, + "@metamask/eth-token-tracker>deep-equal>which-collection": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-map": true, + "@metamask/eth-token-tracker>deep-equal>es-get-iterator>is-set": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakmap": true, + "@metamask/eth-token-tracker>deep-equal>which-collection>is-weakset": true + } + }, + "browserify>util>which-typed-array": { + "packages": { + "string.prototype.matchall>es-abstract>available-typed-arrays": true, + "string.prototype.matchall>call-bind": true, + "browserify>util>which-typed-array>for-each": true, + "string.prototype.matchall>es-abstract>gopd": true, + "koa>is-generator-function>has-tostringtag": true } } } diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 5338922720ef..545922814d1f 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1,57 +1,25 @@ { "resources": { - "@babel/code-frame": { + "@babel/core>@ampproject/remapping": { "globals": { - "console.warn": true, - "process": true + "define": true }, "packages": { - "@babel/code-frame>@babel/highlight": true, - "postcss>picocolors": true + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true, + "terser-webpack-plugin>@jridgewell/trace-mapping": true } }, - "@babel/code-frame>@babel/highlight": { + "@babel/code-frame": { "globals": { + "console.warn": true, "process": true }, "packages": { - "@babel/code-frame>@babel/highlight>chalk": true, - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true, + "@babel/code-frame>@babel/helper-validator-identifier": true, "loose-envify>js-tokens": true, "postcss>picocolors": true } }, - "@babel/code-frame>@babel/highlight>chalk": { - "globals": { - "process.env.TERM": true, - "process.platform": true - }, - "packages": { - "@babel/code-frame>@babel/highlight>chalk>ansi-styles": true, - "@babel/code-frame>@babel/highlight>chalk>escape-string-regexp": true, - "@babel/code-frame>@babel/highlight>chalk>supports-color": true - } - }, - "@babel/code-frame>@babel/highlight>chalk>ansi-styles": { - "packages": { - "@metamask/jazzicon>color>color-convert": true - } - }, - "@babel/code-frame>@babel/highlight>chalk>supports-color": { - "builtin": { - "os.release": true - }, - "globals": { - "process.env": true, - "process.platform": true, - "process.stderr": true, - "process.stdout": true, - "process.versions.node.split": true - }, - "packages": { - "gulp-livereload>chalk>supports-color>has-flag": true - } - }, "@babel/core": { "builtin": { "assert": true, @@ -75,18 +43,13 @@ }, "packages": { "$root$": true, - "@babel/code-frame": true, "@babel/core>@ampproject/remapping": true, + "@babel/code-frame": true, "@babel/core>@babel/generator": true, "@babel/core>@babel/helper-compilation-targets": true, "@babel/core>@babel/helper-module-transforms": true, "@babel/core>@babel/helpers": true, "@babel/core>@babel/parser": true, - "@babel/core>@babel/template": true, - "@babel/core>@babel/types": true, - "@babel/core>convert-source-map": true, - "@babel/core>gensync": true, - "@babel/core>semver": true, "@babel/plugin-proposal-class-properties": true, "@babel/plugin-proposal-nullish-coalescing-operator": true, "@babel/plugin-proposal-object-rest-spread": true, @@ -96,18 +59,44 @@ "@babel/preset-env": true, "@babel/preset-react": true, "@babel/preset-typescript": true, + "@babel/core>@babel/template": true, "depcheck>@babel/traverse": true, + "@babel/core>@babel/types": true, + "@babel/core>convert-source-map": true, + "nock>debug": true, + "@babel/core>gensync": true, "depcheck>json5": true, - "nock>debug": true + "@babel/core>semver": true } }, - "@babel/core>@ampproject/remapping": { + "@babel/eslint-parser": { + "builtin": { + "module": true, + "path": true, + "worker_threads": true + }, "globals": { - "define": true + "__dirname": true, + "process.cwd": true, + "process.versions": true }, "packages": { - "terser-webpack-plugin>@jridgewell/trace-mapping": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true + "@babel/core": true, + "@babel/parser": true, + "@babel/core>@babel/parser": true, + "depcheck>@babel/parser": true, + "lavamoat>lavamoat-tofu>@babel/parser": true, + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals": true, + "eslint": true, + "@babel/eslint-parser>eslint-scope": true, + "@babel/eslint-parser>eslint-visitor-keys": true, + "@babel/eslint-parser>semver": true + } + }, + "@babel/eslint-plugin": { + "packages": { + "eslint": true, + "@babel/eslint-plugin>eslint-rule-composer": true } }, "@babel/core>@babel/generator": { @@ -116,15 +105,20 @@ "console.warn": true }, "packages": { - "@babel/core>@babel/generator>jsesc": true, "@babel/core>@babel/types": true, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true, "terser-webpack-plugin>@jridgewell/trace-mapping": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true + "@babel/core>@babel/generator>jsesc": true } }, - "@babel/core>@babel/generator>jsesc": { - "globals": { - "Buffer": true + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": { + "packages": { + "@babel/core>@babel/types": true + } + }, + "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor": { + "packages": { + "@babel/core>@babel/types": true } }, "@babel/core>@babel/helper-compilation-targets": { @@ -135,22 +129,66 @@ "process.versions.node": true }, "packages": { - "@babel/core>@babel/helper-compilation-targets>lru-cache": true, - "@babel/core>@babel/helper-compilation-targets>semver": true, "@babel/preset-env>@babel/compat-data": true, "@babel/preset-env>@babel/helper-validator-option": true, - "browserslist": true + "browserslist": true, + "@babel/core>@babel/helper-compilation-targets>lru-cache": true, + "@babel/core>@babel/helper-compilation-targets>semver": true } }, - "@babel/core>@babel/helper-compilation-targets>lru-cache": { + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": { + "globals": { + "console.warn": true + }, "packages": { - "@babel/core>@babel/helper-compilation-targets>lru-cache>yallist": true + "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-optimise-call-expression": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true, + "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true, + "depcheck>@babel/traverse": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin>semver": true } }, - "@babel/core>@babel/helper-compilation-targets>semver": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>semver": true + } + }, + "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": { + "builtin": { + "module": true, + "path": true + }, "globals": { - "console": true, - "process": true + "console.log": true, + "console.warn": true, + "process.exitCode": "write", + "process.versions.node": true + }, + "packages": { + "@babel/core": true, + "@babel/core>@babel/helper-compilation-targets": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider>lodash.debounce": true, + "depcheck>resolve": true + } + }, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": { + "packages": { + "@babel/core>@babel/types": true + } + }, + "@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": { + "builtin": { + "assert": true + }, + "packages": { + "@babel/core>@babel/types": true } }, "@babel/core>@babel/helper-module-transforms": { @@ -163,242 +201,112 @@ "@babel/core": true, "@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": true, "@babel/core>@babel/helper-module-transforms>@babel/helper-simple-access": true, - "depcheck>@babel/traverse": true, - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true + "@babel/code-frame>@babel/helper-validator-identifier": true, + "depcheck>@babel/traverse": true } }, - "@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": { - "builtin": { - "assert": true - }, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-optimise-call-expression": { "packages": { "@babel/core>@babel/types": true } }, + "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator>@babel/helper-wrap-function": true, + "depcheck>@babel/traverse": true + } + }, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-optimise-call-expression": true, + "depcheck>@babel/traverse": true + } + }, "@babel/core>@babel/helper-module-transforms>@babel/helper-simple-access": { "packages": { "@babel/core>@babel/types": true } }, - "@babel/core>@babel/helpers": { + "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": { + "packages": { + "@babel/core>@babel/types": true + } + }, + "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator>@babel/helper-wrap-function": { "packages": { "@babel/core>@babel/template": true, + "depcheck>@babel/traverse": true, "@babel/core>@babel/types": true } }, - "@babel/core>@babel/template": { + "@babel/core>@babel/helpers": { "packages": { - "@babel/code-frame": true, - "@babel/core>@babel/parser": true, + "@babel/core>@babel/template": true, "@babel/core>@babel/types": true } }, - "@babel/core>@babel/types": { - "globals": { - "console.warn": true, - "process.env": true - }, + "@babel/preset-env>@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "packages": { - "@babel/core>@babel/types>@babel/helper-string-parser": true, - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true + "@babel/preset-env>@babel/helper-plugin-utils": true, + "depcheck>@babel/traverse": true } }, - "@babel/core>convert-source-map": { - "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "value": true + "@babel/preset-env>@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/core>semver": { - "globals": { - "console": true, - "process": true + "@babel/preset-env>@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/eslint-parser": { - "builtin": { - "module": true, - "path": true, - "worker_threads": true - }, - "globals": { - "__dirname": true, - "process.cwd": true, - "process.versions": true - }, + "@babel/preset-env>@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "packages": { "@babel/core": true, - "@babel/core>@babel/parser": true, - "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals": true, - "@babel/eslint-parser>eslint-scope": true, - "@babel/eslint-parser>eslint-visitor-keys": true, - "@babel/eslint-parser>semver": true, - "@babel/parser": true, - "depcheck>@babel/parser": true, - "eslint": true, - "lavamoat>lavamoat-tofu>@babel/parser": true + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true, + "@babel/preset-env>@babel/plugin-transform-optional-chaining": true } }, - "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals": { + "@babel/preset-env>@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { "packages": { - "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals>eslint-scope": true + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "depcheck>@babel/traverse": true } }, - "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals>eslint-scope": { - "builtin": { - "assert": true - }, + "@babel/preset-env>@babel/plugin-syntax-import-assertions": { "packages": { - "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals>eslint-scope>estraverse": true, - "eslint>eslint-scope>esrecurse": true + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/eslint-parser>semver": { - "globals": { - "console": true, - "process": true + "@babel/preset-env>@babel/plugin-syntax-import-attributes": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/eslint-plugin": { + "@babel/preset-typescript>@babel/plugin-syntax-jsx": { "packages": { - "@babel/eslint-plugin>eslint-rule-composer": true, - "eslint": true + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/plugin-transform-logical-assignment-operators": { + "@babel/preset-typescript>@babel/plugin-transform-typescript>@babel/plugin-syntax-typescript": { "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-logical-assignment-operators": true + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/preset-env": { - "globals": { - "console.log": true, - "console.warn": true, - "process.cwd": true, - "process.env.BABEL_ENV": true - }, - "packages": { - "@babel/core>@babel/helper-compilation-targets": true, - "@babel/plugin-transform-logical-assignment-operators": true, - "@babel/preset-env>@babel/compat-data": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/helper-validator-option": true, - "@babel/preset-env>@babel/plugin-bugfix-firefox-class-in-computed-class-key": true, - "@babel/preset-env>@babel/plugin-bugfix-safari-class-field-initializer-scope": true, - "@babel/preset-env>@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": true, - "@babel/preset-env>@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": true, - "@babel/preset-env>@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": true, - "@babel/preset-env>@babel/plugin-syntax-import-assertions": true, - "@babel/preset-env>@babel/plugin-syntax-import-attributes": true, - "@babel/preset-env>@babel/plugin-syntax-unicode-sets-regex": true, - "@babel/preset-env>@babel/plugin-transform-arrow-functions": true, - "@babel/preset-env>@babel/plugin-transform-async-generator-functions": true, - "@babel/preset-env>@babel/plugin-transform-async-to-generator": true, - "@babel/preset-env>@babel/plugin-transform-block-scoped-functions": true, - "@babel/preset-env>@babel/plugin-transform-block-scoping": true, - "@babel/preset-env>@babel/plugin-transform-class-properties": true, - "@babel/preset-env>@babel/plugin-transform-class-static-block": true, - "@babel/preset-env>@babel/plugin-transform-classes": true, - "@babel/preset-env>@babel/plugin-transform-computed-properties": true, - "@babel/preset-env>@babel/plugin-transform-destructuring": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex": true, - "@babel/preset-env>@babel/plugin-transform-duplicate-keys": true, - "@babel/preset-env>@babel/plugin-transform-duplicate-named-capturing-groups-regex": true, - "@babel/preset-env>@babel/plugin-transform-dynamic-import": true, - "@babel/preset-env>@babel/plugin-transform-exponentiation-operator": true, - "@babel/preset-env>@babel/plugin-transform-export-namespace-from": true, - "@babel/preset-env>@babel/plugin-transform-for-of": true, - "@babel/preset-env>@babel/plugin-transform-function-name": true, - "@babel/preset-env>@babel/plugin-transform-json-strings": true, - "@babel/preset-env>@babel/plugin-transform-literals": true, - "@babel/preset-env>@babel/plugin-transform-member-expression-literals": true, - "@babel/preset-env>@babel/plugin-transform-modules-amd": true, - "@babel/preset-env>@babel/plugin-transform-modules-commonjs": true, - "@babel/preset-env>@babel/plugin-transform-modules-systemjs": true, - "@babel/preset-env>@babel/plugin-transform-modules-umd": true, - "@babel/preset-env>@babel/plugin-transform-named-capturing-groups-regex": true, - "@babel/preset-env>@babel/plugin-transform-new-target": true, - "@babel/preset-env>@babel/plugin-transform-nullish-coalescing-operator": true, - "@babel/preset-env>@babel/plugin-transform-numeric-separator": true, - "@babel/preset-env>@babel/plugin-transform-object-rest-spread": true, - "@babel/preset-env>@babel/plugin-transform-object-super": true, - "@babel/preset-env>@babel/plugin-transform-optional-catch-binding": true, - "@babel/preset-env>@babel/plugin-transform-optional-chaining": true, - "@babel/preset-env>@babel/plugin-transform-parameters": true, - "@babel/preset-env>@babel/plugin-transform-private-methods": true, - "@babel/preset-env>@babel/plugin-transform-private-property-in-object": true, - "@babel/preset-env>@babel/plugin-transform-property-literals": true, - "@babel/preset-env>@babel/plugin-transform-regenerator": true, - "@babel/preset-env>@babel/plugin-transform-reserved-words": true, - "@babel/preset-env>@babel/plugin-transform-shorthand-properties": true, - "@babel/preset-env>@babel/plugin-transform-spread": true, - "@babel/preset-env>@babel/plugin-transform-sticky-regex": true, - "@babel/preset-env>@babel/plugin-transform-template-literals": true, - "@babel/preset-env>@babel/plugin-transform-typeof-symbol": true, - "@babel/preset-env>@babel/plugin-transform-unicode-escapes": true, - "@babel/preset-env>@babel/plugin-transform-unicode-property-regex": true, - "@babel/preset-env>@babel/plugin-transform-unicode-regex": true, - "@babel/preset-env>@babel/plugin-transform-unicode-sets-regex": true, - "@babel/preset-env>@babel/preset-modules": true, - "@babel/preset-env>babel-plugin-polyfill-corejs2": true, - "@babel/preset-env>babel-plugin-polyfill-corejs3": true, - "@babel/preset-env>babel-plugin-polyfill-regenerator": true, - "@babel/preset-env>core-js-compat": true, - "@babel/preset-env>semver": true - } - }, - "@babel/preset-env>@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "depcheck>@babel/traverse": true - } - }, - "@babel/preset-env>@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true - } - }, - "@babel/preset-env>@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true - } - }, - "@babel/preset-env>@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true, - "@babel/preset-env>@babel/plugin-transform-optional-chaining": true - } - }, - "@babel/preset-env>@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "depcheck>@babel/traverse": true - } - }, - "@babel/preset-env>@babel/plugin-syntax-import-assertions": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true - } - }, - "@babel/preset-env>@babel/plugin-syntax-import-attributes": { + "@babel/preset-env>@babel/plugin-syntax-unicode-sets-regex": { "packages": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/preset-env>@babel/plugin-syntax-unicode-sets-regex": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true - } - }, "@babel/preset-env>@babel/plugin-transform-arrow-functions": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true @@ -420,21 +328,6 @@ "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator": true } }, - "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator>@babel/helper-wrap-function": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "depcheck>@babel/traverse": true - } - }, - "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator>@babel/helper-wrap-function": { - "packages": { - "@babel/core>@babel/template": true, - "@babel/core>@babel/types": true, - "depcheck>@babel/traverse": true - } - }, "@babel/preset-env>@babel/plugin-transform-block-scoped-functions": { "packages": { "@babel/core": true, @@ -449,55 +342,32 @@ }, "@babel/preset-env>@babel/plugin-transform-class-properties": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-class-static-block": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-classes": { "packages": { "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, "@babel/core>@babel/helper-compilation-targets": true, "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true, - "@babel/preset-env>@babel/plugin-transform-classes>globals": true, - "depcheck>@babel/traverse": true - } - }, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": { - "packages": { - "@babel/core>@babel/types": true - } - }, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-optimise-call-expression": true, - "depcheck>@babel/traverse": true - } - }, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": { - "packages": { - "@babel/core>@babel/types": true - } - }, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-optimise-call-expression": { - "packages": { - "@babel/core>@babel/types": true + "depcheck>@babel/traverse": true, + "@babel/preset-env>@babel/plugin-transform-classes>globals": true } }, "@babel/preset-env>@babel/plugin-transform-computed-properties": { "packages": { "@babel/core": true, - "@babel/core>@babel/template": true, - "@babel/preset-env>@babel/helper-plugin-utils": true + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/core>@babel/template": true } }, "@babel/preset-env>@babel/plugin-transform-destructuring": { @@ -508,55 +378,8 @@ }, "@babel/preset-env>@babel/plugin-transform-dotall-regex": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>semver": true - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": { - "globals": { - "characterClassItem.kind": true - }, - "packages": { - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsgen": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsparser": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-value-ecmascript": true - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": { - "globals": { - "define": true - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsgen": { - "globals": { - "define": true - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsparser": { - "globals": { - "regjsparser": "write" - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript": { - "packages": { - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript>unicode-canonical-property-names-ecmascript": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript>unicode-property-aliases-ecmascript": true - } - }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>semver": { - "globals": { - "console": true, - "process": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-duplicate-keys": { @@ -567,8 +390,8 @@ }, "@babel/preset-env>@babel/plugin-transform-duplicate-named-capturing-groups-regex": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-dynamic-import": { @@ -579,13 +402,8 @@ "@babel/preset-env>@babel/plugin-transform-exponentiation-operator": { "packages": { "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor": true - } - }, - "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor": { - "packages": { - "@babel/core>@babel/types": true + "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-export-namespace-from": { @@ -601,11 +419,6 @@ "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true } }, - "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": { - "packages": { - "@babel/core>@babel/types": true - } - }, "@babel/preset-env>@babel/plugin-transform-function-name": { "packages": { "@babel/core>@babel/helper-compilation-targets": true, @@ -623,6 +436,13 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/plugin-transform-logical-assignment-operators": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-logical-assignment-operators": true + } + }, "@babel/preset-env>@babel/plugin-transform-member-expression-literals": { "packages": { "@babel/core": true, @@ -640,8 +460,8 @@ "packages": { "@babel/core": true, "@babel/core>@babel/helper-module-transforms": true, - "@babel/core>@babel/helper-module-transforms>@babel/helper-simple-access": true, - "@babel/preset-env>@babel/helper-plugin-utils": true + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/core>@babel/helper-module-transforms>@babel/helper-simple-access": true } }, "@babel/preset-env>@babel/plugin-transform-modules-systemjs": { @@ -652,8 +472,8 @@ "@babel/core": true, "@babel/core>@babel/helper-module-transforms": true, "@babel/preset-env>@babel/helper-plugin-utils": true, - "depcheck>@babel/traverse": true, - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true + "@babel/code-frame>@babel/helper-validator-identifier": true, + "depcheck>@babel/traverse": true } }, "@babel/preset-env>@babel/plugin-transform-modules-umd": { @@ -669,8 +489,8 @@ }, "@babel/preset-env>@babel/plugin-transform-named-capturing-groups-regex": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-new-target": { @@ -725,41 +545,52 @@ }, "@babel/preset-env>@babel/plugin-transform-private-methods": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": { - "globals": { - "console.warn": true - }, + "@babel/preset-env>@babel/plugin-transform-private-property-in-object": { "packages": { - "@babel/core": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-optimise-call-expression": true, - "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin>semver": true, - "depcheck>@babel/traverse": true + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin>semver": { - "globals": { - "console": true, - "process": true + "@babel/preset-env>@babel/plugin-transform-property-literals": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/preset-env>@babel/plugin-transform-private-property-in-object": { + "@babel/preset-react>@babel/plugin-transform-react-display-name": { + "builtin": { + "path.basename": true, + "path.dirname": true, + "path.extname": true + }, "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true + } + }, + "@babel/preset-react>@babel/plugin-transform-react-jsx-development": { + "packages": { + "@babel/preset-react>@babel/plugin-transform-react-jsx": true + } + }, + "@babel/preset-react>@babel/plugin-transform-react-jsx": { + "packages": { + "@babel/core": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + "@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-typescript>@babel/plugin-syntax-jsx": true } }, - "@babel/preset-env>@babel/plugin-transform-property-literals": { + "@babel/preset-react>@babel/plugin-transform-react-pure-annotations": { "packages": { "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, "@babel/preset-env>@babel/helper-plugin-utils": true } }, @@ -769,15 +600,6 @@ "@babel/preset-env>@babel/plugin-transform-regenerator>regenerator-transform": true } }, - "@babel/preset-env>@babel/plugin-transform-regenerator>regenerator-transform": { - "builtin": { - "assert": true, - "util.inherits": true - }, - "packages": { - "@babel/runtime": true - } - }, "@babel/preset-env>@babel/plugin-transform-reserved-words": { "packages": { "@babel/core": true, @@ -815,6 +637,22 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-typescript>@babel/plugin-transform-typescript": { + "builtin": { + "assert": true + }, + "globals": { + "console.warn": true + }, + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true, + "@babel/preset-typescript>@babel/plugin-transform-typescript>@babel/plugin-syntax-typescript": true + } + }, "@babel/preset-env>@babel/plugin-transform-unicode-escapes": { "packages": { "@babel/core": true, @@ -823,77 +661,97 @@ }, "@babel/preset-env>@babel/plugin-transform-unicode-property-regex": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-unicode-regex": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, "@babel/preset-env>@babel/plugin-transform-unicode-sets-regex": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true - } - }, - "@babel/preset-env>babel-plugin-polyfill-corejs2": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/compat-data": true, - "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true, - "@babel/preset-env>babel-plugin-polyfill-corejs2>semver": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true, + "@babel/preset-env>@babel/helper-plugin-utils": true } }, - "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": { - "builtin": { - "module": true, - "path": true - }, + "@babel/preset-env": { "globals": { "console.log": true, "console.warn": true, - "process.exitCode": "write", - "process.versions.node": true + "process.cwd": true, + "process.env.BABEL_ENV": true }, "packages": { - "@babel/core": true, + "@babel/preset-env>@babel/compat-data": true, "@babel/core>@babel/helper-compilation-targets": true, "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider>lodash.debounce": true, - "depcheck>resolve": true - } - }, - "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider>lodash.debounce": { - "globals": { - "clearTimeout": true, - "setTimeout": true - } - }, - "@babel/preset-env>babel-plugin-polyfill-corejs2>semver": { - "globals": { - "console": true, - "process": true - } - }, - "@babel/preset-env>babel-plugin-polyfill-corejs3": { - "packages": { - "@babel/core": true, - "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true, - "@babel/preset-env>core-js-compat": true - } - }, - "@babel/preset-env>babel-plugin-polyfill-regenerator": { - "packages": { - "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true - } - }, - "@babel/preset-env>semver": { - "globals": { - "console": true, - "process": true + "@babel/preset-env>@babel/helper-validator-option": true, + "@babel/preset-env>@babel/plugin-bugfix-firefox-class-in-computed-class-key": true, + "@babel/preset-env>@babel/plugin-bugfix-safari-class-field-initializer-scope": true, + "@babel/preset-env>@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": true, + "@babel/preset-env>@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": true, + "@babel/preset-env>@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": true, + "@babel/preset-env>@babel/plugin-syntax-import-assertions": true, + "@babel/preset-env>@babel/plugin-syntax-import-attributes": true, + "@babel/preset-env>@babel/plugin-syntax-unicode-sets-regex": true, + "@babel/preset-env>@babel/plugin-transform-arrow-functions": true, + "@babel/preset-env>@babel/plugin-transform-async-generator-functions": true, + "@babel/preset-env>@babel/plugin-transform-async-to-generator": true, + "@babel/preset-env>@babel/plugin-transform-block-scoped-functions": true, + "@babel/preset-env>@babel/plugin-transform-block-scoping": true, + "@babel/preset-env>@babel/plugin-transform-class-properties": true, + "@babel/preset-env>@babel/plugin-transform-class-static-block": true, + "@babel/preset-env>@babel/plugin-transform-classes": true, + "@babel/preset-env>@babel/plugin-transform-computed-properties": true, + "@babel/preset-env>@babel/plugin-transform-destructuring": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex": true, + "@babel/preset-env>@babel/plugin-transform-duplicate-keys": true, + "@babel/preset-env>@babel/plugin-transform-duplicate-named-capturing-groups-regex": true, + "@babel/preset-env>@babel/plugin-transform-dynamic-import": true, + "@babel/preset-env>@babel/plugin-transform-exponentiation-operator": true, + "@babel/preset-env>@babel/plugin-transform-export-namespace-from": true, + "@babel/preset-env>@babel/plugin-transform-for-of": true, + "@babel/preset-env>@babel/plugin-transform-function-name": true, + "@babel/preset-env>@babel/plugin-transform-json-strings": true, + "@babel/preset-env>@babel/plugin-transform-literals": true, + "@babel/plugin-transform-logical-assignment-operators": true, + "@babel/preset-env>@babel/plugin-transform-member-expression-literals": true, + "@babel/preset-env>@babel/plugin-transform-modules-amd": true, + "@babel/preset-env>@babel/plugin-transform-modules-commonjs": true, + "@babel/preset-env>@babel/plugin-transform-modules-systemjs": true, + "@babel/preset-env>@babel/plugin-transform-modules-umd": true, + "@babel/preset-env>@babel/plugin-transform-named-capturing-groups-regex": true, + "@babel/preset-env>@babel/plugin-transform-new-target": true, + "@babel/preset-env>@babel/plugin-transform-nullish-coalescing-operator": true, + "@babel/preset-env>@babel/plugin-transform-numeric-separator": true, + "@babel/preset-env>@babel/plugin-transform-object-rest-spread": true, + "@babel/preset-env>@babel/plugin-transform-object-super": true, + "@babel/preset-env>@babel/plugin-transform-optional-catch-binding": true, + "@babel/preset-env>@babel/plugin-transform-optional-chaining": true, + "@babel/preset-env>@babel/plugin-transform-parameters": true, + "@babel/preset-env>@babel/plugin-transform-private-methods": true, + "@babel/preset-env>@babel/plugin-transform-private-property-in-object": true, + "@babel/preset-env>@babel/plugin-transform-property-literals": true, + "@babel/preset-env>@babel/plugin-transform-regenerator": true, + "@babel/preset-env>@babel/plugin-transform-reserved-words": true, + "@babel/preset-env>@babel/plugin-transform-shorthand-properties": true, + "@babel/preset-env>@babel/plugin-transform-spread": true, + "@babel/preset-env>@babel/plugin-transform-sticky-regex": true, + "@babel/preset-env>@babel/plugin-transform-template-literals": true, + "@babel/preset-env>@babel/plugin-transform-typeof-symbol": true, + "@babel/preset-env>@babel/plugin-transform-unicode-escapes": true, + "@babel/preset-env>@babel/plugin-transform-unicode-property-regex": true, + "@babel/preset-env>@babel/plugin-transform-unicode-regex": true, + "@babel/preset-env>@babel/plugin-transform-unicode-sets-regex": true, + "@babel/preset-env>@babel/preset-modules": true, + "@babel/preset-env>babel-plugin-polyfill-corejs2": true, + "@babel/preset-env>babel-plugin-polyfill-corejs3": true, + "@babel/preset-env>babel-plugin-polyfill-regenerator": true, + "@babel/preset-env>core-js-compat": true, + "@babel/preset-env>semver": true } }, "@babel/preset-react": { @@ -901,214 +759,252 @@ "@babel/preset-env>@babel/helper-plugin-utils": true, "@babel/preset-env>@babel/helper-validator-option": true, "@babel/preset-react>@babel/plugin-transform-react-display-name": true, - "@babel/preset-react>@babel/plugin-transform-react-jsx": true, "@babel/preset-react>@babel/plugin-transform-react-jsx-development": true, + "@babel/preset-react>@babel/plugin-transform-react-jsx": true, "@babel/preset-react>@babel/plugin-transform-react-pure-annotations": true } }, - "@babel/preset-react>@babel/plugin-transform-react-display-name": { - "builtin": { - "path.basename": true, - "path.dirname": true, - "path.extname": true - }, - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true - } - }, - "@babel/preset-react>@babel/plugin-transform-react-jsx": { - "packages": { - "@babel/core": true, - "@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-typescript>@babel/plugin-syntax-jsx": true - } - }, - "@babel/preset-react>@babel/plugin-transform-react-jsx-development": { - "packages": { - "@babel/preset-react>@babel/plugin-transform-react-jsx": true - } - }, - "@babel/preset-react>@babel/plugin-transform-react-pure-annotations": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true - } - }, "@babel/preset-typescript": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true, "@babel/preset-env>@babel/helper-validator-option": true, - "@babel/preset-env>@babel/plugin-transform-modules-commonjs": true, "@babel/preset-typescript>@babel/plugin-syntax-jsx": true, + "@babel/preset-env>@babel/plugin-transform-modules-commonjs": true, "@babel/preset-typescript>@babel/plugin-transform-typescript": true } }, - "@babel/preset-typescript>@babel/plugin-syntax-jsx": { + "@babel/core>@babel/template": { "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true + "@babel/code-frame": true, + "@babel/core>@babel/parser": true, + "@babel/core>@babel/types": true } }, - "@babel/preset-typescript>@babel/plugin-transform-typescript": { - "builtin": { - "assert": true - }, + "depcheck>@babel/traverse": { "globals": { - "console.warn": true + "console.log": true }, "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-env>@babel/plugin-transform-for-of>@babel/helper-skip-transparent-expression-wrappers": true, - "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, - "@babel/preset-typescript>@babel/plugin-transform-typescript>@babel/plugin-syntax-typescript": true - } - }, - "@babel/preset-typescript>@babel/plugin-transform-typescript>@babel/plugin-syntax-typescript": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true + "@babel/code-frame": true, + "@babel/core>@babel/generator": true, + "@babel/core>@babel/parser": true, + "@babel/core>@babel/template": true, + "@babel/core>@babel/types": true, + "babel/preset-env>b@babel/types": true, + "nock>debug": true, + "depcheck>@babel/traverse>globals": true } }, - "@babel/register>clone-deep>is-plain-object": { + "@babel/core>@babel/types": { + "globals": { + "console.warn": true, + "process.env": true + }, "packages": { - "gulp>gulp-cli>isobject": true + "@babel/core>@babel/types>@babel/helper-string-parser": true, + "@babel/code-frame>@babel/helper-validator-identifier": true } }, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": { + "sass-embedded>@bufbuild/protobuf": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "__values": true, + "process": true + } + }, + "ts-node>@cspotcode/source-map-support": { "builtin": { - "events.EventEmitter": true, - "util": true + "fs": true, + "path.isAbsolute": true, + "path.join": true, + "path.normalize": true, + "path.resolve": true, + "url.fileURLToPath": true, + "url.pathToFileURL": true, + "util.inspect": true }, "globals": { - "process.nextTick": true, - "process.stderr": true + "Buffer.from": true, + "URL": true, + "XMLHttpRequest": true, + "console.error": true, + "process": true }, "packages": { - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>are-we-there-yet": true, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge": true, - "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, - "nyc>yargs>set-blocking": true + "ts-node>@cspotcode/source-map-support>@jridgewell/trace-mapping": true } }, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>are-we-there-yet": { - "builtin": { - "events.EventEmitter": true, - "util.inherits": true - }, + "eslint-plugin-jsdoc>@es-joy/jsdoccomment": { "packages": { - "koa>delegates": true, - "readable-stream": true + "eslint-plugin-jsdoc>comment-parser": true, + "eslint>esquery": true, + "eslint-plugin-jsdoc>@es-joy/jsdoccomment>jsdoc-type-pratt-parser": true } }, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge": { + "eslint>@eslint-community/eslint-utils": { + "packages": { + "eslint>eslint-visitor-keys": true + } + }, + "eslint>@eslint/eslintrc": { "builtin": { - "util.format": true + "assert": true, + "fs": true, + "module": true, + "os": true, + "path": true, + "url": true, + "util": true }, "globals": { - "clearInterval": true, - "process": true, - "setImmediate": true, - "setInterval": true + "__filename": true, + "process.cwd": true, + "process.emitWarning": true, + "process.platform": true }, "packages": { - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>aproba": true, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": true, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": true, - "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, - "@storybook/react>@storybook/node-logger>npmlog>gauge>has-unicode": true, - "@storybook/react>@storybook/node-logger>npmlog>gauge>wide-align": true, - "nyc>signal-exit": true, - "react>object-assign": true - } - }, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": { - "packages": { - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": true, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": true, - "gulp>gulp-cli>yargs>string-width>code-point-at": true + "$root$": true, + "@babel/eslint-parser": true, + "@babel/eslint-plugin": true, + "@metamask/eslint-config": true, + "@metamask/eslint-config-nodejs": true, + "@metamask/eslint-config-typescript": true, + "@typescript-eslint/eslint-plugin": true, + "eslint>ajv": true, + "nock>debug": true, + "eslint": true, + "eslint-config-prettier": true, + "eslint-plugin-import": true, + "eslint-plugin-jsdoc": true, + "eslint-plugin-node": true, + "eslint-plugin-prettier": true, + "eslint-plugin-react": true, + "eslint-plugin-react-hooks": true, + "eslint>globals": true, + "eslint>ignore": true, + "eslint>minimatch": true, + "mocha>strip-json-comments": true } }, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": { + "gulp-sourcemaps>@gulp-sourcemaps/identity-map": { "packages": { - "gulp>gulp-cli>yargs>string-width>is-fullwidth-code-point>number-is-nan": true + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>acorn": true, + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>normalize-path": true, + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss": true, + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>source-map": true, + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>through2": true } }, - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": { + "gulp-sourcemaps>@gulp-sourcemaps/map-sources": { "packages": { - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi>ansi-regex": true + "gulp-watch>anymatch>normalize-path": true, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": true } }, - "@lavamoat/lavapack": { + "eslint>@humanwhocodes/config-array": { "builtin": { - "assert": true, - "buffer.Buffer.from": true, - "fs.promises.readFile": true, - "fs.promises.writeFile": true, - "fs.readFileSync": true, + "path.dirname": true, "path.join": true, "path.relative": true }, + "packages": { + "eslint>@humanwhocodes/config-array>@humanwhocodes/object-schema": true, + "nock>debug": true, + "eslint>minimatch": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": { "globals": { - "__dirname": true, - "__filename.slice": true, - "console.error": true, - "console.warn": true, - "process.cwd": true, - "setTimeout": true + "define": true }, "packages": { - "@lavamoat/lavapack>combine-source-map": true, - "@lavamoat/lavapack>convert-source-map": true, - "@lavamoat/lavapack>json-stable-stringify": true, - "@lavamoat/lavapack>umd": true, - "browserify>JSONStream": true, - "eslint>espree": true, - "lavamoat-viz>lavamoat-core": true, - "readable-stream": true, - "through2": true + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true, + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true, + "terser-webpack-plugin>@jridgewell/trace-mapping": true } }, - "@lavamoat/lavapack>combine-source-map": { - "builtin": { - "path.dirname": true, - "path.join": true - }, + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { "globals": { - "process.platform": true - }, + "define": true + } + }, + "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": { + "globals": { + "define": true + } + }, + "terser>@jridgewell/source-map": { "packages": { - "@lavamoat/lavapack>combine-source-map>inline-source-map": true, - "@lavamoat/lavapack>combine-source-map>lodash.memoize": true, - "@lavamoat/lavapack>combine-source-map>source-map": true, - "nyc>convert-source-map": true + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true, + "terser-webpack-plugin>@jridgewell/trace-mapping": true } }, - "@lavamoat/lavapack>combine-source-map>inline-source-map": { + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": { "globals": { - "Buffer.from": true + "Buffer": true, + "TextDecoder": true, + "define": true + } + }, + "ts-node>@cspotcode/source-map-support>@jridgewell/trace-mapping": { + "globals": { + "define": true }, "packages": { - "@lavamoat/lavapack>combine-source-map>inline-source-map>source-map": true + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true } }, - "@lavamoat/lavapack>convert-source-map": { + "terser-webpack-plugin>@jridgewell/trace-mapping": { "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "value": true + "define": true + }, + "packages": { + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, + "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true } }, - "@lavamoat/lavapack>json-stable-stringify": { + "lavamoat>@lavamoat/aa": { + "builtin": { + "node:fs.lstatSync": true, + "node:fs.readFileSync": true, + "node:fs.realpathSync": true, + "node:path.dirname": true, + "node:path.join": true, + "node:path.relative": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>isarray": true, - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "lavamoat>json-stable-stringify>jsonify": true, - "string.prototype.matchall>call-bind": true + "depcheck>resolve": true + } + }, + "@lavamoat/lavapack": { + "builtin": { + "node:assert": true, + "node:buffer.Buffer.from": true, + "node:fs.promises.readFile": true, + "node:fs.promises.writeFile": true, + "node:fs.readFileSync": true, + "node:path.join": true, + "node:path.relative": true + }, + "globals": { + "__dirname": true, + "__filename.slice": true, + "console.error": true, + "console.warn": true, + "process.cwd": true, + "setTimeout": true + }, + "packages": { + "browserify>JSONStream": true, + "@lavamoat/lavapack>combine-source-map": true, + "eslint>espree": true, + "@lavamoat/lavapack>json-stable-stringify": true, + "lavamoat>lavamoat-core": true, + "@lavamoat/lavapack>readable-stream": true, + "through2": true, + "@lavamoat/lavapack>umd": true } }, "@metamask/build-utils": { @@ -1124,37 +1020,16 @@ }, "packages": { "@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, "@noble/hashes": true, + "@metamask/utils>@scure/base": true, "nock>debug": true, + "@metamask/utils>pony-cause": true, "semver": true } }, - "@metamask/eth-token-tracker>deep-equal>is-date-object": { - "packages": { - "koa>is-generator-function>has-tostringtag": true - } - }, - "@metamask/jazzicon>color>clone": { - "globals": { - "Buffer": true - } - }, - "@metamask/jazzicon>color>color-convert": { - "packages": { - "@metamask/jazzicon>color>color-convert>color-name": true - } - }, - "@metamask/object-multiplex>once": { + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals": { "packages": { - "@metamask/object-multiplex>once>wrappy": true - } - }, - "@metamask/utils>@scure/base": { - "globals": { - "TextDecoder": true, - "TextEncoder": true + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals>eslint-scope": true } }, "@noble/hashes": { @@ -1163,93 +1038,120 @@ "crypto": true } }, - "@sentry/cli>which>isexe": { + "eslint>@nodelib/fs.walk>@nodelib/fs.scandir": { "builtin": { - "fs": true + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readdir": true, + "fs.readdirSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.sep": true }, "globals": { - "TESTING_WINDOWS": true, - "process.env.PATHEXT": true, - "process.getgid": true, - "process.getuid": true, - "process.platform": true - } - }, - "@storybook/addon-knobs>qs": { + "process.versions.node": true + }, "packages": { - "string.prototype.matchall>side-channel": true + "fast-glob>@nodelib/fs.stat": true, + "eslint>@nodelib/fs.walk>@nodelib/fs.scandir>run-parallel": true } }, - "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": { + "fast-glob>@nodelib/fs.stat": { "builtin": { - "os.homedir": true - }, - "globals": { - "process.env": true, - "process.getuid": true, - "process.platform": true + "fs.lstat": true, + "fs.lstatSync": true, + "fs.stat": true, + "fs.statSync": true } }, - "@storybook/react>@storybook/node-logger>npmlog>gauge>has-unicode": { + "eslint>@nodelib/fs.walk": { "builtin": { - "os.type": true + "events.EventEmitter": true, + "path.sep": true, + "stream.Readable": true }, "globals": { - "process.env.LANG": true, - "process.env.LC_ALL": true, - "process.env.LC_CTYPE": true + "setImmediate": true + }, + "packages": { + "eslint>@nodelib/fs.walk>@nodelib/fs.scandir": true, + "eslint>@nodelib/fs.walk>fastq": true } }, - "@storybook/react>@storybook/node-logger>npmlog>gauge>wide-align": { - "packages": { - "yargs>string-width": true + "@metamask/utils>@scure/base": { + "globals": { + "TextDecoder": true, + "TextEncoder": true } }, - "@storybook/react>acorn-walk": { + "stylelint>@stylelint/postcss-css-in-js": { "globals": { - "define": true + "__dirname": true + }, + "packages": { + "@babel/core": true, + "stylelint>postcss-syntax": true, + "stylelint>postcss": true } }, - "@storybook/test-runner>jest-circus>p-limit": { + "stylelint>@stylelint/postcss-markdown": { "packages": { - "@storybook/test-runner>jest-circus>p-limit>yocto-queue": true + "stylelint>postcss-html": true, + "stylelint>postcss-syntax": true, + "stylelint>@stylelint/postcss-markdown>remark": true, + "stylelint>@stylelint/postcss-markdown>unist-util-find-all-after": true } }, "@typescript-eslint/eslint-plugin": { "packages": { + "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils": true, "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": true, - "@typescript-eslint/eslint-plugin>tsutils": true, - "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, - "eslint": true, "eslint-plugin-jest>@typescript-eslint/utils": true, "eslint>debug": true, - "eslint>regexpp": true, + "eslint": true, "globby>ignore": true, + "eslint>regexpp": true, "semver": true, + "@typescript-eslint/eslint-plugin>tsutils": true, "typescript": true } }, + "eslint-plugin-jest>@typescript-eslint/experimental-utils": { + "builtin": { + "path": true + }, + "packages": { + "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, + "eslint-plugin-jest>@typescript-eslint/experimental-utils>@typescript-eslint/types": true, + "@typescript-eslint/parser>@typescript-eslint/types": true, + "eslint": true, + "eslint>eslint-scope": true, + "webpack>eslint-scope": true, + "eslint-plugin-jest>@typescript-eslint/experimental-utils>eslint-utils": true, + "eslint>eslint-utils": true + } + }, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": { + "builtin": { + "path": true + }, + "packages": { + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": true + } + }, "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils": { "packages": { - "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug": true, "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": true, - "@typescript-eslint/eslint-plugin>tsutils": true, "eslint-plugin-jest>@typescript-eslint/utils": true, + "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug": true, "eslint>debug": true, "nock>debug": true, + "@typescript-eslint/eslint-plugin>tsutils": true, "typescript": true } }, - "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug": { - "globals": { - "console.debug": true, - "console.log": true - }, - "packages": { - "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug>ms": true - } - }, "@typescript-eslint/eslint-plugin>@typescript-eslint/utils": { "builtin": { "path": true @@ -1259,95 +1161,50 @@ "@typescript-eslint/parser>@typescript-eslint/types": true, "@typescript-eslint/utils": true, "eslint": true, - "eslint-plugin-mocha>eslint-utils": true, + "webpack>eslint-scope": true, "eslint>eslint-utils": true, - "webpack>eslint-scope": true - } - }, - "autoprefixer": { - "globals": { - "console": true, - "process.cwd": true, - "process.env.AUTOPREFIXER_GRID": true - }, - "packages": { - "autoprefixer>caniuse-lite": true, - "autoprefixer>fraction.js": true, - "autoprefixer>normalize-range": true, - "browserslist": true, - "postcss": true, - "postcss>picocolors": true, - "stylelint>postcss-value-parser": true + "eslint-plugin-mocha>eslint-utils": true } }, - "babelify": { + "eslint-plugin-jest>@typescript-eslint/utils": { "builtin": { - "path.extname": true, - "path.resolve": true, - "stream.PassThrough": true, - "stream.Transform": true, - "util": true + "assert": true, + "path": true }, "globals": { - "Buffer.concat": true + "afterAll": true, + "process.cwd": true }, "packages": { - "@babel/core": true - } - }, - "bify-module-groups": { - "packages": { - "bify-module-groups>through2": true, - "pify": true, - "pumpify>pump": true + "eslint>@eslint-community/eslint-utils": true, + "eslint>@eslint-community": true, + "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": true, + "eslint-plugin-jest>@typescript-eslint/experimental-utils>@typescript-eslint/types": true, + "@typescript-eslint/parser>@typescript-eslint/types": true, + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true, + "@typescript-eslint/parser>@typescript-eslint": true, + "eslint": true, + "eslint-plugin-jest>@typescript-eslint/utils>eslint-scope": true, + "eslint>eslint-scope": true, + "webpack>eslint-scope": true, + "eslint-plugin-jest>@typescript-eslint/utils>webpack>eslint-scope": true, + "eslint>eslint-utils": true, + "eslint-plugin-mocha>eslint-utils": true, + "semver": true } }, - "bify-module-groups>through2": { + "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": { "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true + "path": true }, "packages": { - "readable-stream": true + "eslint>eslint-visitor-keys": true } }, - "browserify": { - "builtin": { - "events.EventEmitter": true, - "fs.realpath": true, - "path.dirname": true, - "path.join": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true - }, + "eslint>@ungap/structured-clone": { "globals": { - "__dirname": true, - "process.cwd": true, - "process.nextTick": true, - "process.platform": true - }, - "packages": { - "browserify>browser-pack": true, - "browserify>browser-resolve": true, - "browserify>cached-path-relative": true, - "browserify>concat-stream": true, - "browserify>deps-sort": true, - "browserify>has": true, - "browserify>insert-module-globals": true, - "browserify>module-deps": true, - "browserify>read-only-stream": true, - "browserify>shasum-object": true, - "browserify>syntax-error": true, - "browserify>through2": true, - "depcheck>resolve": true, - "labeled-stream-splicer": true, - "lavamoat>htmlescape": true, - "pumpify>inherits": true, - "watchify>defined": true, - "watchify>xtend": true + "structuredClone": true } }, "browserify>JSONStream": { @@ -1359,585 +1216,441 @@ "debounce-stream>through": true } }, - "browserify>JSONStream>jsonparse": { - "globals": { - "Buffer": true + "@lavamoat/lavapack>readable-stream>abort-controller": { + "packages": { + "@lavamoat/lavapack>readable-stream>abort-controller>event-target-shim": true } }, - "browserify>browser-pack": { - "builtin": { - "fs.readFileSync": true, - "path.join": true, - "path.relative": true - }, - "globals": { - "__dirname": true, - "process.cwd": true - }, + "eslint>espree>acorn-jsx": { "packages": { - "@lavamoat/lavapack>combine-source-map": true, - "@lavamoat/lavapack>umd": true, - "browserify>JSONStream": true, - "browserify>browser-pack>through2": true, - "koa>content-disposition>safe-buffer": true, - "watchify>defined": true + "jsdom>acorn": true } }, - "browserify>browser-pack>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, + "browserify>syntax-error>acorn-node": { "packages": { - "browserify>browser-pack>through2>readable-stream": true, + "@storybook/react>acorn-walk": true, + "browserify>syntax-error>acorn-node>acorn": true, "watchify>xtend": true } }, - "browserify>browser-pack>through2>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, + "@storybook/react>acorn-walk": { "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "browserify>browser-pack>through2>readable-stream>isarray": true, - "browserify>browser-pack>through2>readable-stream>safe-buffer": true, - "browserify>browser-pack>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "define": true } }, - "browserify>browser-pack>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "ts-node>acorn-walk": { + "globals": { + "define": true } }, - "browserify>browser-pack>through2>readable-stream>string_decoder": { - "packages": { - "browserify>browser-pack>through2>readable-stream>string_decoder>safe-buffer": true + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>acorn": { + "globals": { + "define": true } }, - "browserify>browser-pack>through2>readable-stream>string_decoder>safe-buffer": { - "builtin": { - "buffer": true + "browserify>syntax-error>acorn-node>acorn": { + "globals": { + "define": true } }, - "browserify>browser-resolve": { - "builtin": { - "fs.readFile": true, - "fs.readFileSync": true, - "path": true - }, + "gulp-sourcemaps>acorn": { "globals": { - "__dirname": true, - "process.platform": true - }, - "packages": { - "depcheck>resolve": true + "define": true } }, - "browserify>cached-path-relative": { - "builtin": { - "path": true - }, + "jsdom>acorn": { "globals": { - "process.cwd": true + "console": true, + "define": true } }, - "browserify>concat-stream": { - "globals": { - "Buffer.concat": true, - "Buffer.isBuffer": true - }, + "del>p-map>aggregate-error": { "packages": { - "browserify>concat-stream>readable-stream": true, - "browserify>concat-stream>typedarray": true, - "pumpify>inherits": true, - "terser>source-map-support>buffer-from": true + "del>p-map>aggregate-error>clean-stack": true, + "prettier-eslint>indent-string": true } }, - "browserify>concat-stream>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, + "eslint>ajv": { "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "console": true }, "packages": { - "browserify>concat-stream>readable-stream>isarray": true, - "browserify>concat-stream>readable-stream>safe-buffer": true, - "browserify>concat-stream>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "eslint>fast-deep-equal": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "eslint>ajv>json-schema-traverse": true, + "uri-js": true } }, - "browserify>concat-stream>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "gulp-watch>ansi-colors": { + "packages": { + "fancy-log>ansi-gray>ansi-wrap": true } }, - "browserify>concat-stream>readable-stream>string_decoder": { + "fancy-log>ansi-gray": { "packages": { - "browserify>concat-stream>readable-stream>safe-buffer": true + "fancy-log>ansi-gray>ansi-wrap": true } }, - "browserify>deps-sort": { + "chalk>ansi-styles": { "packages": { - "browserify>deps-sort>through2": true, - "browserify>shasum-object": true + "chalk>ansi-styles>color-convert": true } }, - "browserify>deps-sort>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, + "gulp-livereload>chalk>ansi-styles": { "packages": { - "browserify>deps-sort>through2>readable-stream": true, - "watchify>xtend": true + "gulp-livereload>chalk>ansi-styles>color-convert": true } }, - "browserify>deps-sort>through2>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, + "stylelint>table>slice-ansi>ansi-styles": { "packages": { - "browserify>deps-sort>through2>readable-stream>isarray": true, - "browserify>deps-sort>through2>readable-stream>safe-buffer": true, - "browserify>deps-sort>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "stylelint>table>slice-ansi>ansi-styles>color-convert": true } }, - "browserify>deps-sort>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "chokidar>anymatch": { + "packages": { + "chokidar>anymatch>normalize-path": true, + "chokidar>anymatch>picomatch": true } }, - "browserify>deps-sort>through2>readable-stream>string_decoder": { + "gulp>glob-watcher>anymatch": { + "builtin": { + "path.sep": true + }, "packages": { - "browserify>deps-sort>through2>readable-stream>safe-buffer": true + "gulp>glob-watcher>anymatch>micromatch": true, + "gulp-watch>anymatch>normalize-path": true } }, - "browserify>duplexer2": { + "gulp-watch>anymatch": { + "builtin": { + "path.sep": true + }, "packages": { - "browserify>duplexer2>readable-stream": true + "gulp-watch>anymatch>micromatch": true, + "gulp-watch>anymatch>normalize-path": true } }, - "browserify>duplexer2>readable-stream": { + "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "os.EOL": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer": true }, "packages": { - "browserify>duplexer2>readable-stream>isarray": true, - "browserify>duplexer2>readable-stream>safe-buffer": true, - "browserify>duplexer2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "gulp>vinyl-fs>vinyl-sourcemap>append-buffer>buffer-equal": true } }, - "browserify>duplexer2>readable-stream>safe-buffer": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>are-we-there-yet": { "builtin": { - "buffer": true + "events.EventEmitter": true, + "util.inherits": true + }, + "packages": { + "koa>delegates": true, + "readable-stream": true } }, - "browserify>duplexer2>readable-stream>string_decoder": { - "packages": { - "browserify>duplexer2>readable-stream>safe-buffer": true + "ts-node>arg": { + "globals": { + "process.argv.slice": true } }, - "browserify>has": { + "gulp-watch>anymatch>micromatch>arr-diff": { "packages": { - "browserify>has>function-bind": true + "gulp>undertaker>arr-flatten": true } }, - "browserify>insert-module-globals": { - "builtin": { - "path.dirname": true, - "path.isAbsolute": true, - "path.relative": true, - "path.sep": true - }, - "globals": { - "Buffer.concat": true, - "Buffer.isBuffer": true - }, + "gulp>undertaker>bach>arr-filter": { "packages": { - "@lavamoat/lavapack>combine-source-map": true, - "browserify>insert-module-globals>through2": true, - "browserify>insert-module-globals>undeclared-identifiers": true, - "browserify>syntax-error>acorn-node": true, - "gulp-watch>path-is-absolute": true, - "watchify>xtend": true + "gulp>undertaker>arr-map>make-iterator": true } }, - "browserify>insert-module-globals>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, + "gulp>undertaker>arr-map": { "packages": { - "browserify>insert-module-globals>through2>readable-stream": true, - "watchify>xtend": true + "gulp>undertaker>arr-map>make-iterator": true } }, - "browserify>insert-module-globals>through2>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, + "eslint-plugin-react>array-includes": { "packages": { - "browserify>insert-module-globals>through2>readable-stream>isarray": true, - "browserify>insert-module-globals>through2>readable-stream>safe-buffer": true, - "browserify>insert-module-globals>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>es-abstract": true, + "string.prototype.matchall>get-intrinsic": true, + "eslint-plugin-react>array-includes>is-string": true } }, - "browserify>insert-module-globals>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "gulp>undertaker>bach>array-initial": { + "packages": { + "gulp>undertaker>object.defaults>array-slice": true, + "gulp>undertaker>bach>array-last>is-number": true } }, - "browserify>insert-module-globals>through2>readable-stream>string_decoder": { + "gulp>undertaker>bach>array-last": { "packages": { - "browserify>insert-module-globals>through2>readable-stream>safe-buffer": true + "gulp>undertaker>bach>array-last>is-number": true } }, - "browserify>insert-module-globals>undeclared-identifiers": { + "eslint-plugin-import>array.prototype.flat": { "packages": { - "browserify>insert-module-globals>undeclared-identifiers>get-assigned-identifiers": true, - "browserify>syntax-error>acorn-node": true, - "watchify>xtend": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>es-abstract": true, + "eslint-plugin-react>array.prototype.flatmap>es-shim-unscopables": true } }, - "browserify>insert-module-globals>undeclared-identifiers>get-assigned-identifiers": { - "builtin": { - "assert.equal": true + "eslint-plugin-react>array.prototype.flatmap": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>es-abstract": true, + "eslint-plugin-react>array.prototype.flatmap>es-shim-unscopables": true } }, - "browserify>module-deps": { - "builtin": { - "fs.createReadStream": true, - "fs.readFile": true, - "path.delimiter": true, - "path.dirname": true, - "path.join": true, - "path.resolve": true - }, + "gulp>glob-watcher>async-done": { "globals": { - "process.cwd": true, - "process.env.NODE_PATH": true, - "process.nextTick": true, - "process.platform": true, - "setTimeout": true, - "tr": true + "process.nextTick": true }, "packages": { - "browserify>browser-resolve": true, - "browserify>cached-path-relative": true, - "browserify>concat-stream": true, - "browserify>duplexer2": true, - "browserify>module-deps>detective": true, - "browserify>module-deps>readable-stream": true, - "browserify>module-deps>stream-combiner2": true, - "browserify>module-deps>through2": true, - "browserify>parents": true, - "depcheck>resolve": true, - "loose-envify": true, - "pumpify>inherits": true, - "watchify>defined": true, - "watchify>xtend": true + "duplexify>end-of-stream": true, + "@metamask/object-multiplex>once": true, + "readable-stream-2>process-nextick-args": true, + "gulp>glob-watcher>async-done>stream-exhaust": true } }, - "browserify>module-deps>detective": { + "gulp>undertaker>bach>async-settle": { "packages": { - "browserify>syntax-error>acorn-node": true, - "watchify>defined": true + "gulp>glob-watcher>async-done": true } }, - "browserify>module-deps>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "gulp-sourcemaps>css>source-map-resolve>atob": { + "globals": { + "Buffer.from": true + } + }, + "autoprefixer": { + "globals": { + "console": true, + "process.cwd": true, + "process.env.AUTOPREFIXER_GRID": true }, + "packages": { + "browserslist": true, + "autoprefixer>caniuse-lite": true, + "autoprefixer>fraction.js": true, + "autoprefixer>normalize-range": true, + "postcss>picocolors": true, + "postcss": true, + "stylelint>postcss-value-parser": true + } + }, + "stylelint>autoprefixer": { "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "console": true, + "process.cwd": true, + "process.env.AUTOPREFIXER_GRID": true }, "packages": { - "browserify>module-deps>readable-stream>isarray": true, - "browserify>module-deps>readable-stream>safe-buffer": true, - "browserify>module-deps>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "browserslist": true, + "autoprefixer>caniuse-lite": true, + "autoprefixer>normalize-range": true, + "stylelint>autoprefixer>num2fraction": true, + "stylelint>postcss>picocolors": true, + "stylelint>postcss-value-parser": true, + "stylelint>postcss": true } }, - "browserify>module-deps>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "@babel/preset-env>babel-plugin-polyfill-corejs2": { + "packages": { + "@babel/preset-env>@babel/compat-data": true, + "@babel/core": true, + "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true, + "@babel/preset-env>babel-plugin-polyfill-corejs2>semver": true } }, - "browserify>module-deps>readable-stream>string_decoder": { + "@babel/preset-env>babel-plugin-polyfill-corejs3": { "packages": { - "browserify>module-deps>readable-stream>safe-buffer": true + "@babel/core": true, + "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true, + "@babel/preset-env>core-js-compat": true } }, - "browserify>module-deps>stream-combiner2": { + "@babel/preset-env>babel-plugin-polyfill-regenerator": { "packages": { - "browserify>duplexer2": true, - "browserify>module-deps>stream-combiner2>readable-stream": true + "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true } }, - "browserify>module-deps>stream-combiner2>readable-stream": { + "babelify": { "builtin": { - "events.EventEmitter": true, - "stream": true, + "path.extname": true, + "path.resolve": true, + "stream.PassThrough": true, + "stream.Transform": true, "util": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer.concat": true }, "packages": { - "browserify>module-deps>stream-combiner2>readable-stream>isarray": true, - "browserify>module-deps>stream-combiner2>readable-stream>safe-buffer": true, - "browserify>module-deps>stream-combiner2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "@babel/core": true } }, - "browserify>module-deps>stream-combiner2>readable-stream>safe-buffer": { + "gulp>undertaker>bach": { "builtin": { - "buffer": true - } - }, - "browserify>module-deps>stream-combiner2>readable-stream>string_decoder": { + "assert.ok": true + }, "packages": { - "browserify>module-deps>stream-combiner2>readable-stream>safe-buffer": true + "gulp>undertaker>bach>arr-filter": true, + "gulp>undertaker>arr-flatten": true, + "gulp>undertaker>arr-map": true, + "gulp>undertaker>bach>array-each": true, + "gulp>undertaker>bach>array-initial": true, + "gulp>undertaker>bach>array-last": true, + "gulp>glob-watcher>async-done": true, + "gulp>undertaker>bach>async-settle": true, + "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true } }, - "browserify>module-deps>through2": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base": { "builtin": { "util.inherits": true }, - "globals": { - "process.nextTick": true - }, "packages": { - "browserify>module-deps>readable-stream": true, - "watchify>xtend": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>component-emitter": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>define-property": true, + "gulp>gulp-cli>isobject": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>pascalcase": true } }, - "browserify>parents": { - "globals": { - "process.cwd": true, - "process.platform": true + "bify-module-groups": { + "packages": { + "pify": true, + "pumpify>pump": true, + "bify-module-groups>through2": true + } + }, + "vinyl-buffer>bl": { + "builtin": { + "util.inherits": true }, "packages": { - "browserify>parents>path-platform": true + "vinyl-buffer>bl>readable-stream": true, + "koa>content-disposition>safe-buffer": true } }, - "browserify>parents>path-platform": { + "gulp-livereload>tiny-lr>body": { "builtin": { - "path": true, - "util.isObject": true, - "util.isString": true + "querystring.parse": true }, - "globals": { - "process.cwd": true, - "process.env": true, - "process.platform": true + "packages": { + "gulp-livereload>tiny-lr>body>continuable-cache": true, + "gulp-livereload>tiny-lr>body>error": true, + "gulp-livereload>tiny-lr>body>raw-body": true, + "gulp-livereload>tiny-lr>body>safe-json-parse": true } }, - "browserify>read-only-stream": { + "eslint>minimatch>brace-expansion": { "packages": { - "browserify>read-only-stream>readable-stream": true + "stylelint>balanced-match": true, + "eslint>minimatch>brace-expansion>concat-map": true } }, - "browserify>read-only-stream>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, + "chokidar>braces": { "packages": { - "browserify>read-only-stream>readable-stream>isarray": true, - "browserify>read-only-stream>readable-stream>safe-buffer": true, - "browserify>read-only-stream>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "chokidar>braces>fill-range": true } }, - "browserify>read-only-stream>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "gulp>glob-watcher>anymatch>micromatch>braces": { + "packages": { + "gulp>undertaker>arr-flatten": true, + "gulp>gulp-cli>matchdep>micromatch>array-unique": true, + "gulp>glob-watcher>anymatch>micromatch>braces>extend-shallow": true, + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range": true, + "gulp>gulp-cli>isobject": true, + "gulp-watch>anymatch>micromatch>braces>repeat-element": true, + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, + "gulp>gulp-cli>matchdep>micromatch>braces>split-string": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex": true } }, - "browserify>read-only-stream>readable-stream>string_decoder": { + "gulp-watch>anymatch>micromatch>braces": { "packages": { - "browserify>read-only-stream>readable-stream>safe-buffer": true + "gulp-watch>anymatch>micromatch>braces>expand-range": true, + "gulp-watch>anymatch>micromatch>braces>preserve": true, + "gulp-watch>anymatch>micromatch>braces>repeat-element": true } }, - "browserify>readable-stream": { + "browserify>browser-pack": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "fs.readFileSync": true, + "path.join": true, + "path.relative": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "__dirname": true, + "process.cwd": true }, "packages": { - "browserify>readable-stream>isarray": true, - "browserify>readable-stream>safe-buffer": true, - "browserify>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "browserify>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "browserify>readable-stream>string_decoder": { - "packages": { - "browserify>readable-stream>safe-buffer": true + "browserify>JSONStream": true, + "@lavamoat/lavapack>combine-source-map": true, + "watchify>defined": true, + "koa>content-disposition>safe-buffer": true, + "browserify>browser-pack>through2": true, + "@lavamoat/lavapack>umd": true } }, - "browserify>shasum-object": { + "browserify>browser-resolve": { "builtin": { - "crypto.createHash": true + "fs.readFile": true, + "fs.readFileSync": true, + "path": true }, "globals": { - "Buffer.isBuffer": true + "__dirname": true, + "process.platform": true }, "packages": { - "@metamask/rpc-errors>fast-safe-stringify": true - } - }, - "browserify>string_decoder": { - "packages": { - "koa>content-disposition>safe-buffer": true - } - }, - "browserify>syntax-error": { - "packages": { - "browserify>syntax-error>acorn-node": true - } - }, - "browserify>syntax-error>acorn-node": { - "packages": { - "@storybook/react>acorn-walk": true, - "browserify>syntax-error>acorn-node>acorn": true, - "watchify>xtend": true - } - }, - "browserify>syntax-error>acorn-node>acorn": { - "globals": { - "define": true + "depcheck>resolve": true } }, - "browserify>through2": { + "browserify": { "builtin": { - "util.inherits": true + "events.EventEmitter": true, + "fs.realpath": true, + "path.dirname": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true }, "globals": { - "process.nextTick": true + "__dirname": true, + "process.cwd": true, + "process.nextTick": true, + "process.platform": true }, "packages": { - "browserify>readable-stream": true, + "browserify>browser-pack": true, + "browserify>browser-resolve": true, + "browserify>cached-path-relative": true, + "browserify>concat-stream": true, + "watchify>defined": true, + "browserify>deps-sort": true, + "browserify>has": true, + "lavamoat>htmlescape": true, + "pumpify>inherits": true, + "browserify>insert-module-globals": true, + "labeled-stream-splicer": true, + "browserify>module-deps": true, + "browserify>read-only-stream": true, + "depcheck>resolve": true, + "browserify>shasum-object": true, + "browserify>syntax-error": true, + "browserify>through2": true, "watchify>xtend": true } }, @@ -1963,38 +1676,84 @@ "browserslist>node-releases": true } }, - "chalk": { - "packages": { - "chalk>ansi-styles": true, - "chalk>supports-color": true + "sass-embedded>buffer-builder": { + "globals": { + "Buffer": true } }, - "chalk>ansi-styles": { - "packages": { - "chalk>ansi-styles>color-convert": true + "gulp-zip>yazl>buffer-crc32": { + "builtin": { + "buffer.Buffer": true } }, - "chalk>ansi-styles>color-convert": { + "gulp>vinyl-fs>vinyl-sourcemap>append-buffer>buffer-equal": { + "builtin": { + "buffer.Buffer.isBuffer": true + } + }, + "terser>source-map-support>buffer-from": { + "globals": { + "Buffer": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base": { "packages": { - "jest-canvas-mock>moo-color>color-name": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>component-emitter": true, + "gulp>gulp-cli>array-sort>get-value": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value": true, + "gulp>gulp-cli>isobject": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>union-value": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value": true } }, - "chalk>supports-color": { + "browserify>cached-path-relative": { "builtin": { - "os.release": true, - "tty.isatty": true + "path": true }, "globals": { - "process.env": true, + "process.cwd": true + } + }, + "string.prototype.matchall>call-bind": { + "packages": { + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>call-bind>set-function-length": true + } + }, + "chalk": { + "packages": { + "chalk>ansi-styles": true, + "chalk>supports-color": true + } + }, + "gulp-livereload>chalk": { + "globals": { + "process.env.TERM": true, "process.platform": true }, "packages": { - "chalk>supports-color>has-flag": true + "gulp-livereload>chalk>ansi-styles": true, + "gulp-livereload>chalk>escape-string-regexp": true, + "gulp-livereload>chalk>supports-color": true } }, - "chalk>supports-color>has-flag": { + "postcss-discard-font-face>postcss>chalk": { "globals": { - "process.argv": true + "process.env.TERM": true, + "process.platform": true + }, + "packages": { + "postcss-discard-font-face>postcss>chalk>ansi-styles": true, + "postcss-discard-font-face>postcss>chalk>escape-string-regexp": true, + "prettier-eslint>loglevel-colored-level-prefix>chalk>has-ansi": true, + "postcss-discard-font-face>postcss>chalk>strip-ansi": true, + "postcss-discard-font-face>postcss>chalk>supports-color": true } }, "chokidar": { @@ -2036,423 +1795,376 @@ "chokidar>anymatch": true, "chokidar>braces": true, "chokidar>fsevents": true, + "eslint>glob-parent": true, "chokidar>is-binary-path": true, - "chokidar>normalize-path": true, - "chokidar>readdirp": true, "del>is-glob": true, - "eslint>glob-parent": true + "chokidar>normalize-path": true, + "chokidar>readdirp": true } }, - "chokidar>anymatch": { + "gulp>glob-watcher>chokidar": { "packages": { - "chokidar>anymatch>normalize-path": true, - "chokidar>anymatch>picomatch": true + "gulp>glob-watcher>chokidar>fsevents": true } }, - "chokidar>anymatch>picomatch": { + "gulp-watch>chokidar": { "builtin": { + "events.EventEmitter": true, + "fs": true, "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, "path.sep": true }, "globals": { + "clearTimeout": true, + "console.error": true, + "process.env.CHOKIDAR_INTERVAL": true, + "process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR": true, + "process.env.CHOKIDAR_USEPOLLING": true, + "process.nextTick": true, "process.platform": true, - "process.version.slice": true - } - }, - "chokidar>braces": { + "setTimeout": true + }, "packages": { - "chokidar>braces>fill-range": true + "gulp-watch>chokidar>anymatch": true, + "gulp-watch>chokidar>async-each": true, + "gulp-watch>chokidar>braces": true, + "gulp-watch>chokidar>fsevents": true, + "gulp-watch>glob-parent": true, + "pumpify>inherits": true, + "gulp-watch>chokidar>is-binary-path": true, + "eslint>is-glob": true, + "chokidar>normalize-path": true, + "gulp-watch>path-is-absolute": true, + "gulp-watch>chokidar>readdirp": true, + "gulp-watch>chokidar>upath": true } }, - "chokidar>braces>fill-range": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils": { "builtin": { - "util.inspect": true + "util": true }, "packages": { - "chokidar>braces>fill-range>to-regex-range": true + "gulp-zip>plugin-error>arr-union": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true, + "gulp>gulp-cli>isobject": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend": true } }, - "chokidar>braces>fill-range>to-regex-range": { - "packages": { - "chokidar>braces>fill-range>to-regex-range>is-number": true + "del>p-map>aggregate-error>clean-stack": { + "builtin": { + "os.homedir": true } }, - "chokidar>fsevents": { + "yargs>cliui": { "globals": { - "console.assert": true, - "process.platform": true + "process": true }, - "native": true + "packages": { + "yargs>string-width": true, + "eslint>strip-ansi": true, + "yargs>cliui>wrap-ansi": true + } }, - "chokidar>is-binary-path": { + "vinyl>clone-buffer": { "builtin": { - "path.extname": true - }, + "buffer.Buffer": true + } + }, + "lavamoat>lavamoat-core>merge-deep>clone-deep": { "packages": { - "chokidar>is-binary-path>binary-extensions": true + "lavamoat>lavamoat-core>merge-deep>clone-deep>for-own": true, + "@babel/register>clone-deep>is-plain-object": true, + "lavamoat>lavamoat-core>merge-deep>kind-of": true, + "lavamoat>lavamoat-core>merge-deep>clone-deep>lazy-cache": true, + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone": true } }, - "chokidar>readdirp": { - "builtin": { - "fs": true, - "path.join": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true, - "stream.Readable": true, - "util.promisify": true - }, - "globals": { - "process.platform": true, - "process.versions.node.split": true - }, + "stylelint>execall>clone-regexp": { "packages": { - "chokidar>anymatch>picomatch": true + "stylelint>execall>clone-regexp>is-regexp": true } }, - "cross-spawn": { + "vinyl>clone-stats": { "builtin": { - "child_process.spawn": true, - "child_process.spawnSync": true, - "fs.closeSync": true, - "fs.openSync": true, - "fs.readSync": true, - "path.delimiter": true, - "path.normalize": true, - "path.resolve": true - }, + "fs.Stats": true + } + }, + "gulp-watch>vinyl-file>vinyl>clone-stats": { + "builtin": { + "fs.Stats": true + } + }, + "@metamask/jazzicon>color>clone": { "globals": { - "Buffer.alloc": true, - "process.chdir": true, - "process.cwd": true, - "process.env": true, - "process.platform": true - }, - "packages": { - "cross-spawn>path-key": true, - "cross-spawn>shebang-command": true, - "cross-spawn>which": true + "Buffer": true } }, - "cross-spawn>path-key": { + "vinyl>clone": { "globals": { - "process.env": true, - "process.platform": true + "Buffer": true + } + }, + "vinyl>cloneable-readable": { + "packages": { + "pumpify>inherits": true, + "vinyl>cloneable-readable>process-nextick-args": true, + "vinyl>cloneable-readable>through2": true } }, - "cross-spawn>shebang-command": { + "gulp>undertaker>collection-map": { + "packages": { + "gulp>undertaker>arr-map": true, + "gulp>undertaker>object.reduce>for-own": true, + "gulp>undertaker>arr-map>make-iterator": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>map-visit": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": true + } + }, + "chalk>ansi-styles>color-convert": { + "packages": { + "jest-canvas-mock>moo-color>color-name": true + } + }, + "gulp-livereload>chalk>ansi-styles>color-convert": { + "packages": { + "gulp-livereload>chalk>ansi-styles>color-convert>color-name": true + } + }, + "stylelint>table>slice-ansi>ansi-styles>color-convert": { "packages": { - "cross-spawn>shebang-command>shebang-regex": true + "stylelint>table>slice-ansi>ansi-styles>color-convert>color-name": true + } + }, + "fancy-log>color-support": { + "globals": { + "process": true } }, - "cross-spawn>which": { + "@lavamoat/lavapack>combine-source-map": { "builtin": { + "path.dirname": true, "path.join": true }, "globals": { - "process.cwd": true, - "process.env.OSTYPE": true, - "process.env.PATH": true, - "process.env.PATHEXT": true, "process.platform": true }, "packages": { - "@sentry/cli>which>isexe": true + "nyc>convert-source-map": true, + "@lavamoat/lavapack>combine-source-map>inline-source-map": true, + "@lavamoat/lavapack>combine-source-map>lodash.memoize": true, + "@lavamoat/lavapack>combine-source-map>source-map": true } }, - "debounce-stream>duplexer": { - "builtin": { - "stream": true + "browserify>concat-stream": { + "globals": { + "Buffer.concat": true, + "Buffer.isBuffer": true + }, + "packages": { + "terser>source-map-support>buffer-from": true, + "pumpify>inherits": true, + "browserify>concat-stream>readable-stream": true, + "browserify>concat-stream>typedarray": true } }, - "debounce-stream>through": { - "builtin": { - "stream": true + "lavamoat-browserify>concat-stream": { + "globals": { + "Buffer.concat": true, + "Buffer.isBuffer": true }, + "packages": { + "terser>source-map-support>buffer-from": true, + "pumpify>inherits": true, + "lavamoat-browserify>concat-stream>readable-stream": true, + "browserify>concat-stream>typedarray": true + } + }, + "@babel/core>convert-source-map": { "globals": { - "process.nextTick": true + "Buffer": true, + "atob": true, + "btoa": true, + "value": true } }, - "del": { + "nyc>convert-source-map": { "builtin": { - "path.resolve": true, - "util.promisify": true + "fs.readFileSync": true, + "path.join": true }, "globals": { - "process.cwd": true, - "process.platform": true - }, - "packages": { - "del>graceful-fs": true, - "del>is-glob": true, - "del>is-path-cwd": true, - "del>is-path-inside": true, - "del>p-map": true, - "del>rimraf": true, - "del>slash": true, - "globby": true + "Buffer.from": true } }, - "del>graceful-fs": { + "readable-stream-2>core-util-is": { + "globals": { + "Buffer.isBuffer": true + } + }, + "stylelint>cosmiconfig": { "builtin": { - "assert.equal": true, - "constants.O_SYMLINK": true, - "constants.O_WRONLY": true, - "constants.hasOwnProperty": true, "fs": true, - "stream.Stream.call": true, - "util": true + "os": true, + "path": true }, "globals": { - "clearTimeout": true, - "console.error": true, - "process": true, - "setTimeout": true - } - }, - "del>is-glob": { + "process.cwd": true + }, "packages": { - "del>is-glob>is-extglob": true + "eslint>@eslint/eslintrc>import-fresh": true, + "depcheck>cosmiconfig>parse-json": true, + "globby>dir-glob>path-type": true, + "stylelint>cosmiconfig>yaml": true } }, - "del>is-path-cwd": { + "ts-node>create-require": { "builtin": { - "path.resolve": true + "fs.lstatSync": true, + "module.Module": true, + "module.createRequire": true, + "module.createRequireFromPath": true, + "path.dirname": true, + "path.join": true }, "globals": { - "process.cwd": true, - "process.platform": true + "process.cwd": true } }, - "del>is-path-inside": { + "gulp-sourcemaps>css": { "builtin": { - "path.relative": true, - "path.resolve": true, + "fs.readFileSync": true, + "path.dirname": true, "path.sep": true + }, + "packages": { + "pumpify>inherits": true, + "gulp-sourcemaps>css>source-map-resolve": true, + "gulp-sourcemaps>css>source-map": true } }, - "del>p-map": { + "resolve-url-loader>es6-iterator>d": { "packages": { - "del>p-map>aggregate-error": true + "resolve-url-loader>es6-iterator>es5-ext": true, + "resolve-url-loader>es6-iterator>d>type": true } }, - "del>p-map>aggregate-error": { + "gulp-sourcemaps>debug-fabulous": { "packages": { - "del>p-map>aggregate-error>clean-stack": true, - "prettier-eslint>indent-string": true + "gulp-sourcemaps>debug-fabulous>debug": true, + "gulp-sourcemaps>debug-fabulous>memoizee": true, + "react>object-assign": true } }, - "del>p-map>aggregate-error>clean-stack": { - "builtin": { - "os.homedir": true + "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug": { + "globals": { + "console.debug": true, + "console.log": true + }, + "packages": { + "@typescript-eslint/eslint-plugin>@typescript-eslint/type-utils>debug>ms": true } }, - "del>rimraf": { + "gulp-sourcemaps>debug-fabulous>debug": { "builtin": { - "assert": true, - "fs": true, - "path.join": true + "tty.isatty": true, + "util": true }, "globals": { - "process.platform": true, - "setTimeout": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "nyc>glob": true + "mocha>ms": true, + "mocha>supports-color": true } }, - "depcheck>@babel/traverse": { + "eslint-import-resolver-node>debug": { + "builtin": { + "tty.isatty": true, + "util": true + }, "globals": { - "console.log": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "@babel/code-frame": true, - "@babel/core>@babel/generator": true, - "@babel/core>@babel/parser": true, - "@babel/core>@babel/template": true, - "@babel/core>@babel/types": true, - "babel/preset-env>b@babel/types": true, - "depcheck>@babel/traverse>globals": true, - "nock>debug": true - } - }, - "depcheck>cosmiconfig>parse-json": { - "packages": { - "@babel/code-frame": true, - "depcheck>cosmiconfig>parse-json>error-ex": true, - "depcheck>cosmiconfig>parse-json>lines-and-columns": true, - "webpack>json-parse-even-better-errors": true + "mocha>ms": true, + "mocha>supports-color": true } }, - "depcheck>cosmiconfig>parse-json>error-ex": { + "eslint-plugin-import>eslint-module-utils>debug": { "builtin": { - "util.inherits": true + "tty.isatty": true, + "util": true }, - "packages": { - "depcheck>cosmiconfig>parse-json>error-ex>is-arrayish": true - } - }, - "depcheck>is-core-module": { "globals": { - "process.versions": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "depcheck>is-core-module>hasown": true - } - }, - "depcheck>is-core-module>hasown": { - "packages": { - "browserify>has>function-bind": true + "mocha>ms": true, + "mocha>supports-color": true } }, - "depcheck>json5": { - "globals": { - "console.warn": true - } - }, - "depcheck>resolve": { + "eslint-plugin-import>debug": { "builtin": { - "fs.readFile": true, - "fs.readFileSync": true, - "fs.realpath": true, - "fs.realpathSync": true, - "fs.stat": true, - "fs.statSync": true, - "os.homedir": true, - "path.dirname": true, - "path.join": true, - "path.parse": true, - "path.relative": true, - "path.resolve": true - }, - "globals": { - "process.env.HOME": true, - "process.env.HOMEDRIVE": true, - "process.env.HOMEPATH": true, - "process.env.LNAME": true, - "process.env.LOGNAME": true, - "process.env.USER": true, - "process.env.USERNAME": true, - "process.env.USERPROFILE": true, - "process.getuid": true, - "process.nextTick": true, - "process.platform": true, - "process.versions.pnp": true - }, - "packages": { - "depcheck>is-core-module": true, - "depcheck>resolve>path-parse": true - } - }, - "depcheck>resolve>path-parse": { - "globals": { - "process.platform": true - } - }, - "duplexify": { - "globals": { - "Buffer": true, - "process.nextTick": true + "fs.SyncWriteStream": true, + "net.Socket": true, + "tty.WriteStream": true, + "tty.isatty": true, + "util": true }, - "packages": { - "duplexify>end-of-stream": true, - "duplexify>stream-shift": true, - "pumpify>inherits": true, - "readable-stream": true - } - }, - "duplexify>end-of-stream": { "globals": { - "process.nextTick": true + "chrome": true, + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "@metamask/object-multiplex>once": true + "eslint-plugin-import>debug>ms": true } }, - "eslint": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": { "builtin": { - "assert": true, - "fs.existsSync": true, - "fs.lstatSync": true, - "fs.promises": true, - "fs.readFileSync": true, - "fs.readdirSync": true, - "fs.statSync": true, - "fs.unlinkSync": true, - "fs.writeFile": true, - "fs.writeFileSync": true, - "path.dirname": true, - "path.extname": true, - "path.isAbsolute": true, - "path.join": true, - "path.normalize": true, - "path.posix.join": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true, - "url.pathToFileURL": true, - "util.format": true, - "util.inspect": true, - "util.promisify": true + "fs.SyncWriteStream": true, + "net.Socket": true, + "tty.WriteStream": true, + "tty.isatty": true, + "util": true }, "globals": { - "__dirname": true, - "console.log": true, - "describe": true, - "it": true, + "chrome": true, + "console": true, + "document": true, + "localStorage": true, + "navigator": true, "process": true }, "packages": { - "del>is-glob": true, - "del>is-path-inside": true, - "eslint>@eslint-community/eslint-utils": true, - "eslint>@eslint-community/regexpp": true, - "eslint>@eslint/eslintrc": true, - "eslint>@eslint/js": true, - "eslint>@humanwhocodes/config-array": true, - "eslint>@nodelib/fs.walk": true, - "eslint>@ungap/structured-clone": true, - "eslint>ajv": true, - "eslint>doctrine": true, - "eslint>eslint-scope": true, - "eslint>eslint-visitor-keys": true, - "eslint>espree": true, - "eslint>esquery": true, - "eslint>esutils": true, - "eslint>fast-deep-equal": true, - "eslint>file-entry-cache": true, - "eslint>glob-parent": true, - "eslint>globals": true, - "eslint>graphemer": true, - "eslint>ignore": true, - "eslint>imurmurhash": true, - "eslint>json-stable-stringify-without-jsonify": true, - "eslint>levn": true, - "eslint>lodash.merge": true, - "eslint>minimatch": true, - "eslint>natural-compare": true, - "mocha>escape-string-regexp": true, - "mocha>find-up": true, - "nock>debug": true - } - }, - "eslint-config-prettier": { - "globals": { - "process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED": true - } - }, - "eslint-import-resolver-node": { - "builtin": { - "path.dirname": true, - "path.join": true, - "path.resolve": true - }, - "packages": { - "depcheck>resolve": true, - "eslint-import-resolver-node>debug": true + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug>ms": true } }, - "eslint-import-resolver-node>debug": { + "gulp-livereload>debug": { "builtin": { "tty.isatty": true, "util": true @@ -2466,60 +2178,29 @@ }, "packages": { "mocha>ms": true, - "mocha>supports-color": true - } - }, - "eslint-import-resolver-typescript": { - "builtin": { - "path": true - }, - "globals": { - "console.warn": true, - "process.cwd": true - }, - "packages": { - "del>is-glob": true, - "depcheck>resolve": true, - "eslint-plugin-import>tsconfig-paths": true, - "nock>debug": true, - "nyc>glob": true + "gulp-livereload>chalk>supports-color": true } }, - "eslint-plugin-import": { + "nock>debug": { "builtin": { - "fs": true, - "path": true, - "vm": true + "tty.isatty": true, + "util.deprecate": true, + "util.formatWithOptions": true, + "util.inspect": true }, "globals": { - "process.cwd": true, - "process.env": true + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true }, "packages": { - "browserify>has": true, - "del>is-glob": true, - "depcheck>is-core-module": true, - "eslint": true, - "eslint-plugin-import>array.prototype.flat": true, - "eslint-plugin-import>debug": true, - "eslint-plugin-import>doctrine": true, - "eslint-plugin-import>eslint-module-utils": true, - "eslint-plugin-import>tsconfig-paths": true, - "eslint-plugin-react>array-includes": true, - "eslint-plugin-react>object.values": true, - "eslint>minimatch": true, - "typescript": true - } - }, - "eslint-plugin-import>array.prototype.flat": { - "packages": { - "eslint-plugin-react>array.prototype.flatmap>es-shim-unscopables": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>es-abstract": true + "nock>debug>ms": true, + "mocha>supports-color": true } }, - "eslint-plugin-import>debug": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug": { "builtin": { "fs.SyncWriteStream": true, "net.Socket": true, @@ -2536,44 +2217,10 @@ "process": true }, "packages": { - "eslint-plugin-import>debug>ms": true - } - }, - "eslint-plugin-import>doctrine": { - "builtin": { - "assert": true - }, - "packages": { - "eslint>esutils": true - } - }, - "eslint-plugin-import>eslint-module-utils": { - "builtin": { - "crypto.createHash": true, - "fs.existsSync": true, - "fs.readFileSync": true, - "fs.readdirSync": true, - "module": true, - "path.dirname": true, - "path.extname": true, - "path.join": true, - "path.parse": true, - "path.resolve": true - }, - "globals": { - "__dirname.toUpperCase": true, - "console.warn": true, - "process.cwd": true, - "process.hrtime": true - }, - "packages": { - "@babel/eslint-parser": true, - "eslint-import-resolver-node": true, - "eslint-plugin-import>eslint-module-utils>debug": true, - "eslint-plugin-import>eslint-module-utils>find-up": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug>ms": true } }, - "eslint-plugin-import>eslint-module-utils>debug": { + "gulp-livereload>tiny-lr>debug": { "builtin": { "tty.isatty": true, "util": true @@ -2590,301 +2237,279 @@ "mocha>supports-color": true } }, - "eslint-plugin-import>eslint-module-utils>find-up": { - "builtin": { - "path.dirname": true, - "path.join": true, - "path.parse": true, - "path.resolve": true - }, - "packages": { - "eslint-plugin-import>eslint-module-utils>find-up>locate-path": true + "gulp>undertaker>last-run>default-resolution": { + "globals": { + "process.version.match": true } }, - "eslint-plugin-import>eslint-module-utils>find-up>locate-path": { - "builtin": { - "path.resolve": true - }, - "globals": { - "process.cwd": true - }, + "string.prototype.matchall>define-properties>define-data-property": { "packages": { - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate": true, - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>path-exists": true + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>gopd": true } }, - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate": { + "string.prototype.matchall>define-properties": { "packages": { - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate>p-limit": true + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate>p-limit": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>define-property": { "packages": { - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate>p-limit>p-try": true + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true } }, - "eslint-plugin-import>eslint-module-utils>find-up>locate-path>path-exists": { - "builtin": { - "fs.access": true, - "fs.accessSync": true + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": { + "packages": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": true } }, - "eslint-plugin-import>tsconfig-paths": { + "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + } + }, + "gulp>glob-watcher>anymatch>micromatch>define-property": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true, + "gulp>gulp-cli>isobject": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true, + "gulp>gulp-cli>isobject": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>define-property": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>to-regex>define-property": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true, + "gulp>gulp-cli>isobject": true + } + }, + "del": { "builtin": { - "fs.existsSync": true, - "fs.lstatSync": true, - "fs.readFile": true, - "fs.readFileSync": true, - "fs.stat": true, - "fs.statSync": true, - "module._resolveFilename": true, - "module.builtinModules": true, - "path.dirname": true, - "path.isAbsolute": true, - "path.join": true, - "path.resolve": true + "path.resolve": true, + "util.promisify": true }, "globals": { - "console.warn": true, - "process.argv.slice": true, "process.cwd": true, - "process.env": true + "process.platform": true }, "packages": { - "eslint-plugin-import>tsconfig-paths>json5": true, - "eslint-plugin-import>tsconfig-paths>strip-bom": true, - "wait-on>minimist": true + "globby": true, + "del>graceful-fs": true, + "del>is-glob": true, + "del>is-path-cwd": true, + "del>is-path-inside": true, + "del>p-map": true, + "del>rimraf": true, + "del>slash": true } }, - "eslint-plugin-import>tsconfig-paths>json5": { - "globals": { - "console.warn": true + "browserify>deps-sort": { + "packages": { + "browserify>shasum-object": true, + "browserify>deps-sort>through2": true } }, - "eslint-plugin-jest": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": { "builtin": { + "child_process.spawnSync": true, "fs.readdirSync": true, - "path.join": true, - "path.parse": true + "os.platform": true }, "globals": { - "__dirname": true - }, - "packages": { - "@typescript-eslint/eslint-plugin": true, - "eslint-plugin-jest>@typescript-eslint/utils": true + "process.env": true } }, - "eslint-plugin-jest>@typescript-eslint/experimental-utils": { - "builtin": { - "path": true - }, + "browserify>module-deps>detective": { "packages": { - "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, - "@typescript-eslint/parser>@typescript-eslint/types": true, - "eslint": true, - "eslint-plugin-jest>@typescript-eslint/experimental-utils>@typescript-eslint/types": true, - "eslint-plugin-jest>@typescript-eslint/experimental-utils>eslint-utils": true, - "eslint>eslint-scope": true, - "eslint>eslint-utils": true, - "webpack>eslint-scope": true + "browserify>syntax-error>acorn-node": true, + "watchify>defined": true } }, - "eslint-plugin-jest>@typescript-eslint/experimental-utils>eslint-utils": { - "packages": { - "eslint-plugin-jest>@typescript-eslint/experimental-utils>eslint-utils>eslint-visitor-keys": true + "ts-node>diff": { + "globals": { + "setTimeout": true } }, - "eslint-plugin-jest>@typescript-eslint/utils": { + "globby>dir-glob": { "builtin": { - "assert": true, - "path": true + "path.extname": true, + "path.isAbsolute": true, + "path.join": true, + "path.posix.join": true + }, + "globals": { + "process.cwd": true }, "packages": { - "@typescript-eslint/parser>@typescript-eslint/scope-manager": true, - "@typescript-eslint/parser>@typescript-eslint/types": true, - "eslint": true, - "eslint-plugin-jest>@typescript-eslint/experimental-utils>@typescript-eslint/types": true, - "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": true, - "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true, - "eslint-plugin-jest>@typescript-eslint/utils>webpack>eslint-scope": true, - "eslint-plugin-mocha>eslint-utils": true, - "eslint>@eslint-community/eslint-utils": true, - "eslint>eslint-scope": true, - "eslint>eslint-utils": true, - "semver": true, - "webpack>eslint-scope": true + "globby>dir-glob>path-type": true } }, - "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager": { + "eslint>doctrine": { "builtin": { - "path": true + "assert": true }, "packages": { - "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": true, - "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/types": true + "eslint>esutils": true } }, - "eslint-plugin-jest>@typescript-eslint/utils>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": { + "eslint-plugin-import>doctrine": { "builtin": { - "path": true + "assert": true }, "packages": { - "eslint>eslint-visitor-keys": true + "eslint>esutils": true } }, - "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils": { + "eslint-plugin-react>doctrine": { + "builtin": { + "assert": true + }, "packages": { - "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils>eslint-visitor-keys": true, - "eslint>@eslint-community/eslint-utils": true, - "semver": true + "eslint>esutils": true } }, - "eslint-plugin-jsdoc": { + "stylelint>postcss-html>htmlparser2>domutils>dom-serializer": { "packages": { - "eslint": true, - "eslint-plugin-jsdoc>@es-joy/jsdoccomment": true, - "eslint-plugin-jsdoc>are-docs-informative": true, - "eslint-plugin-jsdoc>comment-parser": true, - "eslint-plugin-jsdoc>spdx-expression-parse": true, - "eslint>esquery": true, - "mocha>escape-string-regexp": true, - "nock>debug": true, - "semver": true + "stylelint>postcss-html>htmlparser2>domelementtype": true, + "stylelint>postcss-html>htmlparser2>entities": true } }, - "eslint-plugin-jsdoc>@es-joy/jsdoccomment": { + "stylelint>postcss-html>htmlparser2>domhandler": { "packages": { - "eslint-plugin-jsdoc>@es-joy/jsdoccomment>jsdoc-type-pratt-parser": true, - "eslint-plugin-jsdoc>comment-parser": true, - "eslint>esquery": true - } - }, - "eslint-plugin-jsdoc>@es-joy/jsdoccomment>jsdoc-type-pratt-parser": { - "globals": { - "define": true + "stylelint>postcss-html>htmlparser2>domelementtype": true } }, - "eslint-plugin-jsdoc>spdx-expression-parse": { + "stylelint>postcss-html>htmlparser2>domutils": { "packages": { - "eslint-plugin-jsdoc>spdx-expression-parse>spdx-exceptions": true, - "eslint-plugin-jsdoc>spdx-expression-parse>spdx-license-ids": true + "stylelint>postcss-html>htmlparser2>domutils>dom-serializer": true, + "stylelint>postcss-html>htmlparser2>domelementtype": true } }, - "eslint-plugin-mocha>eslint-utils": { + "browserify>duplexer2": { "packages": { - "eslint-plugin-mocha>eslint-utils>eslint-visitor-keys": true + "browserify>duplexer2>readable-stream": true } }, - "eslint-plugin-node": { + "debounce-stream>duplexer": { "builtin": { - "fs.readFileSync": true, - "fs.readdirSync": true, - "fs.statSync": true, - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.isAbsolute": true, - "path.join": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true - }, + "stream": true + } + }, + "duplexify": { "globals": { - "process.cwd": true + "Buffer": true, + "process.nextTick": true }, "packages": { - "depcheck>resolve": true, - "eslint-plugin-node>eslint-plugin-es": true, - "eslint-plugin-node>eslint-utils": true, - "eslint-plugin-node>semver": true, - "eslint>ignore": true, - "eslint>minimatch": true + "duplexify>end-of-stream": true, + "pumpify>inherits": true, + "readable-stream": true, + "duplexify>stream-shift": true } }, - "eslint-plugin-node>eslint-plugin-es": { + "gulp>vinyl-fs>glob-stream>pumpify>duplexify": { + "globals": { + "Buffer": true, + "process.nextTick": true + }, "packages": { - "eslint-plugin-node>eslint-plugin-es>regexpp": true, - "eslint-plugin-node>eslint-utils": true + "duplexify>end-of-stream": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>glob-stream>readable-stream": true, + "duplexify>stream-shift": true } }, - "eslint-plugin-node>eslint-utils": { + "gulp>vinyl-fs>pumpify>duplexify": { + "globals": { + "Buffer": true, + "process.nextTick": true + }, "packages": { - "eslint-plugin-node>eslint-utils>eslint-visitor-keys": true + "duplexify>end-of-stream": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>readable-stream": true, + "duplexify>stream-shift": true } }, - "eslint-plugin-node>semver": { + "duplexify>end-of-stream": { "globals": { - "console": true, - "process": true - } - }, - "eslint-plugin-prettier": { + "process.nextTick": true + }, "packages": { - "eslint-plugin-prettier>prettier-linter-helpers": true, - "prettier": true + "@metamask/object-multiplex>once": true } }, - "eslint-plugin-prettier>prettier-linter-helpers": { + "depcheck>cosmiconfig>parse-json>error-ex": { + "builtin": { + "util.inherits": true + }, "packages": { - "eslint-plugin-prettier>prettier-linter-helpers>fast-diff": true + "depcheck>cosmiconfig>parse-json>error-ex>is-arrayish": true } }, - "eslint-plugin-react": { + "gulp-livereload>tiny-lr>body>error": { "builtin": { - "fs.statSync": true, - "path.dirname": true, - "path.extname": true - }, - "globals": { - "console.error": true, - "console.log": true, - "process.argv.join": true, - "process.cwd": true + "assert": true }, "packages": { - "eslint": true, - "eslint-plugin-react>array-includes": true, - "eslint-plugin-react>array.prototype.flatmap": true, - "eslint-plugin-react>doctrine": true, - "eslint-plugin-react>estraverse": true, - "eslint-plugin-react>jsx-ast-utils": true, - "eslint-plugin-react>object.entries": true, - "eslint-plugin-react>object.fromentries": true, - "eslint-plugin-react>object.hasown": true, - "eslint-plugin-react>object.values": true, - "eslint-plugin-react>resolve": true, - "eslint-plugin-react>semver": true, - "eslint>minimatch": true, - "prop-types": true, - "string.prototype.matchall": true + "gulp-livereload>tiny-lr>body>error>string-template": true, + "watchify>xtend": true } }, - "eslint-plugin-react-hooks": { - "globals": { - "process.env.NODE_ENV": true + "string.prototype.matchall>es-abstract": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>call-bind>es-define-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>es-object-atoms": true, + "string.prototype.matchall>es-abstract>es-set-tostringtag": true, + "string.prototype.matchall>es-abstract>es-to-primitive": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>gopd": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true, + "depcheck>is-core-module>hasown": true, + "string.prototype.matchall>internal-slot": true, + "string.prototype.matchall>es-abstract>is-callable": true, + "string.prototype.matchall>es-abstract>is-regex": true, + "eslint-plugin-react>array-includes>is-string": true, + "string.prototype.matchall>es-abstract>object-inspect": true, + "string.prototype.matchall>es-abstract>safe-regex-test": true, + "string.prototype.matchall>es-abstract>string.prototype.trim": true } }, - "eslint-plugin-react>array-includes": { + "string.prototype.matchall>call-bind>es-define-property": { "packages": { - "eslint-plugin-react>array-includes>is-string": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>es-abstract": true, "string.prototype.matchall>get-intrinsic": true } }, - "eslint-plugin-react>array-includes>is-string": { + "string.prototype.matchall>es-abstract>es-object-atoms": { "packages": { - "koa>is-generator-function>has-tostringtag": true + "string.prototype.matchall>call-bind>es-errors": true } }, - "eslint-plugin-react>array.prototype.flatmap": { + "string.prototype.matchall>es-abstract>es-set-tostringtag": { "packages": { - "eslint-plugin-react>array.prototype.flatmap>es-shim-unscopables": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>es-abstract": true + "string.prototype.matchall>get-intrinsic": true, + "koa>is-generator-function>has-tostringtag": true, + "depcheck>is-core-module>hasown": true } }, "eslint-plugin-react>array.prototype.flatmap>es-shim-unscopables": { @@ -2892,255 +2517,361 @@ "browserify>has": true } }, - "eslint-plugin-react>doctrine": { - "builtin": { - "assert": true - }, + "string.prototype.matchall>es-abstract>es-to-primitive": { "packages": { - "eslint>esutils": true + "string.prototype.matchall>es-abstract>is-callable": true, + "@metamask/eth-token-tracker>deep-equal>is-date-object": true, + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true } }, - "eslint-plugin-react>jsx-ast-utils": { - "globals": { - "console.error": true - }, + "resolve-url-loader>es6-iterator>es5-ext": { "packages": { - "gulp>vinyl-fs>object.assign": true + "resolve-url-loader>es6-iterator>es6-symbol": true } }, - "eslint-plugin-react>object.entries": { + "resolve-url-loader>es6-iterator": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>es-abstract": true + "resolve-url-loader>es6-iterator>d": true, + "resolve-url-loader>es6-iterator>es5-ext": true, + "resolve-url-loader>es6-iterator>es6-symbol": true } }, - "eslint-plugin-react>object.fromentries": { + "resolve-url-loader>es6-iterator>es6-symbol": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>es-abstract": true + "resolve-url-loader>es6-iterator>d": true, + "resolve-url-loader>es6-iterator>es6-symbol>ext": true } }, - "eslint-plugin-react>object.hasown": { + "gulp>undertaker>es6-weak-map": { "packages": { - "string.prototype.matchall>es-abstract": true + "resolve-url-loader>es6-iterator>d": true, + "resolve-url-loader>es6-iterator>es5-ext": true, + "resolve-url-loader>es6-iterator": true, + "resolve-url-loader>es6-iterator>es6-symbol": true } }, - "eslint-plugin-react>resolve": { + "yargs>escalade": { "builtin": { - "fs.readFile": true, + "fs.readdirSync": true, + "fs.statSync": true, + "path.dirname": true, + "path.resolve": true + } + }, + "eslint": { + "builtin": { + "assert": true, + "fs.existsSync": true, + "fs.lstatSync": true, + "fs.promises": true, "fs.readFileSync": true, - "fs.realpath": true, - "fs.realpathSync": true, - "fs.stat": true, + "fs.readdirSync": true, "fs.statSync": true, - "os.homedir": true, + "fs.unlinkSync": true, + "fs.writeFile": true, + "fs.writeFileSync": true, "path.dirname": true, + "path.extname": true, + "path.isAbsolute": true, "path.join": true, - "path.parse": true, + "path.normalize": true, + "path.posix.join": true, "path.relative": true, - "path.resolve": true + "path.resolve": true, + "path.sep": true, + "url.pathToFileURL": true, + "util.format": true, + "util.inspect": true, + "util.promisify": true }, "globals": { - "process.env.HOME": true, - "process.env.HOMEDRIVE": true, - "process.env.HOMEPATH": true, - "process.env.LNAME": true, - "process.env.LOGNAME": true, - "process.env.USER": true, - "process.env.USERNAME": true, - "process.env.USERPROFILE": true, - "process.getuid": true, - "process.nextTick": true, - "process.platform": true, - "process.versions.pnp": true + "__dirname": true, + "console.log": true, + "describe": true, + "it": true, + "process": true }, "packages": { - "depcheck>is-core-module": true, - "depcheck>resolve>path-parse": true + "eslint>@eslint-community/eslint-utils": true, + "eslint>@eslint-community/regexpp": true, + "eslint>@eslint/eslintrc": true, + "eslint>@eslint/js": true, + "eslint>@humanwhocodes/config-array": true, + "eslint>@nodelib/fs.walk": true, + "eslint>@ungap/structured-clone": true, + "eslint>ajv": true, + "nock>debug": true, + "eslint>doctrine": true, + "mocha>escape-string-regexp": true, + "eslint>eslint-scope": true, + "eslint>eslint-visitor-keys": true, + "eslint>espree": true, + "eslint>esquery": true, + "eslint>esutils": true, + "eslint>fast-deep-equal": true, + "eslint>file-entry-cache": true, + "mocha>find-up": true, + "eslint>glob-parent": true, + "eslint>globals": true, + "eslint>graphemer": true, + "eslint>ignore": true, + "eslint>imurmurhash": true, + "del>is-glob": true, + "del>is-path-inside": true, + "eslint>json-stable-stringify-without-jsonify": true, + "eslint>levn": true, + "eslint>lodash.merge": true, + "eslint>minimatch": true, + "eslint>natural-compare": true } }, - "eslint-plugin-react>semver": { + "eslint-config-prettier": { "globals": { - "console": true, - "process": true + "process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED": true } }, - "eslint>@eslint-community/eslint-utils": { + "eslint-import-resolver-node": { + "builtin": { + "path.dirname": true, + "path.join": true, + "path.resolve": true + }, "packages": { - "eslint>eslint-visitor-keys": true + "eslint-import-resolver-node>debug": true, + "depcheck>resolve": true } }, - "eslint>@eslint/eslintrc": { + "eslint-import-resolver-typescript": { "builtin": { - "assert": true, - "fs": true, + "path": true + }, + "globals": { + "console.warn": true, + "process.cwd": true + }, + "packages": { + "nock>debug": true, + "nyc>glob": true, + "del>is-glob": true, + "depcheck>resolve": true, + "eslint-plugin-import>tsconfig-paths": true + } + }, + "eslint-plugin-import>eslint-module-utils": { + "builtin": { + "crypto.createHash": true, + "fs.existsSync": true, + "fs.readFileSync": true, + "fs.readdirSync": true, "module": true, - "os": true, - "path": true, - "url": true, - "util": true + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.parse": true, + "path.resolve": true }, "globals": { - "__filename": true, + "__dirname.toUpperCase": true, + "console.warn": true, "process.cwd": true, - "process.emitWarning": true, - "process.platform": true + "process.hrtime": true }, "packages": { - "$root$": true, "@babel/eslint-parser": true, - "@babel/eslint-plugin": true, - "@metamask/eslint-config": true, - "@metamask/eslint-config-nodejs": true, - "@metamask/eslint-config-typescript": true, - "@typescript-eslint/eslint-plugin": true, - "eslint": true, - "eslint-config-prettier": true, - "eslint-plugin-import": true, - "eslint-plugin-jsdoc": true, - "eslint-plugin-node": true, - "eslint-plugin-prettier": true, - "eslint-plugin-react": true, - "eslint-plugin-react-hooks": true, - "eslint>ajv": true, - "eslint>globals": true, - "eslint>ignore": true, - "eslint>minimatch": true, - "mocha>strip-json-comments": true, - "nock>debug": true + "eslint-plugin-import>eslint-module-utils>debug": true, + "eslint-import-resolver-node": true, + "eslint-plugin-import>eslint-module-utils>find-up": true } }, - "eslint>@eslint/eslintrc>import-fresh": { + "eslint-plugin-node>eslint-plugin-es": { + "packages": { + "eslint-plugin-node>eslint-utils": true, + "eslint-plugin-node>eslint-plugin-es>regexpp": true + } + }, + "eslint-plugin-import": { "builtin": { - "path.dirname": true + "fs": true, + "path": true, + "vm": true }, "globals": { - "__dirname": true, - "__filename": true + "process.cwd": true, + "process.env": true }, "packages": { - "eslint>@eslint/eslintrc>import-fresh>parent-module": true, - "eslint>@eslint/eslintrc>import-fresh>resolve-from": true + "eslint-plugin-react>array-includes": true, + "eslint-plugin-import>array.prototype.flat": true, + "eslint-plugin-import>debug": true, + "eslint-plugin-import>doctrine": true, + "eslint": true, + "eslint-plugin-import>eslint-module-utils": true, + "browserify>has": true, + "depcheck>is-core-module": true, + "del>is-glob": true, + "eslint>minimatch": true, + "eslint-plugin-react>object.values": true, + "eslint-plugin-import>tsconfig-paths": true, + "typescript": true } }, - "eslint>@eslint/eslintrc>import-fresh>parent-module": { + "eslint-plugin-jest": { + "builtin": { + "fs.readdirSync": true, + "path.join": true, + "path.parse": true + }, + "globals": { + "__dirname": true + }, "packages": { - "@metamask/test-bundler>ow>callsites": true + "@typescript-eslint/eslint-plugin": true, + "eslint-plugin-jest>@typescript-eslint/utils": true } }, - "eslint>@eslint/eslintrc>import-fresh>resolve-from": { - "builtin": { - "fs.realpathSync": true, - "module._nodeModulePaths": true, - "module._resolveFilename": true, - "path.join": true, - "path.resolve": true + "eslint-plugin-jsdoc": { + "packages": { + "eslint-plugin-jsdoc>@es-joy/jsdoccomment": true, + "eslint-plugin-jsdoc>are-docs-informative": true, + "eslint-plugin-jsdoc>comment-parser": true, + "nock>debug": true, + "mocha>escape-string-regexp": true, + "eslint": true, + "eslint>esquery": true, + "semver": true, + "eslint-plugin-jsdoc>spdx-expression-parse": true } }, - "eslint>@humanwhocodes/config-array": { + "eslint-plugin-node": { "builtin": { + "fs.readFileSync": true, + "fs.readdirSync": true, + "fs.statSync": true, + "path.basename": true, "path.dirname": true, + "path.extname": true, + "path.isAbsolute": true, "path.join": true, - "path.relative": true + "path.relative": true, + "path.resolve": true, + "path.sep": true + }, + "globals": { + "process.cwd": true }, "packages": { - "eslint>@humanwhocodes/config-array>@humanwhocodes/object-schema": true, + "eslint-plugin-node>eslint-plugin-es": true, + "eslint-plugin-node>eslint-utils": true, + "eslint>ignore": true, "eslint>minimatch": true, - "nock>debug": true + "depcheck>resolve": true, + "eslint-plugin-node>semver": true } }, - "eslint>@nodelib/fs.walk": { - "builtin": { - "events.EventEmitter": true, - "path.sep": true, - "stream.Readable": true - }, - "globals": { - "setImmediate": true - }, + "eslint-plugin-prettier": { "packages": { - "eslint>@nodelib/fs.walk>@nodelib/fs.scandir": true, - "eslint>@nodelib/fs.walk>fastq": true + "prettier": true, + "eslint-plugin-prettier>prettier-linter-helpers": true } }, - "eslint>@nodelib/fs.walk>@nodelib/fs.scandir": { + "eslint-plugin-react": { "builtin": { - "fs.lstat": true, - "fs.lstatSync": true, - "fs.readdir": true, - "fs.readdirSync": true, - "fs.stat": true, "fs.statSync": true, - "path.sep": true + "path.dirname": true, + "path.extname": true }, "globals": { - "process.versions.node": true + "console.error": true, + "console.log": true, + "process.argv.join": true, + "process.cwd": true }, "packages": { - "eslint>@nodelib/fs.walk>@nodelib/fs.scandir>run-parallel": true, - "fast-glob>@nodelib/fs.stat": true + "eslint-plugin-react>array-includes": true, + "eslint-plugin-react>array.prototype.flatmap": true, + "eslint-plugin-react>doctrine": true, + "eslint": true, + "eslint-plugin-react>estraverse": true, + "eslint-plugin-react>jsx-ast-utils": true, + "eslint>minimatch": true, + "eslint-plugin-react>object.entries": true, + "eslint-plugin-react>object.fromentries": true, + "eslint-plugin-react>object.hasown": true, + "eslint-plugin-react>object.values": true, + "prop-types": true, + "eslint-plugin-react>resolve": true, + "eslint-plugin-react>semver": true, + "string.prototype.matchall": true } }, - "eslint>@nodelib/fs.walk>@nodelib/fs.scandir>run-parallel": { + "eslint-plugin-react-hooks": { "globals": { - "process.nextTick": true + "process.env.NODE_ENV": true } }, - "eslint>@nodelib/fs.walk>fastq": { + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals>eslint-scope": { + "builtin": { + "assert": true + }, "packages": { - "eslint>@nodelib/fs.walk>fastq>reusify": true - } - }, - "eslint>@ungap/structured-clone": { - "globals": { - "structuredClone": true + "eslint>eslint-scope>esrecurse": true, + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals>eslint-scope>estraverse": true } }, - "eslint>ajv": { - "globals": { - "console": true + "eslint-plugin-jest>@typescript-eslint/utils>eslint-scope": { + "builtin": { + "assert": true }, "packages": { - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "eslint>ajv>json-schema-traverse": true, - "eslint>fast-deep-equal": true, - "uri-js": true + "eslint>eslint-scope>esrecurse": true, + "eslint-plugin-jest>@typescript-eslint/utils>eslint-scope>estraverse": true } }, - "eslint>doctrine": { + "eslint>eslint-scope": { "builtin": { "assert": true }, "packages": { - "eslint>esutils": true + "eslint>eslint-scope>esrecurse": true, + "eslint-plugin-react>estraverse": true } }, - "eslint>eslint-scope": { + "webpack>eslint-scope": { "builtin": { "assert": true }, "packages": { - "eslint-plugin-react>estraverse": true, - "eslint>eslint-scope>esrecurse": true + "eslint>eslint-scope>esrecurse": true, + "webpack>eslint-scope>estraverse": true } }, - "eslint>eslint-scope>esrecurse": { + "eslint-plugin-jest>@typescript-eslint/experimental-utils>eslint-utils": { "packages": { - "eslint-plugin-react>estraverse": true + "eslint-plugin-jest>@typescript-eslint/experimental-utils>eslint-utils>eslint-visitor-keys": true } }, - "eslint>espree": { + "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils": { "packages": { - "eslint>eslint-visitor-keys": true, - "eslint>espree>acorn-jsx": true, - "jsdom>acorn": true + "eslint>@eslint-community/eslint-utils": true, + "eslint-plugin-jest>@typescript-eslint/utils>eslint-utils>eslint-visitor-keys": true, + "semver": true } }, - "eslint>espree>acorn-jsx": { + "eslint-plugin-mocha>eslint-utils": { "packages": { - "jsdom>acorn": true + "eslint-plugin-mocha>eslint-utils>eslint-visitor-keys": true + } + }, + "eslint-plugin-node>eslint-utils": { + "packages": { + "eslint-plugin-node>eslint-utils>eslint-visitor-keys": true + } + }, + "eslint>espree": { + "packages": { + "eslint>espree>acorn-jsx": true, + "jsdom>acorn": true, + "eslint>eslint-visitor-keys": true } }, "eslint>esquery": { @@ -3148,90 +2879,140 @@ "define": true } }, - "eslint>file-entry-cache": { - "builtin": { - "crypto.createHash": true, - "fs.readFileSync": true, - "fs.statSync": true, - "path.basename": true, - "path.dirname": true - }, + "eslint>eslint-scope>esrecurse": { "packages": { - "eslint>file-entry-cache>flat-cache": true + "eslint-plugin-react>estraverse": true } }, - "eslint>file-entry-cache>flat-cache": { + "eta": { "builtin": { - "fs.existsSync": true, - "fs.mkdirSync": true, - "fs.readFileSync": true, - "fs.writeFileSync": true, - "path.basename": true, - "path.dirname": true, - "path.resolve": true + "node:fs": true, + "node:path": true }, "globals": { - "__dirname": true - }, + "define": true + } + }, + "gulp-sourcemaps>debug-fabulous>memoizee>event-emitter": { "packages": { - "del>rimraf": true, - "eslint>file-entry-cache>flat-cache>flatted": true + "resolve-url-loader>es6-iterator>d": true, + "resolve-url-loader>es6-iterator>es5-ext": true } }, - "eslint>glob-parent": { + "gulp-livereload>event-stream": { "builtin": { - "os.platform": true, - "path.posix.dirname": true + "buffer.Buffer.isBuffer": true, + "stream.Stream": true + }, + "globals": { + "Buffer.concat": true, + "Buffer.isBuffer": true, + "console.error": true, + "process.nextTick": true, + "setImmediate": true }, "packages": { - "del>is-glob": true + "debounce-stream>duplexer": true, + "gulp-livereload>event-stream>from": true, + "gulp-livereload>event-stream>map-stream": true, + "gulp-livereload>event-stream>pause-stream": true, + "gulp-livereload>event-stream>split": true, + "gulp-livereload>event-stream>stream-combiner": true, + "debounce-stream>through": true } }, - "eslint>ignore": { + "@lavamoat/lavapack>readable-stream>abort-controller>event-target-shim": { "globals": { - "process": true + "Event": true, + "EventTarget": true, + "console": true } }, - "eslint>levn": { + "stylelint>execall": { "packages": { - "eslint>levn>prelude-ls": true, - "eslint>levn>type-check": true + "stylelint>execall>clone-regexp": true } }, - "eslint>levn>type-check": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": { + "globals": { + "__filename": true + }, "packages": { - "eslint>levn>prelude-ls": true + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": true, + "gulp>gulp-cli>matchdep>micromatch>extglob>expand-brackets>posix-character-classes": true, + "gulp>gulp-cli>matchdep>micromatch>regex-not": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex": true } }, - "eslint>minimatch": { - "builtin": { - "path": true - }, + "gulp-watch>anymatch>micromatch>expand-brackets": { + "packages": { + "gulp-watch>anymatch>micromatch>expand-brackets>is-posix-bracket": true + } + }, + "gulp-watch>anymatch>micromatch>braces>expand-range": { + "packages": { + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range": true + } + }, + "resolve-url-loader>es6-iterator>es6-symbol>ext": { "globals": { - "console": true - }, + "__global__": true + } + }, + "gulp>glob-watcher>anymatch>micromatch>braces>extend-shallow": { "packages": { - "eslint>minimatch>brace-expansion": true + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true } }, - "eslint>minimatch>brace-expansion": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": { "packages": { - "eslint>minimatch>brace-expansion>concat-map": true, - "stylelint>balanced-match": true + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true } }, - "eslint>strip-ansi": { + "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": { "packages": { - "eslint>strip-ansi>ansi-regex": true + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true } }, - "eta": { - "builtin": { - "node:fs": true, - "node:path": true - }, - "globals": { - "define": true + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>extend-shallow": { + "packages": { + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + } + }, + "gulp-zip>plugin-error>extend-shallow": { + "packages": { + "gulp-zip>plugin-error>extend-shallow>assign-symbols": true, + "gulp-zip>plugin-error>extend-shallow>is-extendable": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value>extend-shallow": { + "packages": { + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>extend-shallow": { + "packages": { + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + } + }, + "gulp>glob-watcher>anymatch>micromatch>extglob": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>array-unique": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": true, + "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, + "gulp>gulp-cli>matchdep>micromatch>regex-not": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex": true + } + }, + "gulp-watch>anymatch>micromatch>extglob": { + "packages": { + "gulp-watch>anymatch>micromatch>is-extglob": true } }, "fancy-log": { @@ -3252,14 +3033,17 @@ "fancy-log>time-stamp": true } }, - "fancy-log>ansi-gray": { - "packages": { - "fancy-log>ansi-gray>ansi-wrap": true - } - }, - "fancy-log>color-support": { + "gulp-watch>fancy-log": { "globals": { - "process": true + "console": true, + "process.argv.indexOf": true, + "process.stderr.write": true, + "process.stdout.write": true + }, + "packages": { + "fancy-log>ansi-gray": true, + "fancy-log>color-support": true, + "fancy-log>time-stamp": true } }, "fast-glob": { @@ -3281,781 +3065,618 @@ "process.cwd": true }, "packages": { + "fast-glob>@nodelib/fs.stat": true, "eslint>@nodelib/fs.walk": true, "eslint>glob-parent": true, - "fast-glob>@nodelib/fs.stat": true, - "fast-glob>micromatch": true, - "globby>merge2": true - } - }, - "fast-glob>@nodelib/fs.stat": { - "builtin": { - "fs.lstat": true, - "fs.lstatSync": true, - "fs.stat": true, - "fs.statSync": true + "globby>merge2": true, + "fast-glob>micromatch": true } }, - "fast-glob>micromatch": { - "builtin": { - "util.inspect": true - }, + "eslint>@nodelib/fs.walk>fastq": { "packages": { - "chokidar>anymatch>picomatch": true, - "chokidar>braces": true + "eslint>@nodelib/fs.walk>fastq>reusify": true } }, - "fs-extra": { + "gulp-livereload>tiny-lr>faye-websocket": { "builtin": { - "assert": true, - "fs": true, - "os.tmpdir": true, - "path.dirname": true, - "path.isAbsolute": true, - "path.join": true, - "path.normalize": true, - "path.parse": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true + "net.connect": true, + "stream.Stream": true, + "tls.connect": true, + "url.parse": true, + "util.inherits": true }, "globals": { "Buffer": true, - "console.warn": true, - "process.arch": true, - "process.cwd": true, - "process.platform": true, - "process.umask": true, - "process.versions.node.split": true, - "setTimeout": true + "clearInterval": true, + "process.nextTick": true, + "setInterval": true }, "packages": { - "del>graceful-fs": true, - "fs-extra>jsonfile": true, - "fs-extra>universalify": true + "webpack-dev-server>sockjs>websocket-driver": true } }, - "fs-extra>jsonfile": { + "eslint>file-entry-cache": { "builtin": { - "fs": true - }, - "globals": { - "Buffer.isBuffer": true + "crypto.createHash": true, + "fs.readFileSync": true, + "fs.statSync": true, + "path.basename": true, + "path.dirname": true }, "packages": { - "del>graceful-fs": true - } - }, - "gh-pages>globby>pinkie-promise": { - "packages": { - "gh-pages>globby>pinkie-promise>pinkie": true - } - }, - "gh-pages>globby>pinkie-promise>pinkie": { - "globals": { - "process": true, - "setImmediate": true, - "setTimeout": true + "eslint>file-entry-cache>flat-cache": true } }, - "globby": { + "stylelint>file-entry-cache": { "builtin": { - "fs.Stats": true, - "fs.readFile": true, + "crypto.createHash": true, "fs.readFileSync": true, "fs.statSync": true, - "path.dirname": true, - "path.isAbsolute": true, - "path.join": true, - "path.posix.join": true, - "path.relative": true, - "stream.Transform": true, - "util.promisify": true - }, - "globals": { - "process.cwd": true + "path.basename": true, + "path.dirname": true }, "packages": { - "del>slash": true, - "eslint>ignore": true, - "fast-glob": true, - "globby>array-union": true, - "globby>dir-glob": true, - "globby>merge2": true + "stylelint>file-entry-cache>flat-cache": true } }, - "globby>dir-glob": { + "chokidar>braces>fill-range": { "builtin": { - "path.extname": true, - "path.isAbsolute": true, - "path.join": true, - "path.posix.join": true - }, - "globals": { - "process.cwd": true + "util.inspect": true }, "packages": { - "globby>dir-glob>path-type": true + "chokidar>braces>fill-range>to-regex-range": true } }, - "globby>dir-glob>path-type": { + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range": { "builtin": { - "fs": true, - "util.promisify": true + "util.inspect": true + }, + "packages": { + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>extend-shallow": true, + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true, + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>to-regex-range": true } }, - "globby>merge2": { - "builtin": { - "stream.PassThrough": true - }, - "globals": { - "process.nextTick": true + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range": { + "packages": { + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number": true, + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject": true, + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic": true, + "gulp-watch>anymatch>micromatch>braces>repeat-element": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true } }, - "gulp": { + "eslint-plugin-import>eslint-module-utils>find-up": { "builtin": { - "util.inherits": true + "path.dirname": true, + "path.join": true, + "path.parse": true, + "path.resolve": true }, "packages": { - "gulp>glob-watcher": true, - "gulp>undertaker": true, - "gulp>vinyl-fs": true + "eslint-plugin-import>eslint-module-utils>find-up>locate-path": true } }, - "gulp-livereload": { + "mocha>find-up": { "builtin": { - "path.relative": true + "path.dirname": true, + "path.parse": true, + "path.resolve": true }, "packages": { - "fancy-log": true, - "gulp-livereload>chalk": true, - "gulp-livereload>debug": true, - "gulp-livereload>event-stream": true, - "gulp-livereload>lodash.assign": true, - "gulp-livereload>tiny-lr": true + "mocha>find-up>locate-path": true, + "nyc>find-up>path-exists": true } }, - "gulp-livereload>chalk": { + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": { + "builtin": { + "util.inherits": true + }, "globals": { - "process.env.TERM": true, - "process.platform": true + "Buffer.concat": true, + "setImmediate": true }, "packages": { - "gulp-livereload>chalk>ansi-styles": true, - "gulp-livereload>chalk>escape-string-regexp": true, - "gulp-livereload>chalk>supports-color": true - } - }, - "gulp-livereload>chalk>ansi-styles": { - "packages": { - "@metamask/jazzicon>color>color-convert": true + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": true } }, - "gulp-livereload>chalk>supports-color": { + "eslint>file-entry-cache>flat-cache": { "builtin": { - "os.release": true + "fs.existsSync": true, + "fs.mkdirSync": true, + "fs.readFileSync": true, + "fs.writeFileSync": true, + "path.basename": true, + "path.dirname": true, + "path.resolve": true }, "globals": { - "process.env": true, - "process.platform": true, - "process.stderr": true, - "process.stdout": true, - "process.versions.node.split": true + "__dirname": true }, "packages": { - "gulp-livereload>chalk>supports-color>has-flag": true + "eslint>file-entry-cache>flat-cache>flatted": true, + "del>rimraf": true } }, - "gulp-livereload>chalk>supports-color>has-flag": { - "globals": { - "process.argv": true - } - }, - "gulp-livereload>debug": { + "stylelint>file-entry-cache>flat-cache": { "builtin": { - "tty.isatty": true, - "util": true + "fs.existsSync": true, + "fs.readFileSync": true, + "path.basename": true, + "path.dirname": true, + "path.resolve": true }, "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "__dirname": true }, "packages": { - "gulp-livereload>chalk>supports-color": true, - "mocha>ms": true + "stylelint>file-entry-cache>flat-cache>flatted": true, + "stylelint>file-entry-cache>flat-cache>rimraf": true, + "stylelint>file-entry-cache>flat-cache>write": true } }, - "gulp-livereload>event-stream": { - "builtin": { - "buffer.Buffer.isBuffer": true, - "stream.Stream": true - }, + "gulp>vinyl-fs>lead>flush-write-stream": { "globals": { - "Buffer.concat": true, - "Buffer.isBuffer": true, - "console.error": true, - "process.nextTick": true, - "setImmediate": true + "Buffer": true }, "packages": { - "debounce-stream>duplexer": true, - "debounce-stream>through": true, - "gulp-livereload>event-stream>from": true, - "gulp-livereload>event-stream>map-stream": true, - "gulp-livereload>event-stream>pause-stream": true, - "gulp-livereload>event-stream>split": true, - "gulp-livereload>event-stream>stream-combiner": true + "pumpify>inherits": true, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream": true } }, - "gulp-livereload>event-stream>from": { - "builtin": { - "stream": true - }, - "globals": { - "process.nextTick": true + "lavamoat>lavamoat-core>merge-deep>clone-deep>for-own": { + "packages": { + "gulp>undertaker>object.reduce>for-own>for-in": true } }, - "gulp-livereload>event-stream>map-stream": { - "builtin": { - "stream.Stream": true - }, - "globals": { - "process.nextTick": true + "gulp-watch>anymatch>micromatch>object.omit>for-own": { + "packages": { + "gulp>undertaker>object.reduce>for-own>for-in": true } }, - "gulp-livereload>event-stream>pause-stream": { + "gulp>undertaker>object.reduce>for-own": { "packages": { - "debounce-stream>through": true + "gulp>undertaker>object.reduce>for-own>for-in": true } }, - "gulp-livereload>event-stream>split": { - "builtin": { - "string_decoder.StringDecoder": true - }, + "gulp>gulp-cli>matchdep>micromatch>fragment-cache": { "packages": { - "debounce-stream>through": true + "gulp>gulp-cli>liftoff>fined>parse-filepath>map-cache": true } }, - "gulp-livereload>event-stream>stream-combiner": { - "packages": { - "debounce-stream>duplexer": true + "gulp-livereload>event-stream>from": { + "builtin": { + "stream": true + }, + "globals": { + "process.nextTick": true } }, - "gulp-livereload>tiny-lr": { + "fs-extra": { "builtin": { - "events": true, + "assert": true, "fs": true, - "http": true, - "https": true, - "url.parse": true + "os.tmpdir": true, + "path.dirname": true, + "path.isAbsolute": true, + "path.join": true, + "path.normalize": true, + "path.parse": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true }, "globals": { - "console.error": true + "Buffer": true, + "console.warn": true, + "process.arch": true, + "process.cwd": true, + "process.platform": true, + "process.umask": true, + "process.versions.node.split": true, + "setTimeout": true }, "packages": { - "@storybook/addon-knobs>qs": true, - "gulp-livereload>tiny-lr>body": true, - "gulp-livereload>tiny-lr>debug": true, - "gulp-livereload>tiny-lr>faye-websocket": true, - "react>object-assign": true + "del>graceful-fs": true, + "fs-extra>jsonfile": true, + "fs-extra>universalify": true } }, - "gulp-livereload>tiny-lr>body": { + "gulp>vinyl-fs>fs-mkdirp-stream": { "builtin": { - "querystring.parse": true + "path.dirname": true, + "path.resolve": true + }, + "globals": { + "process.umask": true }, "packages": { - "gulp-livereload>tiny-lr>body>continuable-cache": true, - "gulp-livereload>tiny-lr>body>error": true, - "gulp-livereload>tiny-lr>body>raw-body": true, - "gulp-livereload>tiny-lr>body>safe-json-parse": true + "del>graceful-fs": true, + "gulp>vinyl-fs>fs-mkdirp-stream>through2": true } }, - "gulp-livereload>tiny-lr>body>error": { + "nyc>glob>fs.realpath": { "builtin": { - "assert": true + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readlink": true, + "fs.readlinkSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.normalize": true, + "path.resolve": true }, - "packages": { - "gulp-livereload>tiny-lr>body>error>string-template": true, - "watchify>xtend": true + "globals": { + "console.error": true, + "console.trace": true, + "process.env.NODE_DEBUG": true, + "process.nextTick": true, + "process.noDeprecation": true, + "process.platform": true, + "process.throwDeprecation": true, + "process.traceDeprecation": true, + "process.version": true } }, - "gulp-livereload>tiny-lr>body>raw-body": { + "chokidar>fsevents": { "globals": { - "Buffer.concat": true, - "process.nextTick": true + "console.assert": true, + "process.platform": true }, - "packages": { - "gulp-livereload>tiny-lr>body>raw-body>bytes": true, - "gulp-livereload>tiny-lr>body>raw-body>string_decoder": true - } - }, - "gulp-livereload>tiny-lr>body>raw-body>string_decoder": { - "builtin": { - "buffer.Buffer": true - } + "native": true }, - "gulp-livereload>tiny-lr>debug": { + "gulp>glob-watcher>chokidar>fsevents": { "builtin": { - "tty.isatty": true, - "util": true + "events.EventEmitter": true, + "fs.stat": true, + "path.join": true, + "util.inherits": true }, "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "__dirname": true, + "console.assert": true, + "process.nextTick": true, + "process.platform": true, + "setImmediate": true }, "packages": { - "mocha>ms": true, - "mocha>supports-color": true + "gulp-watch>chokidar>fsevents>node-pre-gyp": true } }, - "gulp-livereload>tiny-lr>faye-websocket": { + "gulp-watch>chokidar>fsevents": { "builtin": { - "net.connect": true, - "stream.Stream": true, - "tls.connect": true, - "url.parse": true, + "events.EventEmitter": true, + "fs.stat": true, + "path.join": true, "util.inherits": true }, "globals": { - "Buffer": true, - "clearInterval": true, + "__dirname": true, + "console.assert": true, "process.nextTick": true, - "setInterval": true + "process.platform": true, + "setImmediate": true }, "packages": { - "webpack-dev-server>sockjs>websocket-driver": true + "gulp-watch>chokidar>fsevents>node-pre-gyp": true } }, - "gulp-postcss": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge": { "builtin": { - "path.dirname": true, - "path.isAbsolute": true, - "path.join": true, - "stream.Transform": true + "util.format": true }, "globals": { - "Buffer.from": true, - "setImmediate": true - }, - "packages": { - "fancy-log": true, - "gulp-postcss>postcss-load-config": true, - "gulp-zip>plugin-error": true, - "postcss": true, - "vinyl-sourcemaps-apply": true - } - }, - "gulp-postcss>postcss-load-config": { - "builtin": { - "module.createRequire": true, - "module.createRequireFromPath": true, - "path.resolve": true - }, - "globals": { - "process.cwd": true, - "process.env.NODE_ENV": true + "clearInterval": true, + "process": true, + "setImmediate": true, + "setInterval": true }, "packages": { - "gulp-postcss>postcss-load-config>lilconfig": true, - "gulp-postcss>postcss-load-config>yaml": true, - "ts-node": true + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>aproba": true, + "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, + "@storybook/react>@storybook/node-logger>npmlog>gauge>has-unicode": true, + "react>object-assign": true, + "nyc>signal-exit": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": true, + "@storybook/react>@storybook/node-logger>npmlog>gauge>wide-align": true } }, - "gulp-postcss>postcss-load-config>lilconfig": { + "browserify>insert-module-globals>undeclared-identifiers>get-assigned-identifiers": { "builtin": { - "fs.accessSync": true, - "fs.promises.access": true, - "fs.promises.readFile": true, - "fs.readFileSync": true, - "os.homedir": true, - "path.extname": true, - "path.join": true, - "path.parse": true, - "path.resolve": true, - "path.sep": true - }, - "globals": { - "process.cwd": true + "assert.equal": true } }, - "gulp-postcss>postcss-load-config>yaml": { + "string.prototype.matchall>get-intrinsic": { "globals": { - "Buffer": true, - "YAML_SILENCE_DEPRECATION_WARNINGS": true, - "YAML_SILENCE_WARNINGS": true, - "atob": true, - "btoa": true, - "console.warn": true, - "process": true + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "string.prototype.matchall>call-bind>es-errors": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true, + "depcheck>is-core-module>hasown": true } }, - "gulp-sass": { + "gulp-zip>get-stream": { "builtin": { - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.join": true, - "path.relative": true, - "stream.Transform": true + "buffer.constants.MAX_LENGTH": true, + "stream.PassThrough": true }, "globals": { - "process.cwd": true, - "process.exit": true, - "process.stderr.write": true + "Buffer.concat": true }, "packages": { - "eslint>strip-ansi": true, - "gulp-sass>lodash.clonedeep": true, - "gulp-sass>replace-ext": true, - "gulp-zip>plugin-error": true, - "postcss>picocolors": true, - "vinyl-sourcemaps-apply": true + "pumpify>pump": true } }, - "gulp-sass>replace-ext": { + "gulp-watch>anymatch>micromatch>parse-glob>glob-base": { "builtin": { - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.join": true, - "path.sep": true + "path.dirname": true + }, + "packages": { + "eslint>glob-parent": true, + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": true } }, - "gulp-sort": { + "eslint>glob-parent": { + "builtin": { + "os.platform": true, + "path.posix.dirname": true + }, "packages": { - "gulp-sort>through2": true + "del>is-glob": true } }, - "gulp-sort>through2": { + "gulp>vinyl-fs>glob-stream": { "builtin": { "util.inherits": true }, "globals": { + "process.cwd": true, "process.nextTick": true }, "packages": { - "gulp-sort>through2>readable-stream": true, - "watchify>xtend": true + "react-markdown>unified>extend": true, + "eslint>glob-parent": true, + "nyc>glob": true, + "gulp>glob-watcher>is-negated-glob": true, + "gulp>vinyl-fs>glob-stream>ordered-read-streams": true, + "gulp>vinyl-fs>glob-stream>pumpify": true, + "gulp>vinyl-fs>glob-stream>readable-stream": true, + "vinyl>remove-trailing-separator": true, + "gulp>vinyl-fs>glob-stream>to-absolute-glob": true, + "gulp>vinyl-fs>glob-stream>unique-stream": true } }, - "gulp-sort>through2>readable-stream": { + "gulp>glob-watcher": { + "packages": { + "gulp>glob-watcher>anymatch": true, + "gulp>glob-watcher>async-done": true, + "chokidar": true, + "gulp>glob-watcher>is-negated-glob": true, + "gulp>glob-watcher>just-debounce": true, + "gulp>undertaker>object.defaults": true + } + }, + "nyc>glob": { "builtin": { + "assert": true, "events.EventEmitter": true, - "stream": true, + "fs": true, + "path.join": true, + "path.resolve": true, "util": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "console.error": true, + "process.cwd": true, + "process.nextTick": true, + "process.platform": true }, "packages": { - "gulp-sort>through2>readable-stream>isarray": true, - "gulp-sort>through2>readable-stream>safe-buffer": true, - "gulp-sort>through2>readable-stream>string_decoder": true, + "nyc>glob>fs.realpath": true, + "nyc>glob>inflight": true, "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "eslint>minimatch": true, + "@metamask/object-multiplex>once": true, + "gulp-watch>path-is-absolute": true } }, - "gulp-sort>through2>readable-stream>safe-buffer": { + "stylelint>global-modules": { "builtin": { - "buffer": true + "path.resolve": true + }, + "globals": { + "process.env.OSTYPE": true, + "process.platform": true + }, + "packages": { + "stylelint>global-modules>global-prefix": true } }, - "gulp-sort>through2>readable-stream>string_decoder": { + "stylelint>global-modules>global-prefix": { + "builtin": { + "fs.readFileSync": true, + "fs.realpathSync": true, + "os.homedir": true, + "path.dirname": true, + "path.join": true, + "path.resolve": true + }, + "globals": { + "process.env.APPDATA": true, + "process.env.DESTDIR": true, + "process.env.OSTYPE": true, + "process.env.PREFIX": true, + "process.execPath": true, + "process.platform": true + }, "packages": { - "gulp-sort>through2>readable-stream>safe-buffer": true + "stylelint>global-modules>global-prefix>ini": true, + "stylelint>global-modules>global-prefix>which": true } }, - "gulp-sourcemaps": { + "globby": { "builtin": { + "fs.Stats": true, + "fs.readFile": true, + "fs.readFileSync": true, + "fs.statSync": true, "path.dirname": true, - "path.extname": true, + "path.isAbsolute": true, "path.join": true, + "path.posix.join": true, "path.relative": true, - "path.resolve": true, - "path.sep": true + "stream.Transform": true, + "util.promisify": true }, "globals": { - "Buffer.concat": true, - "Buffer.from": true + "process.cwd": true }, "packages": { - "del>graceful-fs": true, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map": true, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources": true, - "gulp-sourcemaps>acorn": true, - "gulp-sourcemaps>css": true, - "gulp-sourcemaps>debug-fabulous": true, - "gulp-sourcemaps>detect-newline": true, - "gulp-sourcemaps>source-map": true, - "gulp-sourcemaps>strip-bom-string": true, - "gulp-sourcemaps>through2": true, - "nyc>convert-source-map": true + "globby>array-union": true, + "globby>dir-glob": true, + "fast-glob": true, + "eslint>ignore": true, + "globby>merge2": true, + "del>slash": true } }, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map": { - "packages": { - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>acorn": true, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>normalize-path": true, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss": true, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>source-map": true, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>through2": true + "stylelint>globjoin": { + "builtin": { + "path.join": true } }, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>acorn": { + "stylelint>postcss-sass>gonzales-pe": { "globals": { + "console.error": true, "define": true } }, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss": { - "builtin": { - "fs": true, - "path": true - }, - "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console": true, - "process.env.NODE_ENV": true - }, + "string.prototype.matchall>es-abstract>gopd": { "packages": { - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss>picocolors": true, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>source-map": true + "string.prototype.matchall>get-intrinsic": true } }, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss>picocolors": { + "del>graceful-fs": { "builtin": { - "tty.isatty": true + "assert.equal": true, + "constants.O_SYMLINK": true, + "constants.O_WRONLY": true, + "constants.hasOwnProperty": true, + "fs": true, + "stream.Stream.call": true, + "util": true }, "globals": { - "process.argv.includes": true, - "process.env": true, - "process.platform": true - } - }, - "gulp-sourcemaps>@gulp-sourcemaps/identity-map>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, - "packages": { - "readable-stream": true - } - }, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources": { - "packages": { - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": true, - "gulp-watch>anymatch>normalize-path": true + "clearTimeout": true, + "console.error": true, + "process": true, + "setTimeout": true } }, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": { + "gulp": { "builtin": { "util.inherits": true }, - "globals": { - "process.nextTick": true - }, "packages": { - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream": true, - "watchify>xtend": true + "gulp>glob-watcher": true, + "gulp>undertaker": true, + "gulp>vinyl-fs": true } }, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream": { + "gulp-livereload": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "path.relative": true }, "packages": { - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>isarray": true, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>safe-buffer": true, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>string_decoder": { - "packages": { - "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>safe-buffer": true - } - }, - "gulp-sourcemaps>acorn": { - "globals": { - "define": true + "gulp-livereload>chalk": true, + "gulp-livereload>debug": true, + "gulp-livereload>event-stream": true, + "fancy-log": true, + "gulp-livereload>lodash.assign": true, + "gulp-livereload>tiny-lr": true } }, - "gulp-sourcemaps>css": { + "gulp-postcss": { "builtin": { - "fs.readFileSync": true, "path.dirname": true, - "path.sep": true - }, - "packages": { - "gulp-sourcemaps>css>source-map": true, - "gulp-sourcemaps>css>source-map-resolve": true, - "pumpify>inherits": true - } - }, - "gulp-sourcemaps>css>source-map-resolve": { - "builtin": { - "path.sep": true, - "url.resolve": true + "path.isAbsolute": true, + "path.join": true, + "stream.Transform": true }, "globals": { - "TextDecoder": true, + "Buffer.from": true, "setImmediate": true }, "packages": { - "gulp-sourcemaps>css>source-map-resolve>atob": true, - "gulp-sourcemaps>css>source-map-resolve>decode-uri-component": true - } - }, - "gulp-sourcemaps>css>source-map-resolve>atob": { - "globals": { - "Buffer.from": true - } - }, - "gulp-sourcemaps>debug-fabulous": { - "packages": { - "gulp-sourcemaps>debug-fabulous>debug": true, - "gulp-sourcemaps>debug-fabulous>memoizee": true, - "react>object-assign": true + "fancy-log": true, + "gulp-zip>plugin-error": true, + "postcss": true, + "gulp-postcss>postcss-load-config": true, + "vinyl-sourcemaps-apply": true } }, - "gulp-sourcemaps>debug-fabulous>debug": { + "gulp-sass": { "builtin": { - "tty.isatty": true, - "util": true - }, - "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.relative": true, + "stream.Transform": true }, - "packages": { - "mocha>ms": true, - "mocha>supports-color": true - } - }, - "gulp-sourcemaps>debug-fabulous>memoizee": { "globals": { - "clearTimeout": true, - "setTimeout": true + "process.cwd": true, + "process.exit": true, + "process.stderr.write": true }, "packages": { - "gulp-sourcemaps>debug-fabulous>memoizee>event-emitter": true, - "gulp-sourcemaps>debug-fabulous>memoizee>is-promise": true, - "gulp-sourcemaps>debug-fabulous>memoizee>lru-queue": true, - "gulp-sourcemaps>debug-fabulous>memoizee>next-tick": true, - "gulp-sourcemaps>debug-fabulous>memoizee>timers-ext": true, - "resolve-url-loader>es6-iterator>d": true, - "resolve-url-loader>es6-iterator>es5-ext": true - } - }, - "gulp-sourcemaps>debug-fabulous>memoizee>event-emitter": { - "packages": { - "resolve-url-loader>es6-iterator>d": true, - "resolve-url-loader>es6-iterator>es5-ext": true - } - }, - "gulp-sourcemaps>debug-fabulous>memoizee>lru-queue": { - "packages": { - "resolve-url-loader>es6-iterator>es5-ext": true - } - }, - "gulp-sourcemaps>debug-fabulous>memoizee>next-tick": { - "globals": { - "MutationObserver": true, - "WebKitMutationObserver": true, - "document": true, - "process": true, - "queueMicrotask": true, - "setImmediate": true, - "setTimeout": true - } - }, - "gulp-sourcemaps>debug-fabulous>memoizee>timers-ext": { - "packages": { - "resolve-url-loader>es6-iterator>es5-ext": true + "gulp-sass>lodash.clonedeep": true, + "postcss>picocolors": true, + "gulp-zip>plugin-error": true, + "gulp-sass>replace-ext": true, + "eslint>strip-ansi": true, + "vinyl-sourcemaps-apply": true } }, - "gulp-sourcemaps>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, + "gulp-sort": { "packages": { - "gulp-sourcemaps>through2>readable-stream": true, - "watchify>xtend": true + "gulp-sort>through2": true } }, - "gulp-sourcemaps>through2>readable-stream": { + "gulp-sourcemaps": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer.concat": true, + "Buffer.from": true }, "packages": { - "gulp-sourcemaps>through2>readable-stream>isarray": true, - "gulp-sourcemaps>through2>readable-stream>safe-buffer": true, - "gulp-sourcemaps>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "gulp-sourcemaps>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "gulp-sourcemaps>through2>readable-stream>string_decoder": { - "packages": { - "gulp-sourcemaps>through2>readable-stream>safe-buffer": true + "gulp-sourcemaps>@gulp-sourcemaps/identity-map": true, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources": true, + "gulp-sourcemaps>acorn": true, + "nyc>convert-source-map": true, + "gulp-sourcemaps>css": true, + "gulp-sourcemaps>debug-fabulous": true, + "gulp-sourcemaps>detect-newline": true, + "del>graceful-fs": true, + "gulp-sourcemaps>source-map": true, + "gulp-sourcemaps>strip-bom-string": true, + "gulp-sourcemaps>through2": true } }, "gulp-stylelint": { @@ -4071,23 +3692,12 @@ "process.nextTick": true }, "packages": { - "eslint>strip-ansi": true, "fancy-log": true, - "gulp-stylelint>through2": true, "gulp-zip>plugin-error": true, "source-map": true, - "stylelint": true - } - }, - "gulp-stylelint>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, - "packages": { - "readable-stream": true + "eslint>strip-ansi": true, + "stylelint": true, + "gulp-stylelint>through2": true } }, "gulp-watch": { @@ -4104,777 +3714,513 @@ "setTimeout": true }, "packages": { - "chokidar": true, - "eslint>glob-parent": true, "gulp-watch>ansi-colors": true, "gulp-watch>anymatch": true, + "chokidar": true, "gulp-watch>fancy-log": true, + "eslint>glob-parent": true, + "react>object-assign": true, "gulp-watch>path-is-absolute": true, + "gulp-zip>plugin-error": true, "gulp-watch>readable-stream": true, "gulp-watch>slash": true, - "gulp-watch>vinyl-file": true, - "gulp-zip>plugin-error": true, - "react>object-assign": true, - "vinyl": true - } - }, - "gulp-watch>ansi-colors": { - "packages": { - "fancy-log>ansi-gray>ansi-wrap": true + "vinyl": true, + "gulp-watch>vinyl-file": true } }, - "gulp-watch>anymatch": { + "gulp-zip": { "builtin": { - "path.sep": true + "buffer.constants.MAX_LENGTH": true, + "path.join": true }, "packages": { - "gulp-watch>anymatch>micromatch": true, - "gulp-watch>anymatch>normalize-path": true + "gulp-zip>get-stream": true, + "gulp-zip>plugin-error": true, + "gulp-zip>through2": true, + "vinyl": true, + "gulp-zip>yazl": true } }, - "gulp-watch>anymatch>micromatch": { - "builtin": { - "path.sep": true - }, - "globals": { - "process": true - }, + "prettier-eslint>loglevel-colored-level-prefix>chalk>has-ansi": { "packages": { - "gulp-watch>anymatch>micromatch>arr-diff": true, - "gulp-watch>anymatch>micromatch>array-unique": true, - "gulp-watch>anymatch>micromatch>braces": true, - "gulp-watch>anymatch>micromatch>expand-brackets": true, - "gulp-watch>anymatch>micromatch>extglob": true, - "gulp-watch>anymatch>micromatch>filename-regex": true, - "gulp-watch>anymatch>micromatch>is-extglob": true, - "gulp-watch>anymatch>micromatch>is-glob": true, - "gulp-watch>anymatch>micromatch>kind-of": true, - "gulp-watch>anymatch>micromatch>object.omit": true, - "gulp-watch>anymatch>micromatch>parse-glob": true, - "gulp-watch>anymatch>micromatch>regex-cache": true, - "gulp-watch>anymatch>normalize-path": true + "prettier-eslint>loglevel-colored-level-prefix>chalk>has-ansi>ansi-regex": true } }, - "gulp-watch>anymatch>micromatch>arr-diff": { - "packages": { - "gulp>undertaker>arr-flatten": true + "chalk>supports-color>has-flag": { + "globals": { + "process.argv": true } }, - "gulp-watch>anymatch>micromatch>braces": { - "packages": { - "gulp-watch>anymatch>micromatch>braces>expand-range": true, - "gulp-watch>anymatch>micromatch>braces>preserve": true, - "gulp-watch>anymatch>micromatch>braces>repeat-element": true + "gulp-livereload>chalk>supports-color>has-flag": { + "globals": { + "process.argv": true } }, - "gulp-watch>anymatch>micromatch>braces>expand-range": { - "packages": { - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range": true + "postcss-discard-font-face>postcss>supports-color>has-flag": { + "globals": { + "process.argv": true } }, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range": { + "string.prototype.matchall>es-abstract>has-property-descriptors": { "packages": { - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number": true, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject": true, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic": true, - "gulp-watch>anymatch>micromatch>braces>repeat-element": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true + "string.prototype.matchall>call-bind>es-define-property": true } }, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number": { + "koa>is-generator-function>has-tostringtag": { "packages": { - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number>kind-of": true + "string.prototype.matchall>has-symbols": true } }, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number>kind-of": { - "packages": { - "browserify>insert-module-globals>is-buffer": true + "@storybook/react>@storybook/node-logger>npmlog>gauge>has-unicode": { + "builtin": { + "os.type": true + }, + "globals": { + "process.env.LANG": true, + "process.env.LC_ALL": true, + "process.env.LC_CTYPE": true } }, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value": { "packages": { - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject>isarray": true + "gulp>gulp-cli>array-sort>get-value": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values": true, + "gulp>gulp-cli>isobject": true } }, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value": { "packages": { - "@babel/register>clone-deep>kind-of": true, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic>math-random": true, - "gulp>undertaker>bach>array-last>is-number": true - } - }, - "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic>math-random": { - "builtin": { - "crypto.randomBytes": true + "gulp>gulp-cli>array-sort>get-value": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>has-values": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject": true } }, - "gulp-watch>anymatch>micromatch>expand-brackets": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values": { "packages": { - "gulp-watch>anymatch>micromatch>expand-brackets>is-posix-bracket": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>kind-of": true } }, - "gulp-watch>anymatch>micromatch>extglob": { + "browserify>has": { "packages": { - "gulp-watch>anymatch>micromatch>is-extglob": true + "browserify>has>function-bind": true } }, - "gulp-watch>anymatch>micromatch>is-glob": { + "depcheck>is-core-module>hasown": { "packages": { - "gulp-watch>anymatch>micromatch>is-extglob": true + "browserify>has>function-bind": true } }, - "gulp-watch>anymatch>micromatch>kind-of": { + "stylelint>postcss-html>htmlparser2": { + "builtin": { + "buffer.Buffer": true, + "events.EventEmitter": true, + "string_decoder.StringDecoder": true + }, "packages": { - "browserify>insert-module-globals>is-buffer": true + "stylelint>postcss-html>htmlparser2>domelementtype": true, + "stylelint>postcss-html>htmlparser2>domhandler": true, + "stylelint>postcss-html>htmlparser2>domutils": true, + "stylelint>postcss-html>htmlparser2>entities": true, + "pumpify>inherits": true, + "readable-stream": true } }, - "gulp-watch>anymatch>micromatch>object.omit": { - "packages": { - "gulp-watch>anymatch>micromatch>object.omit>for-own": true, - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + "webpack-dev-server>sockjs>websocket-driver>http-parser-js": { + "builtin": { + "assert.equal": true, + "assert.ok": true } }, - "gulp-watch>anymatch>micromatch>object.omit>for-own": { - "packages": { - "gulp>undertaker>object.reduce>for-own>for-in": true + "eslint>ignore": { + "globals": { + "process": true } }, - "gulp-watch>anymatch>micromatch>parse-glob": { - "packages": { - "gulp-watch>anymatch>micromatch>is-extglob": true, - "gulp-watch>anymatch>micromatch>parse-glob>glob-base": true, - "gulp-watch>anymatch>micromatch>parse-glob>is-dotfile": true, - "gulp-watch>anymatch>micromatch>parse-glob>is-glob": true + "sass-embedded>immutable": { + "globals": { + "console": true, + "define": true } }, - "gulp-watch>anymatch>micromatch>parse-glob>glob-base": { + "eslint>@eslint/eslintrc>import-fresh": { "builtin": { "path.dirname": true }, + "globals": { + "__dirname": true, + "__filename": true + }, "packages": { - "eslint>glob-parent": true, - "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": true + "eslint>@eslint/eslintrc>import-fresh>parent-module": true, + "eslint>@eslint/eslintrc>import-fresh>resolve-from": true } }, - "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": { + "nyc>glob>inflight": { + "globals": { + "process.nextTick": true + }, "packages": { - "gulp-watch>anymatch>micromatch>is-extglob": true + "@metamask/object-multiplex>once": true, + "@metamask/object-multiplex>once>wrappy": true } }, - "gulp-watch>anymatch>micromatch>parse-glob>is-glob": { - "packages": { - "gulp-watch>anymatch>micromatch>is-extglob": true + "pumpify>inherits": { + "builtin": { + "util.inherits": true } }, - "gulp-watch>anymatch>micromatch>regex-cache": { - "packages": { - "gulp-watch>anymatch>micromatch>regex-cache>is-equal-shallow": true + "ini": { + "globals": { + "process": true } }, - "gulp-watch>anymatch>micromatch>regex-cache>is-equal-shallow": { - "packages": { - "gulp-watch>anymatch>micromatch>regex-cache>is-equal-shallow>is-primitive": true + "stylelint>global-modules>global-prefix>ini": { + "globals": { + "process": true } }, - "gulp-watch>anymatch>normalize-path": { + "@lavamoat/lavapack>combine-source-map>inline-source-map": { + "globals": { + "Buffer.from": true + }, "packages": { - "vinyl>remove-trailing-separator": true + "@lavamoat/lavapack>combine-source-map>inline-source-map>source-map": true } }, - "gulp-watch>chokidar": { + "browserify>insert-module-globals": { "builtin": { - "events.EventEmitter": true, - "fs": true, - "path.basename": true, "path.dirname": true, - "path.extname": true, - "path.join": true, + "path.isAbsolute": true, "path.relative": true, - "path.resolve": true, "path.sep": true }, "globals": { - "clearTimeout": true, - "console.error": true, - "process.env.CHOKIDAR_INTERVAL": true, - "process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR": true, - "process.env.CHOKIDAR_USEPOLLING": true, - "process.nextTick": true, - "process.platform": true, - "setTimeout": true + "Buffer.concat": true, + "Buffer.isBuffer": true }, "packages": { - "chokidar>normalize-path": true, - "eslint>is-glob": true, - "gulp-watch>chokidar>anymatch": true, - "gulp-watch>chokidar>async-each": true, - "gulp-watch>chokidar>braces": true, - "gulp-watch>chokidar>fsevents": true, - "gulp-watch>chokidar>is-binary-path": true, - "gulp-watch>chokidar>readdirp": true, - "gulp-watch>chokidar>upath": true, - "gulp-watch>glob-parent": true, + "browserify>syntax-error>acorn-node": true, + "@lavamoat/lavapack>combine-source-map": true, "gulp-watch>path-is-absolute": true, - "pumpify>inherits": true + "browserify>insert-module-globals>through2": true, + "browserify>insert-module-globals>undeclared-identifiers": true, + "watchify>xtend": true } }, - "gulp-watch>chokidar>fsevents": { - "builtin": { - "events.EventEmitter": true, - "fs.stat": true, - "path.join": true, - "util.inherits": true - }, - "globals": { - "__dirname": true, - "console.assert": true, - "process.nextTick": true, - "process.platform": true, - "setImmediate": true - }, + "string.prototype.matchall>internal-slot": { "packages": { - "gulp-watch>chokidar>fsevents>node-pre-gyp": true + "string.prototype.matchall>call-bind>es-errors": true, + "depcheck>is-core-module>hasown": true, + "string.prototype.matchall>side-channel": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp": { - "builtin": { - "events.EventEmitter": true, - "fs.existsSync": true, - "fs.readFileSync": true, - "fs.renameSync": true, - "path.dirname": true, - "path.existsSync": true, - "path.join": true, - "path.resolve": true, - "url.parse": true, - "url.resolve": true, - "util.inherits": true - }, - "globals": { - "__dirname": true, - "console.log": true, - "process.arch": true, - "process.cwd": true, - "process.env": true, - "process.platform": true, - "process.version.substr": true, - "process.versions": true - }, + "gulp>gulp-cli>replace-homedir>is-absolute": { "packages": { - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": true + "gulp>gulp-cli>replace-homedir>is-absolute>is-relative": true, + "nyc>spawn-wrap>is-windows": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": { - "builtin": { - "child_process.spawnSync": true, - "fs.readdirSync": true, - "os.platform": true - }, - "globals": { - "process.env": true + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": { + "packages": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": { - "builtin": { - "path": true, - "stream.Stream": true, - "url": true - }, - "globals": { - "console": true, - "process.argv": true, - "process.env.DEBUG_NOPT": true, - "process.env.NOPT_DEBUG": true, - "process.platform": true - }, + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": { "packages": { - "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>nopt>abbrev": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": true + "@babel/register>clone-deep>kind-of": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": { - "builtin": { - "child_process.exec": true, - "path": true - }, - "globals": { - "process.env.COMPUTERNAME": true, - "process.env.ComSpec": true, - "process.env.EDITOR": true, - "process.env.HOSTNAME": true, - "process.env.PATH": true, - "process.env.PROMPT": true, - "process.env.PS1": true, - "process.env.Path": true, - "process.env.SHELL": true, - "process.env.USER": true, - "process.env.USERDOMAIN": true, - "process.env.USERNAME": true, - "process.env.VISUAL": true, - "process.env.path": true, - "process.nextTick": true, - "process.platform": true - }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-accessor-descriptor": { "packages": { - "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { - "globals": { - "process.env.SystemRoot": true, - "process.env.TEMP": true, - "process.env.TMP": true, - "process.env.TMPDIR": true, - "process.env.windir": true, - "process.platform": true + "react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": { + "packages": { + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true, + "react-syntax-highlighter>refractor>parse-entities>is-decimal": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": { + "chokidar>is-binary-path": { "builtin": { - "assert": true, - "fs": true, - "path.join": true - }, - "globals": { - "process.platform": true, - "setTimeout": true + "path.extname": true }, "packages": { - "nyc>glob": true + "chokidar>is-binary-path>binary-extensions": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": { + "string.prototype.matchall>es-abstract>is-callable": { "globals": { - "console": true, - "process": true + "document": true } }, - "gulp-watch>fancy-log": { + "depcheck>is-core-module": { "globals": { - "console": true, - "process.argv.indexOf": true, - "process.stderr.write": true, - "process.stdout.write": true + "process.versions": true }, "packages": { - "fancy-log>ansi-gray": true, - "fancy-log>color-support": true, - "fancy-log>time-stamp": true + "depcheck>is-core-module>hasown": true } }, - "gulp-watch>path-is-absolute": { - "globals": { - "process.platform": true + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": { + "packages": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": true } }, - "gulp-watch>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": { "packages": { - "gulp-watch>readable-stream>isarray": true, - "gulp-watch>readable-stream>safe-buffer": true, - "gulp-watch>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "@babel/register>clone-deep>kind-of": true } }, - "gulp-watch>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true } }, - "gulp-watch>readable-stream>string_decoder": { + "@metamask/eth-token-tracker>deep-equal>is-date-object": { "packages": { - "gulp-watch>readable-stream>safe-buffer": true + "koa>is-generator-function>has-tostringtag": true } }, - "gulp-watch>vinyl-file": { - "builtin": { - "path.resolve": true - }, - "globals": { - "process.cwd": true - }, + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": { "packages": { - "del>graceful-fs": true, - "gh-pages>globby>pinkie-promise": true, - "gulp-watch>vinyl-file>pify": true, - "gulp-watch>vinyl-file>strip-bom": true, - "gulp-watch>vinyl-file>strip-bom-stream": true, - "gulp-watch>vinyl-file>vinyl": true + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": true, + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>kind-of": true } }, - "gulp-watch>vinyl-file>strip-bom": { - "globals": { - "Buffer.isBuffer": true - }, + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": { "packages": { - "gulp>vinyl-fs>remove-bom-buffer>is-utf8": true + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": true, + "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": true, + "@babel/register>clone-deep>kind-of": true } }, - "gulp-watch>vinyl-file>strip-bom-stream": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor": { "packages": { - "gulp-watch>vinyl-file>strip-bom": true, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-accessor-descriptor": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>kind-of": true } }, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": { - "builtin": { - "util.inherits": true - }, - "globals": { - "Buffer.concat": true, - "setImmediate": true - }, + "gulp-watch>anymatch>micromatch>regex-cache>is-equal-shallow": { "packages": { - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": true + "gulp-watch>anymatch>micromatch>regex-cache>is-equal-shallow>is-primitive": true } }, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, + "gulp-zip>plugin-error>extend-shallow>is-extendable": { "packages": { - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>isarray": true, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>safe-buffer": true, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "@babel/register>clone-deep>is-plain-object": true } }, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep>is-extendable": { + "packages": { + "@babel/register>clone-deep>is-plain-object": true } }, - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>string_decoder": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": { "packages": { - "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>safe-buffer": true + "gulp>gulp-cli>yargs>string-width>is-fullwidth-code-point>number-is-nan": true } }, - "gulp-watch>vinyl-file>vinyl": { - "builtin": { - "buffer.Buffer": true, - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.join": true, - "path.relative": true, - "stream.PassThrough": true, - "stream.Stream": true - }, - "globals": { - "process.cwd": true - }, + "del>is-glob": { "packages": { - "@metamask/jazzicon>color>clone": true, - "gulp-watch>vinyl-file>vinyl>clone-stats": true, - "gulp-watch>vinyl-file>vinyl>replace-ext": true + "del>is-glob>is-extglob": true } }, - "gulp-watch>vinyl-file>vinyl>clone-stats": { - "builtin": { - "fs.Stats": true + "gulp-watch>anymatch>micromatch>parse-glob>glob-base>is-glob": { + "packages": { + "gulp-watch>anymatch>micromatch>is-extglob": true } }, - "gulp-watch>vinyl-file>vinyl>replace-ext": { - "builtin": { - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.join": true - } - }, - "gulp-zip": { - "builtin": { - "buffer.constants.MAX_LENGTH": true, - "path.join": true - }, + "gulp-watch>anymatch>micromatch>is-glob": { "packages": { - "gulp-zip>get-stream": true, - "gulp-zip>plugin-error": true, - "gulp-zip>through2": true, - "gulp-zip>yazl": true, - "vinyl": true + "gulp-watch>anymatch>micromatch>is-extglob": true } }, - "gulp-zip>get-stream": { - "builtin": { - "buffer.constants.MAX_LENGTH": true, - "stream.PassThrough": true - }, - "globals": { - "Buffer.concat": true - }, + "gulp-watch>anymatch>micromatch>parse-glob>is-glob": { "packages": { - "pumpify>pump": true + "gulp-watch>anymatch>micromatch>is-extglob": true } }, - "gulp-zip>plugin-error": { - "builtin": { - "util.inherits": true - }, + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": { "packages": { - "gulp-watch>ansi-colors": true, - "gulp-zip>plugin-error>arr-diff": true, - "gulp-zip>plugin-error>arr-union": true, - "gulp-zip>plugin-error>extend-shallow": true + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number>kind-of": true } }, - "gulp-zip>plugin-error>extend-shallow": { + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number": { "packages": { - "gulp-zip>plugin-error>extend-shallow>assign-symbols": true, - "gulp-zip>plugin-error>extend-shallow>is-extendable": true + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number>kind-of": true } }, - "gulp-zip>plugin-error>extend-shallow>is-extendable": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number": { "packages": { - "@babel/register>clone-deep>is-plain-object": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": true } }, - "gulp-zip>through2": { + "del>is-path-cwd": { "builtin": { - "util.inherits": true + "path.resolve": true }, "globals": { - "process.nextTick": true - }, - "packages": { - "readable-stream": true + "process.cwd": true, + "process.platform": true } }, - "gulp-zip>yazl": { + "del>is-path-inside": { "builtin": { - "events.EventEmitter": true, - "fs.createReadStream": true, - "fs.stat": true, - "stream.PassThrough": true, - "stream.Transform": true, - "util.inherits": true, - "zlib.DeflateRaw": true, - "zlib.deflateRaw": true - }, - "globals": { - "Buffer": true, - "setImmediate": true, - "utf8FileName.length": true - }, - "packages": { - "gulp-zip>yazl>buffer-crc32": true + "path.relative": true, + "path.resolve": true, + "path.sep": true } }, - "gulp-zip>yazl>buffer-crc32": { - "builtin": { - "buffer.Buffer": true + "@babel/register>clone-deep>is-plain-object": { + "packages": { + "gulp>gulp-cli>isobject": true } }, - "gulp>glob-watcher": { + "string.prototype.matchall>es-abstract>is-regex": { "packages": { - "chokidar": true, - "gulp>glob-watcher>anymatch": true, - "gulp>glob-watcher>async-done": true, - "gulp>glob-watcher>is-negated-glob": true, - "gulp>glob-watcher>just-debounce": true, - "gulp>undertaker>object.defaults": true + "string.prototype.matchall>call-bind": true, + "koa>is-generator-function>has-tostringtag": true } }, - "gulp>glob-watcher>anymatch": { - "builtin": { - "path.sep": true - }, + "gulp>gulp-cli>replace-homedir>is-absolute>is-relative": { "packages": { - "gulp-watch>anymatch>normalize-path": true, - "gulp>glob-watcher>anymatch>micromatch": true + "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path": true } }, - "gulp>glob-watcher>anymatch>micromatch": { - "builtin": { - "path.basename": true, - "path.sep": true, - "util.inspect": true - }, - "globals": { - "process.platform": true - }, + "eslint-plugin-react>array-includes>is-string": { "packages": { - "@babel/register>clone-deep>kind-of": true, - "gulp-zip>plugin-error>arr-diff": true, - "gulp-zip>plugin-error>extend-shallow": true, - "gulp>glob-watcher>anymatch>micromatch>braces": true, - "gulp>glob-watcher>anymatch>micromatch>define-property": true, - "gulp>glob-watcher>anymatch>micromatch>extglob": true, - "gulp>gulp-cli>liftoff>fined>object.pick": true, - "gulp>gulp-cli>matchdep>micromatch>array-unique": true, - "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, - "gulp>gulp-cli>matchdep>micromatch>nanomatch": true, - "gulp>gulp-cli>matchdep>micromatch>regex-not": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex": true + "koa>is-generator-function>has-tostringtag": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces": { + "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { "packages": { - "gulp-watch>anymatch>micromatch>braces>repeat-element": true, - "gulp>glob-watcher>anymatch>micromatch>braces>extend-shallow": true, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range": true, - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>array-unique": true, - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node": true, - "gulp>gulp-cli>matchdep>micromatch>braces>split-string": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex": true, - "gulp>undertaker>arr-flatten": true + "string.prototype.matchall>has-symbols": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces>extend-shallow": { + "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path": { "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path>unc-path-regex": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range": { + "mocha>log-symbols>is-unicode-supported": { + "globals": { + "process.env.CI": true, + "process.env.TERM": true, + "process.env.TERM_PROGRAM": true, + "process.env.WT_SESSION": true, + "process.platform": true + } + }, + "nyc>spawn-wrap>is-windows": { + "globals": { + "define": true, + "isWindows": "write", + "process": true + } + }, + "@sentry/cli>which>isexe": { "builtin": { - "util.inspect": true + "fs": true }, - "packages": { - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>extend-shallow": true, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": true, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>to-regex-range": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true + "globals": { + "TESTING_WINDOWS": true, + "process.env.PATHEXT": true, + "process.getgid": true, + "process.getuid": true, + "process.platform": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>extend-shallow": { + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject": { "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject>isarray": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject": { "packages": { - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number>kind-of": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject>isarray": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number>kind-of": { - "packages": { - "browserify>insert-module-globals>is-buffer": true + "postcss-discard-font-face>postcss>js-base64": { + "globals": { + "Base64": "write", + "define": true } }, - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>to-regex-range": { - "packages": { - "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true + "eslint-plugin-jsdoc>@es-joy/jsdoccomment>jsdoc-type-pratt-parser": { + "globals": { + "define": true } }, - "gulp>glob-watcher>anymatch>micromatch>define-property": { - "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + "@babel/core>@babel/generator>jsesc": { + "globals": { + "Buffer": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob": { - "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": true, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": true, - "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": true, - "gulp>gulp-cli>matchdep>micromatch>array-unique": true, - "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, - "gulp>gulp-cli>matchdep>micromatch>regex-not": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex": true + "webpack>json-parse-even-better-errors": { + "globals": { + "Buffer.isBuffer": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": { + "@lavamoat/lavapack>json-stable-stringify": { "packages": { - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + "string.prototype.matchall>call-bind": true, + "@lavamoat/lavapack>json-stable-stringify>isarray": true, + "@lavamoat/lavapack>json-stable-stringify>jsonify": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": { + "depcheck>json5": { "globals": { - "__filename": true - }, - "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": true, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": true, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": true, - "gulp>gulp-cli>matchdep>micromatch>extglob>expand-brackets>posix-character-classes": true, - "gulp>gulp-cli>matchdep>micromatch>regex-not": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex": true + "console.warn": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": { + "eslint-plugin-import>tsconfig-paths>json5": { + "globals": { + "console.warn": true + } + }, + "fs-extra>jsonfile": { "builtin": { - "fs.SyncWriteStream": true, - "net.Socket": true, - "tty.WriteStream": true, - "tty.isatty": true, - "util": true + "fs": true }, "globals": { - "chrome": true, - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true + "Buffer.isBuffer": true }, "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug>ms": true + "del>graceful-fs": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": { - "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": true + "browserify>JSONStream>jsonparse": { + "globals": { + "Buffer": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": { + "eslint-plugin-react>jsx-ast-utils": { + "globals": { + "console.error": true + }, "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": true, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": true, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>kind-of": true + "gulp>vinyl-fs>object.assign": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": { - "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": true + "gulp>glob-watcher>just-debounce": { + "globals": { + "clearTimeout": true, + "setTimeout": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>kind-of": { "packages": { "browserify>insert-module-globals>is-buffer": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": { + "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": { "packages": { - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": true + "browserify>insert-module-globals>is-buffer": true } }, "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": { @@ -4882,85 +4228,47 @@ "browserify>insert-module-globals>is-buffer": true } }, - "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": { - "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true - } - }, - "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": { "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>glob-watcher>async-done": { - "globals": { - "process.nextTick": true - }, + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number>kind-of": { "packages": { - "@metamask/object-multiplex>once": true, - "duplexify>end-of-stream": true, - "gulp>glob-watcher>async-done>stream-exhaust": true, - "readable-stream-2>process-nextick-args": true - } - }, - "gulp>glob-watcher>async-done>stream-exhaust": { - "builtin": { - "stream.Writable": true, - "util.inherits": true - }, - "globals": { - "setImmediate": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>glob-watcher>chokidar": { + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number>kind-of": { "packages": { - "gulp>glob-watcher>chokidar>fsevents": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>glob-watcher>chokidar>fsevents": { - "builtin": { - "events.EventEmitter": true, - "fs.stat": true, - "path.join": true, - "util.inherits": true - }, - "globals": { - "__dirname": true, - "console.assert": true, - "process.nextTick": true, - "process.platform": true, - "setImmediate": true - }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": { "packages": { - "gulp-watch>chokidar>fsevents>node-pre-gyp": true - } - }, - "gulp>glob-watcher>just-debounce": { - "globals": { - "clearTimeout": true, - "setTimeout": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>gulp-cli>liftoff>fined>object.pick": { + "lavamoat>lavamoat-core>merge-deep>kind-of": { "packages": { - "gulp>gulp-cli>isobject": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node": { + "gulp-watch>anymatch>micromatch>kind-of": { "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>define-property": true, - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>define-property": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>kind-of": { "packages": { - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util": { + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>kind-of": { + "globals": { + "Buffer": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util>kind-of": true + "browserify>insert-module-globals>is-buffer": true } }, "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util>kind-of": { @@ -4968,1828 +4276,1234 @@ "browserify>insert-module-globals>is-buffer": true } }, - "gulp>gulp-cli>matchdep>micromatch>braces>split-string": { - "packages": { - "gulp-zip>plugin-error>extend-shallow": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path>kind-of": { "packages": { - "@babel/register>clone-deep>kind-of": true, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": true, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": true + "browserify>insert-module-globals>is-buffer": true } }, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": { + "labeled-stream-splicer": { "packages": { - "@babel/register>clone-deep>kind-of": true + "pumpify>inherits": true, + "labeled-stream-splicer>stream-splicer": true } }, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": { + "gulp>undertaker>last-run": { + "builtin": { + "assert": true + }, "packages": { - "@babel/register>clone-deep>kind-of": true + "gulp>undertaker>last-run>default-resolution": true, + "gulp>undertaker>es6-weak-map": true } }, - "gulp>gulp-cli>matchdep>micromatch>fragment-cache": { + "lavamoat-browserify": { + "builtin": { + "node:fs.existsSync": true, + "node:fs.mkdirSync": true, + "node:fs.readFileSync": true, + "node:fs.writeFileSync": true, + "node:path.dirname": true, + "node:path.extname": true, + "node:path.resolve": true, + "node:util.callbackify": true + }, + "globals": { + "console.warn": true, + "process.cwd": true, + "setTimeout": true + }, "packages": { - "gulp>gulp-cli>liftoff>fined>parse-filepath>map-cache": true + "lavamoat>@lavamoat/aa": true, + "@lavamoat/lavapack": true, + "browserify>browser-resolve": true, + "lavamoat-browserify>concat-stream": true, + "duplexify": true, + "lavamoat>lavamoat-core": true, + "lavamoat-browserify>readable-stream": true, + "through2": true } }, - "gulp>gulp-cli>matchdep>micromatch>nanomatch": { + "lavamoat>lavamoat-core": { "builtin": { - "path.basename": true, - "path.sep": true, - "util.inspect": true + "node:events": true, + "node:fs.readFileSync": true, + "node:fs/promises": true, + "node:path.extname": true, + "node:path.join": true + }, + "globals": { + "__dirname": true, + "console.error": true, + "console.warn": true, + "define": true }, "packages": { - "@babel/register>clone-deep>kind-of": true, - "gulp-zip>plugin-error>arr-diff": true, - "gulp-zip>plugin-error>extend-shallow": true, - "gulp>gulp-cli>liftoff>fined>object.pick": true, - "gulp>gulp-cli>matchdep>micromatch>array-unique": true, - "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, - "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": true, - "gulp>gulp-cli>matchdep>micromatch>regex-not": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex": true, - "nyc>spawn-wrap>is-windows": true + "@lavamoat/lavapack>json-stable-stringify": true, + "lavamoat>lavamoat-tofu": true, + "lavamoat>lavamoat-core>merge-deep": true } }, - "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": { + "lavamoat>lavamoat-tofu": { + "globals": { + "console.log": true + }, "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + "@babel/core>@babel/parser": true, + "depcheck>@babel/traverse": true } }, - "gulp>gulp-cli>matchdep>micromatch>regex-not": { - "packages": { - "gulp-zip>plugin-error>extend-shallow": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": true + "lavamoat>lavamoat-core>merge-deep>clone-deep>lazy-cache": { + "globals": { + "process.env.TRAVIS": true, + "process.env.UNLAZY": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon": { - "builtin": { - "fs.readFileSync": true, - "path.dirname": true, - "util.inspect": true - }, + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>lazy-cache": { "globals": { - "__filename": true - }, - "packages": { - "gulp>gulp-cli>liftoff>fined>parse-filepath>map-cache": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>extend-shallow": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>source-map": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>use": true, - "resolve-url-loader>rework>css>source-map-resolve": true + "process.env.UNLAZY": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base": { + "gulp>vinyl-fs>lazystream": { "builtin": { "util.inherits": true }, "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>component-emitter": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>define-property": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>pascalcase": true + "gulp>vinyl-fs>lazystream>readable-stream": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base": { + "gulp>vinyl-fs>lead": { + "globals": { + "process.nextTick": true + }, "packages": { - "gulp>gulp-cli>array-sort>get-value": true, - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>union-value": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>component-emitter": true + "gulp>vinyl-fs>lead>flush-write-stream": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit": { + "eslint>levn": { "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>map-visit": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": true + "eslint>levn>prelude-ls": true, + "eslint>levn>type-check": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>map-visit": { + "gulp-postcss>postcss-load-config>lilconfig": { "builtin": { - "util.inspect": true + "fs.accessSync": true, + "fs.promises.access": true, + "fs.promises.readFile": true, + "fs.readFileSync": true, + "os.homedir": true, + "path.extname": true, + "path.join": true, + "path.parse": true, + "path.resolve": true, + "path.sep": true }, - "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": true + "globals": { + "process.cwd": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": { + "eslint-plugin-import>eslint-module-utils>find-up>locate-path": { + "builtin": { + "path.resolve": true + }, + "globals": { + "process.cwd": true + }, "packages": { - "gulp>gulp-cli>isobject": true + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate": true, + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>path-exists": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value": { - "packages": { - "gulp>gulp-cli>array-sort>get-value": true, - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values": { - "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>kind-of": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number": { - "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": { - "packages": { - "browserify>insert-module-globals>is-buffer": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>kind-of": { - "packages": { - "browserify>insert-module-globals>is-buffer": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": { + "mocha>find-up>locate-path": { + "builtin": { + "fs.lstat": true, + "fs.lstatSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.resolve": true, + "util.promisify": true + }, + "globals": { + "process.cwd": true + }, "packages": { - "@babel/register>clone-deep>is-plain-object": true, - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, - "gulp>gulp-cli>matchdep>micromatch>braces>split-string": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value>extend-shallow": true + "mocha>find-up>locate-path>p-locate": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value>extend-shallow": { - "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true + "lodash": { + "globals": { + "define": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path": { - "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path>kind-of": true + "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider>lodash.debounce": { + "globals": { + "clearTimeout": true, + "setTimeout": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path>kind-of": { + "mocha>log-symbols": { "packages": { - "browserify>insert-module-globals>is-buffer": true + "chalk": true, + "mocha>log-symbols>is-unicode-supported": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>union-value": { + "loose-envify": { + "builtin": { + "stream.PassThrough": true, + "stream.Transform": true, + "util.inherits": true + }, + "globals": { + "process.env": true + }, "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, - "gulp-zip>plugin-error>arr-union": true, - "gulp>gulp-cli>array-sort>get-value": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": true + "loose-envify>js-tokens": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value": { + "@babel/core>@babel/helper-compilation-targets>lru-cache": { "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value": true + "@babel/core>@babel/helper-compilation-targets>lru-cache>yallist": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value": { + "gulp-sourcemaps>debug-fabulous>memoizee>lru-queue": { "packages": { - "gulp>gulp-cli>array-sort>get-value": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>has-values": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject": true + "resolve-url-loader>es6-iterator>es5-ext": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject": { + "gulp>undertaker>arr-map>make-iterator": { "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject>isarray": true + "@babel/register>clone-deep>kind-of": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils": { + "gulp-livereload>event-stream>map-stream": { "builtin": { - "util": true + "stream.Stream": true }, - "packages": { - "gulp-zip>plugin-error>arr-union": true, - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true + "globals": { + "process.nextTick": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>map-visit": { "builtin": { - "util.inherits": true + "util.inspect": true }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy": { + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>markdown-table": { "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>copy-descriptor": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>kind-of": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>kind-of": { - "packages": { - "browserify>insert-module-globals>is-buffer": true + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic>math-random": { + "builtin": { + "crypto.randomBytes": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>define-property": { + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>mdast-util-compact": { "packages": { - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + "react-markdown>unist-util-visit": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep": { + "gulp-sourcemaps>debug-fabulous>memoizee": { + "globals": { + "clearTimeout": true, + "setTimeout": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep>is-extendable": true, - "gulp>undertaker>object.reduce>for-own>for-in": true + "resolve-url-loader>es6-iterator>d": true, + "resolve-url-loader>es6-iterator>es5-ext": true, + "gulp-sourcemaps>debug-fabulous>memoizee>event-emitter": true, + "gulp-sourcemaps>debug-fabulous>memoizee>is-promise": true, + "gulp-sourcemaps>debug-fabulous>memoizee>lru-queue": true, + "gulp-sourcemaps>debug-fabulous>memoizee>next-tick": true, + "gulp-sourcemaps>debug-fabulous>memoizee>timers-ext": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep>is-extendable": { + "lavamoat>lavamoat-core>merge-deep": { "packages": { - "@babel/register>clone-deep>is-plain-object": true + "gulp-zip>plugin-error>arr-union": true, + "lavamoat>lavamoat-core>merge-deep>clone-deep": true, + "lavamoat>lavamoat-core>merge-deep>kind-of": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug": { + "globby>merge2": { "builtin": { - "fs.SyncWriteStream": true, - "net.Socket": true, - "tty.WriteStream": true, - "tty.isatty": true, - "util": true + "stream.PassThrough": true }, "globals": { - "chrome": true, - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug>ms": true + "process.nextTick": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": { + "gulp>glob-watcher>anymatch>micromatch": { + "builtin": { + "path.basename": true, + "path.sep": true, + "util.inspect": true + }, + "globals": { + "process.platform": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor": true + "gulp-zip>plugin-error>arr-diff": true, + "gulp>gulp-cli>matchdep>micromatch>array-unique": true, + "gulp>glob-watcher>anymatch>micromatch>braces": true, + "gulp>glob-watcher>anymatch>micromatch>define-property": true, + "gulp-zip>plugin-error>extend-shallow": true, + "gulp>glob-watcher>anymatch>micromatch>extglob": true, + "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, + "@babel/register>clone-deep>kind-of": true, + "gulp>gulp-cli>matchdep>micromatch>nanomatch": true, + "gulp>gulp-cli>liftoff>fined>object.pick": true, + "gulp>gulp-cli>matchdep>micromatch>regex-not": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor": { + "gulp-watch>anymatch>micromatch": { + "builtin": { + "path.sep": true + }, + "globals": { + "process": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-accessor-descriptor": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor": true, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>kind-of": true + "gulp-watch>anymatch>micromatch>arr-diff": true, + "gulp-watch>anymatch>micromatch>array-unique": true, + "gulp-watch>anymatch>micromatch>braces": true, + "gulp-watch>anymatch>micromatch>expand-brackets": true, + "gulp-watch>anymatch>micromatch>extglob": true, + "gulp-watch>anymatch>micromatch>filename-regex": true, + "gulp-watch>anymatch>micromatch>is-extglob": true, + "gulp-watch>anymatch>micromatch>is-glob": true, + "gulp-watch>anymatch>micromatch>kind-of": true, + "gulp-watch>anymatch>normalize-path": true, + "gulp-watch>anymatch>micromatch>object.omit": true, + "gulp-watch>anymatch>micromatch>parse-glob": true, + "gulp-watch>anymatch>micromatch>regex-cache": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-accessor-descriptor": { + "fast-glob>micromatch": { + "builtin": { + "util.inspect": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true + "chokidar>braces": true, + "chokidar>anymatch>picomatch": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor": { + "eslint>minimatch": { + "builtin": { + "path": true + }, + "globals": { + "console": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true + "eslint>minimatch>brace-expansion": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep": { "packages": { - "browserify>insert-module-globals>is-buffer": true + "gulp>undertaker>object.reduce>for-own>for-in": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep>is-extendable": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>extend-shallow": { + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>mixin-object": { "packages": { + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>mixin-object>for-in": true, "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true } }, - "gulp>gulp-cli>matchdep>micromatch>snapdragon>use": { - "packages": { - "@babel/register>clone-deep>kind-of": true + "mockttp>portfinder>mkdirp": { + "builtin": { + "fs": true, + "path.dirname": true, + "path.resolve": true } }, - "gulp>gulp-cli>matchdep>micromatch>to-regex": { + "browserify>module-deps": { + "builtin": { + "fs.createReadStream": true, + "fs.readFile": true, + "path.delimiter": true, + "path.dirname": true, + "path.join": true, + "path.resolve": true + }, + "globals": { + "process.cwd": true, + "process.env.NODE_PATH": true, + "process.nextTick": true, + "process.platform": true, + "setTimeout": true, + "tr": true + }, "packages": { - "gulp-zip>plugin-error>extend-shallow": true, - "gulp>gulp-cli>matchdep>micromatch>regex-not": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex>define-property": true, - "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": true - } - }, - "gulp>gulp-cli>matchdep>micromatch>to-regex>define-property": { - "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true + "browserify>browser-resolve": true, + "browserify>cached-path-relative": true, + "browserify>concat-stream": true, + "watchify>defined": true, + "browserify>module-deps>detective": true, + "browserify>duplexer2": true, + "pumpify>inherits": true, + "loose-envify": true, + "browserify>parents": true, + "browserify>module-deps>readable-stream": true, + "depcheck>resolve": true, + "browserify>module-deps>stream-combiner2": true, + "browserify>module-deps>through2": true, + "watchify>xtend": true } }, - "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": { + "gulp>gulp-cli>matchdep>micromatch>nanomatch": { + "builtin": { + "path.basename": true, + "path.sep": true, + "util.inspect": true + }, "packages": { - "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex>ret": true + "gulp-zip>plugin-error>arr-diff": true, + "gulp>gulp-cli>matchdep>micromatch>array-unique": true, + "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": true, + "gulp-zip>plugin-error>extend-shallow": true, + "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, + "nyc>spawn-wrap>is-windows": true, + "@babel/register>clone-deep>kind-of": true, + "gulp>gulp-cli>liftoff>fined>object.pick": true, + "gulp>gulp-cli>matchdep>micromatch>regex-not": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex": true } }, - "gulp>gulp-cli>replace-homedir>is-absolute": { - "packages": { - "gulp>gulp-cli>replace-homedir>is-absolute>is-relative": true, - "nyc>spawn-wrap>is-windows": true + "gulp-sourcemaps>debug-fabulous>memoizee>next-tick": { + "globals": { + "MutationObserver": true, + "WebKitMutationObserver": true, + "document": true, + "process": true, + "queueMicrotask": true, + "setImmediate": true, + "setTimeout": true } }, - "gulp>gulp-cli>replace-homedir>is-absolute>is-relative": { + "gulp-watch>chokidar>fsevents>node-pre-gyp": { + "builtin": { + "events.EventEmitter": true, + "fs.existsSync": true, + "fs.readFileSync": true, + "fs.renameSync": true, + "path.dirname": true, + "path.existsSync": true, + "path.join": true, + "path.resolve": true, + "url.parse": true, + "url.resolve": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "console.log": true, + "process.arch": true, + "process.cwd": true, + "process.env": true, + "process.platform": true, + "process.version.substr": true, + "process.versions": true + }, "packages": { - "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path": true + "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": true } }, - "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path": { - "packages": { - "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path>unc-path-regex": true - } + "node-sass": { + "native": true }, - "gulp>undertaker": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": { "builtin": { - "assert": true, - "events.EventEmitter": true, - "util.inherits": true + "path": true, + "stream.Stream": true, + "url": true }, "globals": { - "process.env.UNDERTAKER_SETTLE": true, - "process.env.UNDERTAKER_TIME_RESOLUTION": true, - "process.hrtime": true + "console": true, + "process.argv": true, + "process.env.DEBUG_NOPT": true, + "process.env.NOPT_DEBUG": true, + "process.platform": true }, "packages": { - "gulp>undertaker>arr-flatten": true, - "gulp>undertaker>arr-map": true, - "gulp>undertaker>bach": true, - "gulp>undertaker>collection-map": true, - "gulp>undertaker>es6-weak-map": true, - "gulp>undertaker>last-run": true, - "gulp>undertaker>object.defaults": true, - "gulp>undertaker>object.reduce": true, - "gulp>undertaker>undertaker-registry": true + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>nopt>abbrev": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": true } }, - "gulp>undertaker>arr-map": { + "gulp-watch>anymatch>normalize-path": { "packages": { - "gulp>undertaker>arr-map>make-iterator": true + "vinyl>remove-trailing-separator": true } }, - "gulp>undertaker>arr-map>make-iterator": { + "stylelint>normalize-selector": { + "globals": { + "define": true + } + }, + "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": { "packages": { - "@babel/register>clone-deep>kind-of": true + "@metamask/object-multiplex>once": true } }, - "gulp>undertaker>bach": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": { "builtin": { - "assert.ok": true + "events.EventEmitter": true, + "util": true + }, + "globals": { + "process.nextTick": true, + "process.stderr": true }, "packages": { - "gulp>glob-watcher>async-done": true, - "gulp>undertaker>arr-flatten": true, - "gulp>undertaker>arr-map": true, - "gulp>undertaker>bach>arr-filter": true, - "gulp>undertaker>bach>array-each": true, - "gulp>undertaker>bach>array-initial": true, - "gulp>undertaker>bach>array-last": true, - "gulp>undertaker>bach>async-settle": true, - "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>are-we-there-yet": true, + "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge": true, + "nyc>yargs>set-blocking": true } }, - "gulp>undertaker>bach>arr-filter": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy": { "packages": { - "gulp>undertaker>arr-map>make-iterator": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>copy-descriptor": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>kind-of": true } }, - "gulp>undertaker>bach>array-initial": { + "string.prototype.matchall>es-abstract>object-inspect": { + "builtin": { + "util.inspect": true + }, + "globals": { + "HTMLElement": true, + "WeakRef": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": { "packages": { - "gulp>undertaker>bach>array-last>is-number": true, - "gulp>undertaker>object.defaults>array-slice": true + "gulp>gulp-cli>isobject": true } }, - "gulp>undertaker>bach>array-last": { + "gulp>vinyl-fs>object.assign": { "packages": { - "gulp>undertaker>bach>array-last>is-number": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>has-symbols": true, + "@lavamoat/lavapack>json-stable-stringify>object-keys": true } }, - "gulp>undertaker>bach>async-settle": { + "gulp>undertaker>object.defaults": { "packages": { - "gulp>glob-watcher>async-done": true + "gulp>undertaker>bach>array-each": true, + "gulp>undertaker>object.defaults>array-slice": true, + "gulp>undertaker>object.reduce>for-own": true, + "gulp>gulp-cli>isobject": true } }, - "gulp>undertaker>collection-map": { + "eslint-plugin-react>object.entries": { "packages": { - "gulp>undertaker>arr-map": true, - "gulp>undertaker>arr-map>make-iterator": true, - "gulp>undertaker>object.reduce>for-own": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>es-abstract": true } }, - "gulp>undertaker>es6-weak-map": { + "eslint-plugin-react>object.fromentries": { "packages": { - "resolve-url-loader>es6-iterator": true, - "resolve-url-loader>es6-iterator>d": true, - "resolve-url-loader>es6-iterator>es5-ext": true, - "resolve-url-loader>es6-iterator>es6-symbol": true + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>es-abstract": true } }, - "gulp>undertaker>last-run": { - "builtin": { - "assert": true - }, + "eslint-plugin-react>object.hasown": { "packages": { - "gulp>undertaker>es6-weak-map": true, - "gulp>undertaker>last-run>default-resolution": true + "string.prototype.matchall>es-abstract": true } }, - "gulp>undertaker>last-run>default-resolution": { - "globals": { - "process.version.match": true + "gulp-watch>anymatch>micromatch>object.omit": { + "packages": { + "gulp-watch>anymatch>micromatch>object.omit>for-own": true, + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true } }, - "gulp>undertaker>object.defaults": { + "gulp>gulp-cli>liftoff>fined>object.pick": { "packages": { - "gulp>gulp-cli>isobject": true, - "gulp>undertaker>bach>array-each": true, - "gulp>undertaker>object.defaults>array-slice": true, - "gulp>undertaker>object.reduce>for-own": true + "gulp>gulp-cli>isobject": true } }, "gulp>undertaker>object.reduce": { "packages": { - "gulp>undertaker>arr-map>make-iterator": true, - "gulp>undertaker>object.reduce>for-own": true + "gulp>undertaker>object.reduce>for-own": true, + "gulp>undertaker>arr-map>make-iterator": true } }, - "gulp>undertaker>object.reduce>for-own": { + "@metamask/object-multiplex>once": { "packages": { - "gulp>undertaker>object.reduce>for-own>for-in": true + "@metamask/object-multiplex>once>wrappy": true } }, - "gulp>vinyl-fs": { + "gulp>vinyl-fs>glob-stream>ordered-read-streams": { "builtin": { - "os.platform": true, - "path.relative": true, - "path.resolve": true, "util.inherits": true }, - "globals": { - "Buffer.isBuffer": true, - "process.cwd": true, - "process.geteuid": true, - "process.getuid": true, - "process.nextTick": true - }, "packages": { - "del>graceful-fs": true, - "gulp>vinyl-fs>fs-mkdirp-stream": true, - "gulp>vinyl-fs>glob-stream": true, - "gulp>vinyl-fs>is-valid-glob": true, - "gulp>vinyl-fs>lazystream": true, - "gulp>vinyl-fs>lead": true, - "gulp>vinyl-fs>object.assign": true, - "gulp>vinyl-fs>pumpify": true, - "gulp>vinyl-fs>readable-stream": true, - "gulp>vinyl-fs>remove-bom-buffer": true, - "gulp>vinyl-fs>remove-bom-stream": true, - "gulp>vinyl-fs>resolve-options": true, - "gulp>vinyl-fs>through2": true, - "gulp>vinyl-fs>to-through": true, - "gulp>vinyl-fs>value-or-function": true, - "gulp>vinyl-fs>vinyl-sourcemap": true, - "vinyl": true + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream": true } }, - "gulp>vinyl-fs>fs-mkdirp-stream": { + "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": { "builtin": { - "path.dirname": true, - "path.resolve": true + "os.homedir": true }, "globals": { - "process.umask": true - }, - "packages": { - "del>graceful-fs": true, - "gulp>vinyl-fs>fs-mkdirp-stream>through2": true + "process.env": true, + "process.getuid": true, + "process.platform": true } }, - "gulp>vinyl-fs>fs-mkdirp-stream>through2": { - "builtin": { - "util.inherits": true - }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { "globals": { - "process.nextTick": true - }, - "packages": { - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream": true, - "watchify>xtend": true + "process.env.SystemRoot": true, + "process.env.TEMP": true, + "process.env.TMP": true, + "process.env.TMPDIR": true, + "process.env.windir": true, + "process.platform": true } }, - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "child_process.exec": true, + "path": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "process.env.COMPUTERNAME": true, + "process.env.ComSpec": true, + "process.env.EDITOR": true, + "process.env.HOSTNAME": true, + "process.env.PATH": true, + "process.env.PROMPT": true, + "process.env.PS1": true, + "process.env.Path": true, + "process.env.SHELL": true, + "process.env.USER": true, + "process.env.USERDOMAIN": true, + "process.env.USERNAME": true, + "process.env.VISUAL": true, + "process.env.path": true, + "process.nextTick": true, + "process.platform": true }, "packages": { - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>isarray": true, - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true } }, - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "@storybook/test-runner>jest-circus>p-limit": { + "packages": { + "@storybook/test-runner>jest-circus>p-limit>yocto-queue": true } }, - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>string_decoder": { + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate>p-limit": { "packages": { - "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>safe-buffer": true + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate>p-limit>p-try": true } }, - "gulp>vinyl-fs>glob-stream": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.cwd": true, - "process.nextTick": true - }, + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate": { "packages": { - "eslint>glob-parent": true, - "gulp>glob-watcher>is-negated-glob": true, - "gulp>vinyl-fs>glob-stream>ordered-read-streams": true, - "gulp>vinyl-fs>glob-stream>pumpify": true, - "gulp>vinyl-fs>glob-stream>readable-stream": true, - "gulp>vinyl-fs>glob-stream>to-absolute-glob": true, - "gulp>vinyl-fs>glob-stream>unique-stream": true, - "nyc>glob": true, - "react-markdown>unified>extend": true, - "vinyl>remove-trailing-separator": true + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>p-locate>p-limit": true } }, - "gulp>vinyl-fs>glob-stream>ordered-read-streams": { - "builtin": { - "util.inherits": true - }, + "mocha>find-up>locate-path>p-locate": { "packages": { - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream": true + "@storybook/test-runner>jest-circus>p-limit": true } }, - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, + "del>p-map": { "packages": { - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>isarray": true, - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "del>p-map>aggregate-error": true } }, - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "eslint>@eslint/eslintrc>import-fresh>parent-module": { + "packages": { + "@metamask/test-bundler>ow>callsites": true } }, - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>string_decoder": { + "browserify>parents": { + "globals": { + "process.cwd": true, + "process.platform": true + }, "packages": { - "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": true + "browserify>parents>path-platform": true } }, - "gulp>vinyl-fs>glob-stream>pumpify": { + "react-syntax-highlighter>refractor>parse-entities": { "packages": { - "gulp>vinyl-fs>glob-stream>pumpify>duplexify": true, - "gulp>vinyl-fs>glob-stream>pumpify>pump": true, - "pumpify>inherits": true + "react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true, + "react-syntax-highlighter>refractor>parse-entities>character-entities": true, + "react-syntax-highlighter>refractor>parse-entities>character-reference-invalid": true, + "react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true, + "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, + "react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true } }, - "gulp>vinyl-fs>glob-stream>pumpify>duplexify": { - "globals": { - "Buffer": true, - "process.nextTick": true - }, + "gulp-watch>anymatch>micromatch>parse-glob": { "packages": { - "duplexify>end-of-stream": true, - "duplexify>stream-shift": true, - "gulp>vinyl-fs>glob-stream>readable-stream": true, - "pumpify>inherits": true + "gulp-watch>anymatch>micromatch>parse-glob>glob-base": true, + "gulp-watch>anymatch>micromatch>parse-glob>is-dotfile": true, + "gulp-watch>anymatch>micromatch>is-extglob": true, + "gulp-watch>anymatch>micromatch>parse-glob>is-glob": true } }, - "gulp>vinyl-fs>glob-stream>pumpify>pump": { - "builtin": { - "fs": true - }, + "depcheck>cosmiconfig>parse-json": { "packages": { - "@metamask/object-multiplex>once": true, - "duplexify>end-of-stream": true + "@babel/code-frame": true, + "depcheck>cosmiconfig>parse-json>error-ex": true, + "webpack>json-parse-even-better-errors": true, + "depcheck>cosmiconfig>parse-json>lines-and-columns": true } }, - "gulp>vinyl-fs>glob-stream>readable-stream": { + "nyc>find-up>path-exists": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "gulp>vinyl-fs>glob-stream>readable-stream>isarray": true, - "gulp>vinyl-fs>glob-stream>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>glob-stream>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "fs.access": true, + "fs.accessSync": true, + "util.promisify": true } }, - "gulp>vinyl-fs>glob-stream>readable-stream>safe-buffer": { + "eslint-plugin-import>eslint-module-utils>find-up>locate-path>path-exists": { "builtin": { - "buffer": true + "fs.access": true, + "fs.accessSync": true } }, - "gulp>vinyl-fs>glob-stream>readable-stream>string_decoder": { - "packages": { - "gulp>vinyl-fs>glob-stream>readable-stream>safe-buffer": true + "gulp-watch>path-is-absolute": { + "globals": { + "process.platform": true } }, - "gulp>vinyl-fs>glob-stream>to-absolute-glob": { + "depcheck>resolve>path-parse": { + "globals": { + "process.platform": true + } + }, + "browserify>parents>path-platform": { "builtin": { - "path.resolve": true + "path": true, + "util.isObject": true, + "util.isString": true }, "globals": { "process.cwd": true, + "process.env": true, "process.platform": true - }, - "packages": { - "gulp>glob-watcher>is-negated-glob": true, - "gulp>gulp-cli>replace-homedir>is-absolute": true } }, - "gulp>vinyl-fs>glob-stream>unique-stream": { - "packages": { - "@lavamoat/lavapack>json-stable-stringify": true, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter": true + "globby>dir-glob>path-type": { + "builtin": { + "fs": true, + "util.promisify": true } }, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter": { + "gulp-livereload>event-stream>pause-stream": { "packages": { - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2": true, - "watchify>xtend": true + "debounce-stream>through": true } }, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2": { + "postcss>picocolors": { + "globals": { + "process": true + } + }, + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss>picocolors": { "builtin": { - "util.inherits": true + "tty.isatty": true }, "globals": { - "process.nextTick": true + "process.argv.includes": true, + "process.env": true, + "process.platform": true + } + }, + "stylelint>postcss-less>postcss>picocolors": { + "builtin": { + "tty.isatty": true }, - "packages": { - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream": true, - "watchify>xtend": true + "globals": { + "process.argv.includes": true, + "process.env": true, + "process.platform": true } }, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream": { + "stylelint>postcss-safe-parser>postcss>picocolors": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "tty.isatty": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>isarray": true, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "process.argv.includes": true, + "process.env": true, + "process.platform": true } }, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>safe-buffer": { + "stylelint>postcss-sass>postcss>picocolors": { "builtin": { - "buffer": true + "tty.isatty": true + }, + "globals": { + "process.argv.includes": true, + "process.env": true, + "process.platform": true } }, - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>string_decoder": { - "packages": { - "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>safe-buffer": true + "stylelint>postcss-scss>postcss>picocolors": { + "builtin": { + "tty.isatty": true + }, + "globals": { + "process.argv.includes": true, + "process.env": true, + "process.platform": true } }, - "gulp>vinyl-fs>lazystream": { + "stylelint>postcss>picocolors": { "builtin": { - "util.inherits": true + "tty.isatty": true }, - "packages": { - "gulp>vinyl-fs>lazystream>readable-stream": true + "globals": { + "process.argv.includes": true, + "process.env": true, + "process.platform": true } }, - "gulp>vinyl-fs>lazystream>readable-stream": { + "stylelint>sugarss>postcss>picocolors": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "tty.isatty": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "gulp>vinyl-fs>lazystream>readable-stream>isarray": true, - "gulp>vinyl-fs>lazystream>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>lazystream>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "process.argv.includes": true, + "process.env": true, + "process.platform": true } }, - "gulp>vinyl-fs>lazystream>readable-stream>safe-buffer": { + "chokidar>anymatch>picomatch": { "builtin": { - "buffer": true + "path.basename": true, + "path.sep": true + }, + "globals": { + "process.platform": true, + "process.version.slice": true } }, - "gulp>vinyl-fs>lazystream>readable-stream>string_decoder": { + "gh-pages>globby>pinkie-promise": { "packages": { - "gulp>vinyl-fs>lazystream>readable-stream>safe-buffer": true + "gh-pages>globby>pinkie-promise>pinkie": true } }, - "gulp>vinyl-fs>lead": { + "gh-pages>globby>pinkie-promise>pinkie": { "globals": { - "process.nextTick": true - }, - "packages": { - "gulp>vinyl-fs>lead>flush-write-stream": true + "process": true, + "setImmediate": true, + "setTimeout": true } }, - "gulp>vinyl-fs>lead>flush-write-stream": { - "globals": { - "Buffer": true + "gulp-zip>plugin-error": { + "builtin": { + "util.inherits": true }, "packages": { - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream": true, - "pumpify>inherits": true + "gulp-watch>ansi-colors": true, + "gulp-zip>plugin-error>arr-diff": true, + "gulp-zip>plugin-error>arr-union": true, + "gulp-zip>plugin-error>extend-shallow": true } }, - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream": { + "postcss": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "fs.existsSync": true, + "fs.readFileSync": true, + "path.dirname": true, + "path.isAbsolute": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true, + "url.fileURLToPath": true, + "url.pathToFileURL": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer": true, + "URL": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.LANG": true, + "process.env.NODE_ENV": true }, "packages": { - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>isarray": true, - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "nanoid": true, + "postcss>picocolors": true, + "postcss>source-map-js": true } }, - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>string_decoder": { + "postcss-discard-font-face": { "packages": { - "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>safe-buffer": true + "postcss-discard-font-face>balanced-match": true, + "postcss-discard-font-face>postcss": true } }, - "gulp>vinyl-fs>object.assign": { + "stylelint>postcss-html": { + "globals": { + "__dirname": true + }, "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>has-symbols": true + "stylelint>postcss-html>htmlparser2": true, + "stylelint>postcss-syntax": true } }, - "gulp>vinyl-fs>pumpify": { + "stylelint>postcss-less": { "packages": { - "gulp>vinyl-fs>pumpify>duplexify": true, - "gulp>vinyl-fs>pumpify>pump": true, - "pumpify>inherits": true + "stylelint>postcss-less>postcss": true } }, - "gulp>vinyl-fs>pumpify>duplexify": { + "gulp-postcss>postcss-load-config": { + "builtin": { + "module.createRequire": true, + "module.createRequireFromPath": true, + "path.resolve": true + }, "globals": { - "Buffer": true, - "process.nextTick": true + "process.cwd": true, + "process.env.NODE_ENV": true }, "packages": { - "duplexify>end-of-stream": true, - "duplexify>stream-shift": true, - "gulp>vinyl-fs>readable-stream": true, - "pumpify>inherits": true + "gulp-postcss>postcss-load-config>lilconfig": true, + "ts-node": true, + "gulp-postcss>postcss-load-config>yaml": true } }, - "gulp>vinyl-fs>pumpify>pump": { - "builtin": { - "fs": true - }, + "stylelint>postcss-reporter": { "packages": { - "@metamask/object-multiplex>once": true, - "duplexify>end-of-stream": true + "lodash": true } }, - "gulp>vinyl-fs>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, + "postcss-rtlcss": { "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "SuppressedError": true }, "packages": { - "gulp>vinyl-fs>readable-stream>isarray": true, - "gulp>vinyl-fs>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true + "postcss": true, + "postcss-rtlcss>rtlcss": true } }, - "gulp>vinyl-fs>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "stylelint>postcss-safe-parser": { + "packages": { + "stylelint>postcss-safe-parser>postcss": true } }, - "gulp>vinyl-fs>readable-stream>string_decoder": { + "stylelint>postcss-sass": { "packages": { - "gulp>vinyl-fs>readable-stream>safe-buffer": true + "stylelint>postcss-sass>gonzales-pe": true, + "stylelint>postcss-sass>postcss": true } }, - "gulp>vinyl-fs>remove-bom-buffer": { + "stylelint>postcss-scss": { "packages": { - "browserify>insert-module-globals>is-buffer": true, - "gulp>vinyl-fs>remove-bom-buffer>is-utf8": true + "stylelint>postcss-scss>postcss": true } }, - "gulp>vinyl-fs>remove-bom-stream": { + "stylelint>postcss-selector-parser": { "packages": { - "gulp>vinyl-fs>remove-bom-buffer": true, - "gulp>vinyl-fs>remove-bom-stream>through2": true, - "koa>content-disposition>safe-buffer": true + "stylelint>postcss-selector-parser>cssesc": true, + "readable-stream>util-deprecate": true } }, - "gulp>vinyl-fs>remove-bom-stream>through2": { + "stylelint>postcss-syntax": { "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true + "path.isAbsolute": true, + "path.resolve": true, + "path.sep": true }, "packages": { - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream": true, - "watchify>xtend": true + "stylelint>postcss": true } }, - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream": { + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "fs": true, + "path": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>isarray": true, - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder": { - "packages": { - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder>safe-buffer": true - } - }, - "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "gulp>vinyl-fs>resolve-options": { + "Buffer": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.NODE_ENV": true + }, "packages": { - "gulp>vinyl-fs>value-or-function": true + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss>picocolors": true, + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>source-map": true } }, - "gulp>vinyl-fs>through2": { + "postcss-discard-font-face>postcss": { "builtin": { - "util.inherits": true + "fs": true, + "path": true }, "globals": { - "process.nextTick": true + "console": true }, "packages": { - "gulp>vinyl-fs>readable-stream": true, - "watchify>xtend": true - } - }, - "gulp>vinyl-fs>to-through": { - "packages": { - "gulp>vinyl-fs>to-through>through2": true + "postcss-discard-font-face>postcss>chalk": true, + "postcss-discard-font-face>postcss>js-base64": true, + "postcss-discard-font-face>postcss>source-map": true, + "postcss-discard-font-face>postcss>supports-color": true } }, - "gulp>vinyl-fs>to-through>through2": { + "stylelint>postcss-less>postcss": { "builtin": { - "util.inherits": true + "fs": true, + "path": true }, "globals": { - "process.nextTick": true + "Buffer": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.NODE_ENV": true }, "packages": { - "gulp>vinyl-fs>to-through>through2>readable-stream": true, - "watchify>xtend": true + "stylelint>postcss-less>postcss>picocolors": true, + "stylelint>postcss-less>postcss>source-map": true } }, - "gulp>vinyl-fs>to-through>through2>readable-stream": { + "stylelint>postcss-safe-parser>postcss": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "fs": true, + "path": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.NODE_ENV": true }, "packages": { - "gulp>vinyl-fs>to-through>through2>readable-stream>isarray": true, - "gulp>vinyl-fs>to-through>through2>readable-stream>safe-buffer": true, - "gulp>vinyl-fs>to-through>through2>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "gulp>vinyl-fs>to-through>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "gulp>vinyl-fs>to-through>through2>readable-stream>string_decoder": { - "packages": { - "gulp>vinyl-fs>to-through>through2>readable-stream>safe-buffer": true + "stylelint>postcss-safe-parser>postcss>picocolors": true, + "stylelint>postcss-safe-parser>postcss>source-map": true } }, - "gulp>vinyl-fs>vinyl-sourcemap": { + "stylelint>postcss-sass>postcss": { "builtin": { - "path.dirname": true, - "path.join": true, - "path.relative": true, - "path.resolve": true + "fs": true, + "path": true }, "globals": { - "Buffer": true + "Buffer": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.NODE_ENV": true }, "packages": { - "del>graceful-fs": true, - "gulp-watch>anymatch>normalize-path": true, - "gulp>vinyl-fs>remove-bom-buffer": true, - "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": true, - "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true, - "nyc>convert-source-map": true, - "vinyl": true + "stylelint>postcss-sass>postcss>picocolors": true, + "stylelint>postcss-sass>postcss>source-map": true } }, - "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": { + "stylelint>postcss-scss>postcss": { "builtin": { - "os.EOL": true + "fs": true, + "path": true }, "globals": { - "Buffer": true + "Buffer": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.NODE_ENV": true }, "packages": { - "gulp>vinyl-fs>vinyl-sourcemap>append-buffer>buffer-equal": true + "stylelint>postcss-scss>postcss>picocolors": true, + "stylelint>postcss-scss>postcss>source-map": true } }, - "gulp>vinyl-fs>vinyl-sourcemap>append-buffer>buffer-equal": { + "stylelint>postcss": { "builtin": { - "buffer.Buffer.isBuffer": true - } - }, - "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": { - "packages": { - "@metamask/object-multiplex>once": true - } - }, - "ini": { - "globals": { - "process": true - } - }, - "jsdom>acorn": { + "fs": true, + "path": true + }, "globals": { + "Buffer": true, + "atob": true, + "btoa": true, "console": true, - "define": true - } - }, - "koa>content-disposition>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "koa>is-generator-function>has-tostringtag": { - "packages": { - "string.prototype.matchall>has-symbols": true - } - }, - "labeled-stream-splicer": { - "packages": { - "labeled-stream-splicer>stream-splicer": true, - "pumpify>inherits": true - } - }, - "labeled-stream-splicer>stream-splicer": { - "globals": { - "process.nextTick": true, - "setImmediate": true + "process.env.NODE_ENV": true }, "packages": { - "labeled-stream-splicer>stream-splicer>readable-stream": true, - "pumpify>inherits": true + "stylelint>postcss>picocolors": true, + "stylelint>postcss>source-map": true } }, - "labeled-stream-splicer>stream-splicer>readable-stream": { + "stylelint>sugarss>postcss": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "fs": true, + "path": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer": true, + "atob": true, + "btoa": true, + "console": true, + "process.env.NODE_ENV": true }, "packages": { - "labeled-stream-splicer>stream-splicer>readable-stream>isarray": true, - "labeled-stream-splicer>stream-splicer>readable-stream>safe-buffer": true, - "labeled-stream-splicer>stream-splicer>readable-stream>string_decoder": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true - } - }, - "labeled-stream-splicer>stream-splicer>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "labeled-stream-splicer>stream-splicer>readable-stream>string_decoder": { - "packages": { - "labeled-stream-splicer>stream-splicer>readable-stream>safe-buffer": true + "stylelint>sugarss>postcss>picocolors": true, + "stylelint>sugarss>postcss>source-map": true } }, - "lavamoat-browserify": { + "prettier": { "builtin": { - "fs.existsSync": true, - "fs.mkdirSync": true, - "fs.readFileSync": true, - "fs.writeFileSync": true, - "path.dirname": true, - "path.extname": true, - "path.resolve": true, - "util.callbackify": true + "assert": true, + "events.EventEmitter": true, + "fs": true, + "module._nodeModulePaths": true, + "module._resolveFilename": true, + "os": true, + "path": true, + "stream.PassThrough": true, + "stream.Readable": true, + "util.inherits": true, + "util.inspect": true, + "util.promisify": true }, "globals": { - "console.warn": true, - "process.cwd": true, - "setTimeout": true - }, - "packages": { - "@lavamoat/lavapack": true, - "@lavamoat/lavapack>json-stable-stringify": true, - "browserify>browser-resolve": true, - "lavamoat-browserify>concat-stream": true, - "lavamoat-browserify>duplexify": true, - "lavamoat-viz>lavamoat-core": true, - "lavamoat>@lavamoat/aa": true, - "readable-stream": true, - "through2": true - } - }, - "lavamoat-browserify>concat-stream": { - "globals": { - "Buffer.concat": true, - "Buffer.isBuffer": true - }, - "packages": { - "browserify>concat-stream>typedarray": true, - "pumpify>inherits": true, - "readable-stream": true, - "terser>source-map-support>buffer-from": true - } - }, - "lavamoat-browserify>duplexify": { - "globals": { + "ANONYMOUS": true, "Buffer": true, - "process.nextTick": true - }, - "packages": { - "duplexify>end-of-stream": true, - "duplexify>stream-shift": true, - "pumpify>inherits": true, - "readable-stream": true - } - }, - "lavamoat-viz>lavamoat-core": { - "builtin": { - "node:events": true, - "node:fs.readFileSync": true, - "node:fs/promises.writeFile": true, - "node:path.extname": true, - "node:path.join": true - }, - "globals": { + "BuilderFileEmit": true, + "BuilderProgramKind": true, + "BuilderState": true, + "CheckMode": true, + "ClassificationType": true, + "ClassificationTypeNames": true, + "CompletionInfoFlags": true, + "CompletionTriggerKind": true, + "ConfigFileProgramReloadLevel": true, + "CoreServicesShimHostAdapter": true, + "DocumentHighlights": true, + "Element": true, + "EndOfLineState": true, + "ExportKind": true, + "FileSystemEntryKind": true, + "FileWatcherEventKind": true, + "FlattenLevel": true, + "ForegroundColorEscapeSequences": true, + "HTMLElement": true, + "HighlightSpanKind": true, + "ImportKind": true, + "IndentStyle": true, + "InlayHintKind": true, + "Intl": true, + "InvalidatedProjectKind": true, + "LanguageServiceMode": true, + "LanguageServiceShimHostAdapter": true, + "ModuleInstanceState": true, + "NodeResolutionFeatures": true, + "OrganizeImportsMode": true, + "OutliningSpanKind": true, + "OutputFileType": true, + "PackageJsonAutoImportPreference": true, + "PackageJsonDependencyGroup": true, + "PatternMatchKind": true, + "PollingInterval": true, + "PrivateIdentifierKind": true, + "ProcessLevel": true, + "QuotePreference": true, + "SVGElement": true, + "ScriptElementKind": true, + "ScriptElementKindModifier": true, + "ScriptSnapshot": true, + "SemanticClassificationFormat": true, + "SemanticMeaning": true, + "SemicolonPreference": true, + "SignatureCheckMode": true, + "SymbolDisplayPartKind": true, + "TokenClass": true, + "TypeFacts": true, + "TypeScriptServicesFactory": true, + "UpToDateStatusType": true, + "Version": true, + "VersionRange": true, + "WatchLogLevel": true, + "WatchType": true, + "WorkerGlobalScope": true, + "YAML_SILENCE_DEPRECATION_WARNINGS": true, + "YAML_SILENCE_WARNINGS": true, "__dirname": true, - "console.error": true, - "console.warn": true, - "define": true - }, - "packages": { - "@lavamoat/lavapack>json-stable-stringify": true, - "lavamoat-viz>lavamoat-core>lavamoat-tofu": true, - "lavamoat>lavamoat-core>merge-deep": true - } - }, - "lavamoat-viz>lavamoat-core>lavamoat-tofu": { - "globals": { - "console.log": true - }, - "packages": { - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/parser": true, - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/traverse": true - } - }, - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/traverse": { - "globals": { - "console.log": true - }, - "packages": { - "@babel/code-frame": true, - "@babel/core>@babel/generator": true, - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/traverse>@babel/parser": true, - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/traverse>@babel/types": true, - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/traverse>globals": true, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-environment-visitor": true, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name": true, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-hoist-variables": true, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-split-export-declaration": true, - "nock>debug": true - } - }, - "lavamoat-viz>lavamoat-core>lavamoat-tofu>@babel/traverse>@babel/types": { - "globals": { - "console.warn": true, - "process.env": true - }, - "packages": { - "@babel/core>@babel/types>@babel/helper-string-parser": true, - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true - } - }, - "lavamoat>@lavamoat/aa": { - "builtin": { - "node:fs.lstatSync": true, - "node:fs.readFileSync": true, - "node:fs.realpathSync": true, - "node:path.dirname": true, - "node:path.join": true, - "node:path.relative": true - }, - "packages": { - "depcheck>resolve": true - } - }, - "lavamoat>lavamoat-core>merge-deep": { - "packages": { - "gulp-zip>plugin-error>arr-union": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep": true, - "lavamoat>lavamoat-core>merge-deep>kind-of": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep": { - "packages": { - "@babel/register>clone-deep>is-plain-object": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>for-own": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>lazy-cache": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone": true, - "lavamoat>lavamoat-core>merge-deep>kind-of": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep>for-own": { - "packages": { - "gulp>undertaker>object.reduce>for-own>for-in": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep>lazy-cache": { - "globals": { - "process.env.TRAVIS": true, - "process.env.UNLAZY": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone": { - "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>kind-of": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>lazy-cache": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>mixin-object": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>kind-of": { - "globals": { - "Buffer": true - }, - "packages": { - "browserify>insert-module-globals>is-buffer": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>lazy-cache": { - "globals": { - "process.env.UNLAZY": true - } - }, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>mixin-object": { - "packages": { - "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, - "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>mixin-object>for-in": true - } - }, - "lavamoat>lavamoat-core>merge-deep>kind-of": { - "packages": { - "browserify>insert-module-globals>is-buffer": true - } - }, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name": { - "packages": { - "@babel/core>@babel/template": true, - "@babel/core>@babel/types": true - } - }, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-hoist-variables": { - "packages": { - "@babel/core>@babel/types": true - } - }, - "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-split-export-declaration": { - "packages": { - "@babel/core>@babel/types": true - } - }, - "lodash": { - "globals": { - "define": true - } - }, - "loose-envify": { - "builtin": { - "stream.PassThrough": true, - "stream.Transform": true, - "util.inherits": true - }, - "globals": { - "process.env": true - }, - "packages": { - "loose-envify>js-tokens": true - } - }, - "mocha>find-up": { - "builtin": { - "path.dirname": true, - "path.parse": true, - "path.resolve": true - }, - "packages": { - "mocha>find-up>locate-path": true, - "nyc>find-up>path-exists": true - } - }, - "mocha>find-up>locate-path": { - "builtin": { - "fs.lstat": true, - "fs.lstatSync": true, - "fs.stat": true, - "fs.statSync": true, - "path.resolve": true, - "util.promisify": true - }, - "globals": { - "process.cwd": true - }, - "packages": { - "mocha>find-up>locate-path>p-locate": true - } - }, - "mocha>find-up>locate-path>p-locate": { - "packages": { - "@storybook/test-runner>jest-circus>p-limit": true - } - }, - "mocha>log-symbols": { - "packages": { - "chalk": true, - "mocha>log-symbols>is-unicode-supported": true - } - }, - "mocha>log-symbols>is-unicode-supported": { - "globals": { - "process.env.CI": true, - "process.env.TERM": true, - "process.env.TERM_PROGRAM": true, - "process.env.WT_SESSION": true, - "process.platform": true - } - }, - "mocha>supports-color": { - "builtin": { - "os.release": true, - "tty.isatty": true - }, - "globals": { - "process.env": true, - "process.platform": true - }, - "packages": { - "mocha>supports-color>has-flag": true - } - }, - "mocha>supports-color>has-flag": { - "globals": { - "process.argv": true - } - }, - "mockttp>portfinder>mkdirp": { - "builtin": { - "fs": true, - "path.dirname": true, - "path.resolve": true - } - }, - "nock>debug": { - "builtin": { - "tty.isatty": true, - "util.deprecate": true, - "util.formatWithOptions": true, - "util.inspect": true - }, - "globals": { - "console": true, - "document": true, - "localStorage": true, - "navigator": true, - "process": true - }, - "packages": { - "mocha>supports-color": true, - "nock>debug>ms": true - } - }, - "node-sass": { - "native": true - }, - "nyc>convert-source-map": { - "builtin": { - "fs.readFileSync": true, - "path.join": true - }, - "globals": { - "Buffer.from": true - } - }, - "nyc>find-up>path-exists": { - "builtin": { - "fs.access": true, - "fs.accessSync": true, - "util.promisify": true - } - }, - "nyc>glob": { - "builtin": { - "assert": true, - "events.EventEmitter": true, - "fs": true, - "path.join": true, - "path.resolve": true, - "util": true - }, - "globals": { - "console.error": true, - "process.cwd": true, - "process.nextTick": true, - "process.platform": true - }, - "packages": { - "@metamask/object-multiplex>once": true, - "eslint>minimatch": true, - "gulp-watch>path-is-absolute": true, - "nyc>glob>fs.realpath": true, - "nyc>glob>inflight": true, - "pumpify>inherits": true - } - }, - "nyc>glob>fs.realpath": { - "builtin": { - "fs.lstat": true, - "fs.lstatSync": true, - "fs.readlink": true, - "fs.readlinkSync": true, - "fs.realpath": true, - "fs.realpathSync": true, - "fs.stat": true, - "fs.statSync": true, - "path.normalize": true, - "path.resolve": true - }, - "globals": { - "console.error": true, - "console.trace": true, - "process.env.NODE_DEBUG": true, - "process.nextTick": true, - "process.noDeprecation": true, - "process.platform": true, - "process.throwDeprecation": true, - "process.traceDeprecation": true, - "process.version": true - } - }, - "nyc>glob>inflight": { - "globals": { - "process.nextTick": true - }, - "packages": { - "@metamask/object-multiplex>once": true, - "@metamask/object-multiplex>once>wrappy": true - } - }, - "nyc>resolve-from": { - "builtin": { - "fs.realpathSync": true, - "module._nodeModulePaths": true, - "module._resolveFilename": true, - "path.join": true, - "path.resolve": true - } - }, - "nyc>signal-exit": { - "builtin": { - "assert.equal": true, - "events": true - }, - "globals": { - "process": true - } - }, - "nyc>spawn-wrap>is-windows": { - "globals": { - "define": true, - "isWindows": "write", - "process": true - } - }, - "nyc>yargs>set-blocking": { - "globals": { - "process.stderr": true, - "process.stdout": true - } - }, - "postcss": { - "builtin": { - "fs.existsSync": true, - "fs.readFileSync": true, - "path.dirname": true, - "path.isAbsolute": true, - "path.join": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true, - "url.fileURLToPath": true, - "url.pathToFileURL": true - }, - "globals": { - "Buffer": true, - "URL": true, + "__filename": true, + "accessPrivateIdentifier": true, + "addEmitFlags": true, + "addEmitHelper": true, + "addEmitHelpers": true, + "addInternalEmitFlags": true, + "addSyntheticLeadingComment": true, + "addSyntheticTrailingComment": true, + "advancedAsyncSuperHelper": true, + "affectsDeclarationPathOptionDeclarations": true, + "affectsEmitOptionDeclarations": true, + "allKeysStartWithDot": true, + "assertDoc": true, + "assignHelper": true, + "asyncDelegator": true, + "asyncGeneratorHelper": true, + "asyncSuperHelper": true, + "asyncValues": true, "atob": true, + "awaitHelper": true, + "awaiterHelper": true, + "bindSourceFile": true, + "breakIntoCharacterSpans": true, + "breakIntoWordSpans": true, "btoa": true, - "console": true, - "process.env.LANG": true, - "process.env.NODE_ENV": true - }, - "packages": { - "postcss>nanoid": true, - "postcss>picocolors": true, - "postcss>source-map-js": true - } - }, - "postcss-discard-font-face": { - "packages": { - "postcss-discard-font-face>balanced-match": true, - "postcss-discard-font-face>postcss": true - } - }, - "postcss-discard-font-face>postcss": { - "builtin": { - "fs": true, - "path": true - }, - "globals": { - "console": true - }, - "packages": { - "postcss-discard-font-face>postcss>chalk": true, - "postcss-discard-font-face>postcss>js-base64": true, - "postcss-discard-font-face>postcss>source-map": true, - "postcss-discard-font-face>postcss>supports-color": true - } - }, - "postcss-discard-font-face>postcss>chalk": { - "globals": { - "process.env.TERM": true, - "process.platform": true - }, - "packages": { - "postcss-discard-font-face>postcss>chalk>ansi-styles": true, - "postcss-discard-font-face>postcss>chalk>escape-string-regexp": true, - "postcss-discard-font-face>postcss>chalk>strip-ansi": true, - "postcss-discard-font-face>postcss>chalk>supports-color": true, - "prettier-eslint>loglevel-colored-level-prefix>chalk>has-ansi": true - } - }, - "postcss-discard-font-face>postcss>chalk>strip-ansi": { - "packages": { - "postcss-discard-font-face>postcss>chalk>strip-ansi>ansi-regex": true - } - }, - "postcss-discard-font-face>postcss>chalk>supports-color": { - "globals": { - "process.argv": true, - "process.env": true, - "process.platform": true, - "process.stdout": true - } - }, - "postcss-discard-font-face>postcss>js-base64": { - "globals": { - "Base64": "write", - "define": true - } - }, - "postcss-discard-font-face>postcss>supports-color": { - "globals": { - "process": true - }, - "packages": { - "postcss-discard-font-face>postcss>supports-color>has-flag": true - } - }, - "postcss-discard-font-face>postcss>supports-color>has-flag": { - "globals": { - "process.argv": true - } - }, - "postcss-rtlcss": { - "globals": { - "SuppressedError": true - }, - "packages": { - "postcss": true, - "postcss-rtlcss>rtlcss": true - } - }, - "postcss-rtlcss>rtlcss": { - "packages": { - "postcss": true - } - }, - "postcss>picocolors": { - "globals": { - "process": true - } - }, - "postcss>source-map-js": { - "globals": { - "console": true - } - }, - "prettier": { - "builtin": { - "assert": true, - "events.EventEmitter": true, - "fs": true, - "module._nodeModulePaths": true, - "module._resolveFilename": true, - "os": true, - "path": true, - "stream.PassThrough": true, - "stream.Readable": true, - "util.inherits": true, - "util.inspect": true, - "util.promisify": true - }, - "globals": { - "ANONYMOUS": true, - "Buffer": true, - "BuilderFileEmit": true, - "BuilderProgramKind": true, - "BuilderState": true, - "CheckMode": true, - "ClassificationType": true, - "ClassificationTypeNames": true, - "CompletionInfoFlags": true, - "CompletionTriggerKind": true, - "ConfigFileProgramReloadLevel": true, - "CoreServicesShimHostAdapter": true, - "DocumentHighlights": true, - "Element": true, - "EndOfLineState": true, - "ExportKind": true, - "FileSystemEntryKind": true, - "FileWatcherEventKind": true, - "FlattenLevel": true, - "ForegroundColorEscapeSequences": true, - "HTMLElement": true, - "HighlightSpanKind": true, - "ImportKind": true, - "IndentStyle": true, - "InlayHintKind": true, - "Intl": true, - "InvalidatedProjectKind": true, - "LanguageServiceMode": true, - "LanguageServiceShimHostAdapter": true, - "ModuleInstanceState": true, - "NodeResolutionFeatures": true, - "OrganizeImportsMode": true, - "OutliningSpanKind": true, - "OutputFileType": true, - "PackageJsonAutoImportPreference": true, - "PackageJsonDependencyGroup": true, - "PatternMatchKind": true, - "PollingInterval": true, - "PrivateIdentifierKind": true, - "ProcessLevel": true, - "QuotePreference": true, - "SVGElement": true, - "ScriptElementKind": true, - "ScriptElementKindModifier": true, - "ScriptSnapshot": true, - "SemanticClassificationFormat": true, - "SemanticMeaning": true, - "SemicolonPreference": true, - "SignatureCheckMode": true, - "SymbolDisplayPartKind": true, - "TokenClass": true, - "TypeFacts": true, - "TypeScriptServicesFactory": true, - "UpToDateStatusType": true, - "Version": true, - "VersionRange": true, - "WatchLogLevel": true, - "WatchType": true, - "WorkerGlobalScope": true, - "YAML_SILENCE_DEPRECATION_WARNINGS": true, - "YAML_SILENCE_WARNINGS": true, - "__dirname": true, - "__filename": true, - "accessPrivateIdentifier": true, - "addEmitFlags": true, - "addEmitHelper": true, - "addEmitHelpers": true, - "addInternalEmitFlags": true, - "addSyntheticLeadingComment": true, - "addSyntheticTrailingComment": true, - "advancedAsyncSuperHelper": true, - "affectsDeclarationPathOptionDeclarations": true, - "affectsEmitOptionDeclarations": true, - "allKeysStartWithDot": true, - "assertDoc": true, - "assignHelper": true, - "asyncDelegator": true, - "asyncGeneratorHelper": true, - "asyncSuperHelper": true, - "asyncValues": true, - "atob": true, - "awaitHelper": true, - "awaiterHelper": true, - "bindSourceFile": true, - "breakIntoCharacterSpans": true, - "breakIntoWordSpans": true, - "btoa": true, - "buildLinkParts": true, - "buildOpts": true, - "buildOverload": true, - "bundlerModuleNameResolver": true, - "canBeConvertedToAsync": true, - "canJsonReportNoInputFiles": true, - "canProduceDiagnostics": true, - "canWatchDirectoryOrFile": true, - "chainBundle": true, - "changeCompilerHostLikeToUseCache": true, - "classPrivateFieldGetHelper": true, - "classPrivateFieldInHelper": true, - "classPrivateFieldSetHelper": true, - "classicNameResolver": true, - "cleanExtendedConfigCache": true, - "clearSharedExtendedConfigFileWatcher": true, - "clearTimeout": true, - "climbPastPropertyAccess": true, - "climbPastPropertyOrElementAccess": true, - "cloneCompilerOptions": true, - "closeFileWatcherOf": true, - "collectExternalModuleInfo": true, - "commonOptionsWithBuild": true, - "compareEmitHelpers": true, - "comparePatternKeys": true, - "compileOnSaveCommandLineOption": true, - "compilerOptionsDidYouMeanDiagnostics": true, - "compilerOptionsIndicateEsModules": true, - "computeCommonSourceDirectoryOfFilenames": true, - "computeSignature": true, - "computeSignatureWithDiagnostics": true, - "computeSuggestionDiagnostics": true, + "buildLinkParts": true, + "buildOpts": true, + "buildOverload": true, + "bundlerModuleNameResolver": true, + "canBeConvertedToAsync": true, + "canJsonReportNoInputFiles": true, + "canProduceDiagnostics": true, + "canWatchDirectoryOrFile": true, + "chainBundle": true, + "changeCompilerHostLikeToUseCache": true, + "classPrivateFieldGetHelper": true, + "classPrivateFieldInHelper": true, + "classPrivateFieldSetHelper": true, + "classicNameResolver": true, + "cleanExtendedConfigCache": true, + "clearSharedExtendedConfigFileWatcher": true, + "clearTimeout": true, + "climbPastPropertyAccess": true, + "climbPastPropertyOrElementAccess": true, + "cloneCompilerOptions": true, + "closeFileWatcherOf": true, + "collectExternalModuleInfo": true, + "commonOptionsWithBuild": true, + "compareEmitHelpers": true, + "comparePatternKeys": true, + "compileOnSaveCommandLineOption": true, + "compilerOptionsDidYouMeanDiagnostics": true, + "compilerOptionsIndicateEsModules": true, + "computeCommonSourceDirectoryOfFilenames": true, + "computeSignature": true, + "computeSignatureWithDiagnostics": true, + "computeSuggestionDiagnostics": true, "console": true, "consumesNodeCoreModules": true, "convertCompilerOptionsForTelemetry": true, @@ -7449,208 +6163,1361 @@ "zipToModeAwareCache": true } }, - "prettier-eslint>loglevel-colored-level-prefix>chalk>has-ansi": { + "eslint-plugin-prettier>prettier-linter-helpers": { + "packages": { + "eslint-plugin-prettier>prettier-linter-helpers>fast-diff": true + } + }, + "process": { + "globals": { + "process": true + } + }, + "vinyl>cloneable-readable>process-nextick-args": { + "globals": { + "process.nextTick": true, + "process.version": true + } + }, + "readable-stream-2>process-nextick-args": { + "globals": { + "process": true + } + }, + "vinyl>cloneable-readable>through2>readable-stream>process-nextick-args": { + "globals": { + "process": true + } + }, + "prop-types": { + "globals": { + "console": true, + "process.env.NODE_ENV": true + }, + "packages": { + "react>object-assign": true, + "prop-types>react-is": true + } + }, + "pumpify>pump": { + "builtin": { + "fs": true + }, + "globals": { + "process.version": true + }, + "packages": { + "duplexify>end-of-stream": true, + "@metamask/object-multiplex>once": true + } + }, + "gulp>vinyl-fs>glob-stream>pumpify>pump": { + "builtin": { + "fs": true + }, + "packages": { + "duplexify>end-of-stream": true, + "@metamask/object-multiplex>once": true + } + }, + "gulp>vinyl-fs>pumpify>pump": { + "builtin": { + "fs": true + }, + "packages": { + "duplexify>end-of-stream": true, + "@metamask/object-multiplex>once": true + } + }, + "pumpify": { + "packages": { + "duplexify": true, + "pumpify>inherits": true, + "pumpify>pump": true + } + }, + "gulp>vinyl-fs>glob-stream>pumpify": { + "packages": { + "gulp>vinyl-fs>glob-stream>pumpify>duplexify": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>glob-stream>pumpify>pump": true + } + }, + "gulp>vinyl-fs>pumpify": { + "packages": { + "gulp>vinyl-fs>pumpify>duplexify": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>pumpify>pump": true + } + }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic": { + "packages": { + "gulp>undertaker>bach>array-last>is-number": true, + "@babel/register>clone-deep>kind-of": true, + "gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic>math-random": true + } + }, + "randomcolor": { + "globals": { + "define": true + } + }, + "gulp-livereload>tiny-lr>body>raw-body": { + "globals": { + "Buffer.concat": true, + "process.nextTick": true + }, + "packages": { + "gulp-livereload>tiny-lr>body>raw-body>bytes": true, + "gulp-livereload>tiny-lr>body>raw-body>string_decoder": true + } + }, + "prop-types>react-is": { + "globals": { + "console": true, + "process.env.NODE_ENV": true + } + }, + "browserify>read-only-stream": { + "packages": { + "browserify>read-only-stream>readable-stream": true + } + }, + "readable-stream": { + "builtin": { + "buffer.Buffer": true, + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.env.READABLE_STREAM": true, + "process.nextTick": true, + "process.stderr": true, + "process.stdout": true + }, + "packages": { + "pumpify>inherits": true, + "browserify>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "@lavamoat/lavapack>readable-stream": { + "builtin": { + "buffer.Blob": true, + "buffer.Buffer": true, + "events.EventEmitter": true, + "events.addAbortListener": true, + "stream": true, + "string_decoder.StringDecoder": true + }, + "globals": { + "AbortController": true, + "AbortSignal": true, + "AggregateError": true, + "Blob": true, + "ERR_INVALID_ARG_TYPE": true, + "process.env.READABLE_STREAM": true, + "queueMicrotask": true + }, + "packages": { + "@lavamoat/lavapack>readable-stream>abort-controller": true, + "process": true + } + }, + "vinyl-buffer>bl>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "vinyl-buffer>bl>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "vinyl-buffer>bl>readable-stream>safe-buffer": true, + "vinyl-buffer>bl>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>readable-stream>safe-buffer": true, + "browserify>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>concat-stream>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>concat-stream>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>concat-stream>readable-stream>safe-buffer": true, + "browserify>concat-stream>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "lavamoat-browserify>concat-stream>readable-stream": { + "builtin": { + "buffer.Buffer": true, + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.env.READABLE_STREAM": true, + "process.nextTick": true, + "process.stderr": true, + "process.stdout": true + }, + "packages": { + "pumpify>inherits": true, + "browserify>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>duplexer2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>duplexer2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>duplexer2>readable-stream>safe-buffer": true, + "browserify>duplexer2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>safe-buffer": true, + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>glob-stream>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>glob-stream>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>glob-stream>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>glob-stream>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp-watch>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp-watch>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp-watch>readable-stream>safe-buffer": true, + "gulp-watch>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "lavamoat-browserify>readable-stream": { + "builtin": { + "buffer.Blob": true, + "buffer.Buffer": true, + "events.EventEmitter": true, + "events.addAbortListener": true, + "stream": true, + "string_decoder.StringDecoder": true + }, + "globals": { + "AbortController": true, + "AbortSignal": true, + "AggregateError": true, + "Blob": true, + "ERR_INVALID_ARG_TYPE": true, + "process.env.READABLE_STREAM": true, + "queueMicrotask": true + }, + "packages": { + "@lavamoat/lavapack>readable-stream>abort-controller": true, + "process": true + } + }, + "gulp>vinyl-fs>lazystream>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>lazystream>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>lazystream>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>lazystream>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>module-deps>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>module-deps>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>module-deps>readable-stream>safe-buffer": true, + "browserify>module-deps>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>read-only-stream>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>read-only-stream>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>read-only-stream>readable-stream>safe-buffer": true, + "browserify>read-only-stream>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>module-deps>stream-combiner2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>module-deps>stream-combiner2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>module-deps>stream-combiner2>readable-stream>safe-buffer": true, + "browserify>module-deps>stream-combiner2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "labeled-stream-splicer>stream-splicer>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "labeled-stream-splicer>stream-splicer>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "labeled-stream-splicer>stream-splicer>readable-stream>safe-buffer": true, + "labeled-stream-splicer>stream-splicer>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>safe-buffer": true, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>browser-pack>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>browser-pack>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>browser-pack>through2>readable-stream>safe-buffer": true, + "browserify>browser-pack>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "vinyl>cloneable-readable>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "vinyl>cloneable-readable>through2>readable-stream>isarray": true, + "vinyl>cloneable-readable>through2>readable-stream>process-nextick-args": true, + "vinyl>cloneable-readable>through2>readable-stream>safe-buffer": true, + "vinyl>cloneable-readable>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>deps-sort>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>deps-sort>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>deps-sort>through2>readable-stream>safe-buffer": true, + "browserify>deps-sort>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp-sort>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp-sort>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp-sort>through2>readable-stream>safe-buffer": true, + "gulp-sort>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp-sourcemaps>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp-sourcemaps>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp-sourcemaps>through2>readable-stream>safe-buffer": true, + "gulp-sourcemaps>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>insert-module-globals>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "browserify>insert-module-globals>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "browserify>insert-module-globals>through2>readable-stream>safe-buffer": true, + "browserify>insert-module-globals>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>to-through>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>to-through>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>to-through>through2>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>to-through>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "vinyl-source-stream>through2>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "vinyl-source-stream>through2>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "vinyl-source-stream>through2>readable-stream>safe-buffer": true, + "vinyl-source-stream>through2>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "gulp>vinyl-fs>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "readable-stream-2>core-util-is": true, + "pumpify>inherits": true, + "gulp>vinyl-fs>readable-stream>isarray": true, + "readable-stream-2>process-nextick-args": true, + "gulp>vinyl-fs>readable-stream>safe-buffer": true, + "gulp>vinyl-fs>readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "chokidar>readdirp": { + "builtin": { + "fs": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true, + "stream.Readable": true, + "util.promisify": true + }, + "globals": { + "process.platform": true, + "process.versions.node.split": true + }, + "packages": { + "chokidar>anymatch>picomatch": true + } + }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": { + "globals": { + "define": true + } + }, + "@babel/preset-env>@babel/plugin-transform-regenerator>regenerator-transform": { + "builtin": { + "assert": true, + "util.inherits": true + }, + "packages": { + "@babel/runtime": true + } + }, + "gulp-watch>anymatch>micromatch>regex-cache": { + "packages": { + "gulp-watch>anymatch>micromatch>regex-cache>is-equal-shallow": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>regex-not": { + "packages": { + "gulp-zip>plugin-error>extend-shallow": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": true + } + }, + "string.prototype.matchall>regexp.prototype.flags": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": true + } + }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": { + "globals": { + "characterClassItem.kind": true + }, + "packages": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsgen": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsparser": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-value-ecmascript": true + } + }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsgen": { + "globals": { + "define": true + } + }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsparser": { + "globals": { + "regjsparser": "write" + } + }, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse": { + "packages": { + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>collapse-white-space": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true, + "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-word-character": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true, + "react-syntax-highlighter>refractor>parse-entities": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>state-toggle": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>trim-trailing-lines": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>trim": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unist-util-remove-position": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>vfile-location": true, + "watchify>xtend": true + } + }, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify": { + "packages": { + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>is-alphanumeric": true, + "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>longest-streak": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>markdown-table": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>mdast-util-compact": true, + "react-syntax-highlighter>refractor>parse-entities": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>state-toggle": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": true, + "watchify>xtend": true + } + }, + "stylelint>@stylelint/postcss-markdown>remark": { + "packages": { + "stylelint>@stylelint/postcss-markdown>remark>remark-parse": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify": true, + "react-markdown>unified": true + } + }, + "gulp>vinyl-fs>remove-bom-buffer": { + "packages": { + "browserify>insert-module-globals>is-buffer": true, + "gulp>vinyl-fs>remove-bom-buffer>is-utf8": true + } + }, + "gulp>vinyl-fs>remove-bom-stream": { + "packages": { + "gulp>vinyl-fs>remove-bom-buffer": true, + "koa>content-disposition>safe-buffer": true, + "gulp>vinyl-fs>remove-bom-stream>through2": true + } + }, + "vinyl>remove-trailing-separator": { + "globals": { + "process.platform": true + } + }, + "gulp-sass>replace-ext": { + "builtin": { + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.sep": true + } + }, + "react-markdown>vfile>replace-ext": { + "builtin": { + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.join": true + } + }, + "vinyl>replace-ext": { + "builtin": { + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.join": true, + "path.sep": true + } + }, + "gulp-watch>vinyl-file>vinyl>replace-ext": { + "builtin": { + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.join": true + } + }, + "yargs>require-directory": { + "builtin": { + "fs.readdirSync": true, + "fs.statSync": true, + "path.dirname": true, + "path.join": true, + "path.resolve": true + } + }, + "eslint>@eslint/eslintrc>import-fresh>resolve-from": { + "builtin": { + "fs.realpathSync": true, + "module._nodeModulePaths": true, + "module._resolveFilename": true, + "path.join": true, + "path.resolve": true + } + }, + "nyc>resolve-from": { + "builtin": { + "fs.realpathSync": true, + "module._nodeModulePaths": true, + "module._resolveFilename": true, + "path.join": true, + "path.resolve": true + } + }, + "gulp>vinyl-fs>resolve-options": { "packages": { - "prettier-eslint>loglevel-colored-level-prefix>chalk>has-ansi>ansi-regex": true + "gulp>vinyl-fs>value-or-function": true } }, - "prop-types": { + "depcheck>resolve": { + "builtin": { + "fs.readFile": true, + "fs.readFileSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.stat": true, + "fs.statSync": true, + "os.homedir": true, + "path.dirname": true, + "path.join": true, + "path.parse": true, + "path.relative": true, + "path.resolve": true + }, "globals": { - "console": true, - "process.env.NODE_ENV": true + "process.env.HOME": true, + "process.env.HOMEDRIVE": true, + "process.env.HOMEPATH": true, + "process.env.LNAME": true, + "process.env.LOGNAME": true, + "process.env.USER": true, + "process.env.USERNAME": true, + "process.env.USERPROFILE": true, + "process.getuid": true, + "process.nextTick": true, + "process.platform": true, + "process.versions.pnp": true }, "packages": { - "prop-types>react-is": true, - "react>object-assign": true + "depcheck>is-core-module": true, + "depcheck>resolve>path-parse": true } }, - "prop-types>react-is": { + "eslint-plugin-react>resolve": { + "builtin": { + "fs.readFile": true, + "fs.readFileSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.stat": true, + "fs.statSync": true, + "os.homedir": true, + "path.dirname": true, + "path.join": true, + "path.parse": true, + "path.relative": true, + "path.resolve": true + }, "globals": { - "console": true, - "process.env.NODE_ENV": true + "process.env.HOME": true, + "process.env.HOMEDRIVE": true, + "process.env.HOMEPATH": true, + "process.env.LNAME": true, + "process.env.LOGNAME": true, + "process.env.USER": true, + "process.env.USERNAME": true, + "process.env.USERPROFILE": true, + "process.getuid": true, + "process.nextTick": true, + "process.platform": true, + "process.versions.pnp": true + }, + "packages": { + "depcheck>is-core-module": true, + "depcheck>resolve>path-parse": true } }, - "pumpify": { + "del>rimraf": { + "builtin": { + "assert": true, + "fs": true, + "path.join": true + }, + "globals": { + "process.platform": true, + "setTimeout": true + }, "packages": { - "duplexify": true, - "pumpify>inherits": true, - "pumpify>pump": true + "nyc>glob": true } }, - "pumpify>inherits": { + "stylelint>file-entry-cache>flat-cache>rimraf": { "builtin": { - "util.inherits": true + "assert": true, + "fs": true, + "path.join": true + }, + "globals": { + "process.platform": true, + "setTimeout": true + }, + "packages": { + "nyc>glob": true } }, - "pumpify>pump": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": { "builtin": { - "fs": true + "assert": true, + "fs": true, + "path.join": true }, "globals": { - "process.version": true + "process.platform": true, + "setTimeout": true }, "packages": { - "@metamask/object-multiplex>once": true, - "duplexify>end-of-stream": true + "nyc>glob": true } }, - "randomcolor": { + "postcss-rtlcss>rtlcss": { + "packages": { + "postcss": true + } + }, + "eslint>@nodelib/fs.walk>@nodelib/fs.scandir>run-parallel": { "globals": { - "define": true + "process.nextTick": true } }, - "react-markdown>unified": { - "packages": { - "mocha>yargs-unparser>is-plain-obj": true, - "react-markdown>unified>bail": true, - "react-markdown>unified>extend": true, - "react-markdown>unified>is-buffer": true, - "react-markdown>unified>trough": true, - "react-markdown>vfile": true + "wait-on>rxjs": { + "globals": { + "cancelAnimationFrame": true, + "clearInterval": true, + "clearTimeout": true, + "performance": true, + "requestAnimationFrame": true, + "setInterval.apply": true, + "setTimeout.apply": true } }, - "react-markdown>unist-util-visit": { - "packages": { - "react-markdown>unist-util-visit>unist-util-visit-parents": true + "koa>content-disposition>safe-buffer": { + "builtin": { + "buffer": true } }, - "react-markdown>unist-util-visit>unist-util-visit-parents": { - "packages": { - "react-markdown>unist-util-visit>unist-util-is": true + "vinyl-buffer>bl>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "react-markdown>vfile": { + "browserify>readable-stream>safe-buffer": { "builtin": { - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.join": true, - "path.sep": true - }, - "globals": { - "process.cwd": true - }, - "packages": { - "react-markdown>vfile>is-buffer": true, - "react-markdown>vfile>replace-ext": true, - "react-markdown>vfile>vfile-message": true + "buffer": true } }, - "react-markdown>vfile>replace-ext": { + "browserify>concat-stream>readable-stream>safe-buffer": { "builtin": { - "path.basename": true, - "path.dirname": true, - "path.extname": true, - "path.join": true + "buffer": true } }, - "react-markdown>vfile>vfile-message": { - "packages": { - "react-markdown>vfile>unist-util-stringify-position": true + "browserify>duplexer2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "react-syntax-highlighter>refractor>parse-entities": { - "packages": { - "react-syntax-highlighter>refractor>parse-entities>character-entities": true, - "react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true, - "react-syntax-highlighter>refractor>parse-entities>character-reference-invalid": true, - "react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true, - "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, - "react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp>vinyl-fs>glob-stream>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp-watch>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp>vinyl-fs>lazystream>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "browserify>module-deps>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "browserify>read-only-stream>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "browserify>module-deps>stream-combiner2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "labeled-stream-splicer>stream-splicer>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "browserify>browser-pack>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "vinyl>cloneable-readable>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "browserify>deps-sort>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true + } + }, + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": { - "packages": { - "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true + "gulp-sort>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "readable-stream": { + "gulp-sourcemaps>through2>readable-stream>safe-buffer": { "builtin": { - "buffer.Buffer": true, - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.env.READABLE_STREAM": true, - "process.nextTick": true, - "process.stderr": true, - "process.stdout": true - }, - "packages": { - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true + "buffer": true } }, - "readable-stream-2>core-util-is": { - "globals": { - "Buffer.isBuffer": true + "browserify>insert-module-globals>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "readable-stream-2>process-nextick-args": { - "globals": { - "process": true + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "readable-stream>util-deprecate": { + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>safe-buffer": { "builtin": { - "util.deprecate": true + "buffer": true } }, - "resolve-url-loader>es6-iterator": { - "packages": { - "resolve-url-loader>es6-iterator>d": true, - "resolve-url-loader>es6-iterator>es5-ext": true, - "resolve-url-loader>es6-iterator>es6-symbol": true + "gulp>vinyl-fs>to-through>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "resolve-url-loader>es6-iterator>d": { - "packages": { - "resolve-url-loader>es6-iterator>d>type": true, - "resolve-url-loader>es6-iterator>es5-ext": true + "vinyl-source-stream>through2>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "resolve-url-loader>es6-iterator>es5-ext": { - "packages": { - "resolve-url-loader>es6-iterator>es6-symbol": true + "gulp>vinyl-fs>readable-stream>safe-buffer": { + "builtin": { + "buffer": true } }, - "resolve-url-loader>es6-iterator>es6-symbol": { - "packages": { - "resolve-url-loader>es6-iterator>d": true, - "resolve-url-loader>es6-iterator>es6-symbol>ext": true + "vinyl-buffer>bl>readable-stream>string_decoder>safe-buffer": { + "builtin": { + "buffer": true } }, - "resolve-url-loader>es6-iterator>es6-symbol>ext": { - "globals": { - "__global__": true + "browserify>browser-pack>through2>readable-stream>string_decoder>safe-buffer": { + "builtin": { + "buffer": true } }, - "resolve-url-loader>rework>css>source-map-resolve": { + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder>safe-buffer": { "builtin": { - "url.resolve": true - }, - "globals": { - "TextDecoder": true, - "setImmediate": true - }, - "packages": { - "gulp-sourcemaps>css>source-map-resolve>atob": true, - "gulp-sourcemaps>css>source-map-resolve>decode-uri-component": true, - "resolve-url-loader>rework>css>source-map-resolve>source-map-url": true, - "resolve-url-loader>rework>css>urix": true + "buffer": true } }, - "resolve-url-loader>rework>css>source-map-resolve>source-map-url": { - "globals": { - "define": true + "string.prototype.matchall>es-abstract>safe-regex-test": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>is-regex": true } }, - "resolve-url-loader>rework>css>urix": { - "builtin": { - "path.sep": true + "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex>ret": true } }, "sass": { @@ -7701,37 +7568,183 @@ "process.stderr.write": true }, "packages": { - "mocha>supports-color": true, "sass-embedded>@bufbuild/protobuf": true, "sass-embedded>buffer-builder": true, "sass-embedded>immutable": true, - "sass-embedded>varint": true, - "wait-on>rxjs": true + "wait-on>rxjs": true, + "mocha>supports-color": true, + "sass-embedded>varint": true } }, - "sass-embedded>@bufbuild/protobuf": { + "semver": { "globals": { - "TextDecoder": true, - "TextEncoder": true, - "__values": true, + "console.error": true, "process": true } }, - "sass-embedded>buffer-builder": { + "@babel/core>semver": { "globals": { - "Buffer": true + "console": true, + "process": true } }, - "sass-embedded>immutable": { + "@babel/eslint-parser>semver": { "globals": { "console": true, - "define": true + "process": true } }, - "semver": { + "@babel/core>@babel/helper-compilation-targets>semver": { + "globals": { + "console": true, + "process": true + } + }, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin>semver": { + "globals": { + "console": true, + "process": true + } + }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>semver": { + "globals": { + "console": true, + "process": true + } + }, + "@babel/preset-env>semver": { + "globals": { + "console": true, + "process": true + } + }, + "@babel/preset-env>babel-plugin-polyfill-corejs2>semver": { + "globals": { + "console": true, + "process": true + } + }, + "eslint-plugin-node>semver": { + "globals": { + "console": true, + "process": true + } + }, + "eslint-plugin-react>semver": { + "globals": { + "console": true, + "process": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": { + "globals": { + "console": true, + "process": true + } + }, + "nyc>yargs>set-blocking": { + "globals": { + "process.stderr": true, + "process.stdout": true + } + }, + "string.prototype.matchall>call-bind>set-function-length": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>gopd": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true + } + }, + "string.prototype.matchall>regexp.prototype.flags>set-function-name": { + "packages": { + "string.prototype.matchall>define-properties>define-data-property": true, + "string.prototype.matchall>call-bind>es-errors": true, + "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, + "string.prototype.matchall>es-abstract>has-property-descriptors": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value>extend-shallow": true, + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, + "@babel/register>clone-deep>is-plain-object": true, + "gulp>gulp-cli>matchdep>micromatch>braces>split-string": true + } + }, + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone": { + "packages": { + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>kind-of": true, + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>lazy-cache": true, + "lavamoat>lavamoat-core>merge-deep>clone-deep>shallow-clone>mixin-object": true + } + }, + "browserify>shasum-object": { + "builtin": { + "crypto.createHash": true + }, + "globals": { + "Buffer.isBuffer": true + }, + "packages": { + "@metamask/rpc-errors>fast-safe-stringify": true + } + }, + "string.prototype.matchall>side-channel": { + "packages": { + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true, + "string.prototype.matchall>es-abstract>object-inspect": true + } + }, + "nyc>signal-exit": { + "builtin": { + "assert.equal": true, + "events": true + }, + "globals": { + "process": true + } + }, + "stylelint>table>slice-ansi": { + "packages": { + "stylelint>table>slice-ansi>ansi-styles": true, + "stylelint>table>slice-ansi>astral-regex": true, + "stylelint>table>slice-ansi>is-fullwidth-code-point": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>define-property": true, + "gulp>gulp-cli>isobject": true, + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util>kind-of": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon": { + "builtin": { + "fs.readFileSync": true, + "path.dirname": true, + "util.inspect": true + }, "globals": { - "console.error": true, - "process": true + "__filename": true + }, + "packages": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>extend-shallow": true, + "gulp>gulp-cli>liftoff>fined>parse-filepath>map-cache": true, + "resolve-url-loader>rework>css>source-map-resolve": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>source-map": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>use": true } }, "source-map": { @@ -7748,134 +7761,152 @@ "fetch": true } }, - "string.prototype.matchall": { - "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>es-abstract": true, - "string.prototype.matchall>has-symbols": true, - "string.prototype.matchall>regexp.prototype.flags": true + "postcss>source-map-js": { + "globals": { + "console": true } }, - "string.prototype.matchall>call-bind": { + "gulp-sourcemaps>css>source-map-resolve": { + "builtin": { + "path.sep": true, + "url.resolve": true + }, + "globals": { + "TextDecoder": true, + "setImmediate": true + }, "packages": { - "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>call-bind>set-function-length": true, - "string.prototype.matchall>get-intrinsic": true + "gulp-sourcemaps>css>source-map-resolve>atob": true, + "gulp-sourcemaps>css>source-map-resolve>decode-uri-component": true } }, - "string.prototype.matchall>call-bind>es-define-property": { + "resolve-url-loader>rework>css>source-map-resolve": { + "builtin": { + "url.resolve": true + }, + "globals": { + "TextDecoder": true, + "setImmediate": true + }, "packages": { - "string.prototype.matchall>get-intrinsic": true + "gulp-sourcemaps>css>source-map-resolve>atob": true, + "gulp-sourcemaps>css>source-map-resolve>decode-uri-component": true, + "resolve-url-loader>rework>css>source-map-resolve>source-map-url": true, + "resolve-url-loader>rework>css>urix": true } }, - "string.prototype.matchall>call-bind>set-function-length": { + "terser>source-map-support": { + "builtin": { + "fs": true, + "path.dirname": true, + "path.resolve": true + }, + "globals": { + "XMLHttpRequest": true, + "console.error": true, + "process": true + }, "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>gopd": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true, - "string.prototype.matchall>get-intrinsic": true + "terser>source-map-support>buffer-from": true, + "terser>source-map-support>source-map": true } }, - "string.prototype.matchall>define-properties": { - "packages": { - "@lavamoat/lavapack>json-stable-stringify>object-keys": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "resolve-url-loader>rework>css>source-map-resolve>source-map-url": { + "globals": { + "define": true } }, - "string.prototype.matchall>define-properties>define-data-property": { + "eslint-plugin-jsdoc>spdx-expression-parse": { "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true + "eslint-plugin-jsdoc>spdx-expression-parse>spdx-exceptions": true, + "eslint-plugin-jsdoc>spdx-expression-parse>spdx-license-ids": true } }, - "string.prototype.matchall>es-abstract": { - "packages": { - "depcheck>is-core-module>hasown": true, - "eslint-plugin-react>array-includes>is-string": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>es-object-atoms": true, - "string.prototype.matchall>es-abstract>es-set-tostringtag": true, - "string.prototype.matchall>es-abstract>es-to-primitive": true, - "string.prototype.matchall>es-abstract>gopd": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true, - "string.prototype.matchall>es-abstract>has-proto": true, - "string.prototype.matchall>es-abstract>is-callable": true, - "string.prototype.matchall>es-abstract>is-regex": true, - "string.prototype.matchall>es-abstract>object-inspect": true, - "string.prototype.matchall>es-abstract>safe-regex-test": true, - "string.prototype.matchall>es-abstract>string.prototype.trim": true, - "string.prototype.matchall>get-intrinsic": true, - "string.prototype.matchall>has-symbols": true, - "string.prototype.matchall>internal-slot": true + "stylelint>specificity": { + "globals": { + "define": true } }, - "string.prototype.matchall>es-abstract>es-object-atoms": { + "gulp>gulp-cli>matchdep>micromatch>braces>split-string": { "packages": { - "string.prototype.matchall>call-bind>es-errors": true + "gulp-zip>plugin-error>extend-shallow": true } }, - "string.prototype.matchall>es-abstract>es-set-tostringtag": { + "gulp-livereload>event-stream>split": { + "builtin": { + "string_decoder.StringDecoder": true + }, "packages": { - "depcheck>is-core-module>hasown": true, - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>get-intrinsic": true + "debounce-stream>through": true } }, - "string.prototype.matchall>es-abstract>es-to-primitive": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend": { + "builtin": { + "util.inherits": true + }, "packages": { - "@metamask/eth-token-tracker>deep-equal>is-date-object": true, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": true, - "string.prototype.matchall>es-abstract>is-callable": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy": true } }, - "string.prototype.matchall>es-abstract>es-to-primitive>is-symbol": { + "browserify>module-deps>stream-combiner2": { "packages": { - "string.prototype.matchall>has-symbols": true + "browserify>duplexer2": true, + "browserify>module-deps>stream-combiner2>readable-stream": true } }, - "string.prototype.matchall>es-abstract>gopd": { + "gulp-livereload>event-stream>stream-combiner": { "packages": { - "string.prototype.matchall>get-intrinsic": true + "debounce-stream>duplexer": true } }, - "string.prototype.matchall>es-abstract>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true + "gulp>glob-watcher>async-done>stream-exhaust": { + "builtin": { + "stream.Writable": true, + "util.inherits": true + }, + "globals": { + "setImmediate": true } }, - "string.prototype.matchall>es-abstract>is-callable": { + "labeled-stream-splicer>stream-splicer": { "globals": { - "document": true + "process.nextTick": true, + "setImmediate": true + }, + "packages": { + "pumpify>inherits": true, + "labeled-stream-splicer>stream-splicer>readable-stream": true } }, - "string.prototype.matchall>es-abstract>is-regex": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": { "packages": { - "koa>is-generator-function>has-tostringtag": true, - "string.prototype.matchall>call-bind": true + "gulp>gulp-cli>yargs>string-width>code-point-at": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": true, + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": true } }, - "string.prototype.matchall>es-abstract>object-inspect": { - "builtin": { - "util.inspect": true - }, - "globals": { - "HTMLElement": true, - "WeakRef": true + "stylelint>table>string-width": { + "packages": { + "stylelint>table>string-width>emoji-regex": true, + "stylelint>table>slice-ansi>is-fullwidth-code-point": true, + "stylelint>table>string-width>strip-ansi": true } }, - "string.prototype.matchall>es-abstract>safe-regex-test": { + "yargs>string-width": { + "packages": { + "yargs>string-width>emoji-regex": true, + "yargs>string-width>is-fullwidth-code-point": true, + "eslint>strip-ansi": true + } + }, + "string.prototype.matchall": { "packages": { "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>is-regex": true + "string.prototype.matchall>define-properties": true, + "string.prototype.matchall>es-abstract": true, + "string.prototype.matchall>has-symbols": true, + "string.prototype.matchall>regexp.prototype.flags": true } }, "string.prototype.matchall>es-abstract>string.prototype.trim": { @@ -7886,821 +7917,684 @@ "string.prototype.matchall>es-abstract>es-object-atoms": true } }, - "string.prototype.matchall>get-intrinsic": { - "globals": { - "AggregateError": true, - "FinalizationRegistry": true, - "WeakRef": true - }, + "browserify>string_decoder": { "packages": { - "browserify>has>function-bind": true, - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>has-proto": true, - "string.prototype.matchall>has-symbols": true + "koa>content-disposition>safe-buffer": true } }, - "string.prototype.matchall>internal-slot": { + "gulp-livereload>tiny-lr>body>raw-body>string_decoder": { + "builtin": { + "buffer.Buffer": true + } + }, + "vinyl-buffer>bl>readable-stream>string_decoder": { "packages": { - "depcheck>is-core-module>hasown": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>side-channel": true + "vinyl-buffer>bl>readable-stream>string_decoder>safe-buffer": true } }, - "string.prototype.matchall>regexp.prototype.flags": { + "browserify>readable-stream>string_decoder": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties": true, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": true + "browserify>readable-stream>safe-buffer": true } }, - "string.prototype.matchall>regexp.prototype.flags>set-function-name": { + "browserify>concat-stream>readable-stream>string_decoder": { "packages": { - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>define-properties>define-data-property": true, - "string.prototype.matchall>es-abstract>function.prototype.name>functions-have-names": true, - "string.prototype.matchall>es-abstract>has-property-descriptors": true + "browserify>concat-stream>readable-stream>safe-buffer": true } }, - "string.prototype.matchall>side-channel": { + "browserify>duplexer2>readable-stream>string_decoder": { "packages": { - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract>object-inspect": true, - "string.prototype.matchall>get-intrinsic": true + "browserify>duplexer2>readable-stream>safe-buffer": true } }, - "stylelint": { - "builtin": { - "fs.lstatSync": true, - "fs.readFile": true, - "fs.readFileSync": true, - "fs.stat": true, - "os.EOL": true, - "path.dirname": true, - "path.isAbsolute": true, - "path.join": true, - "path.normalize": true, - "path.relative": true, - "path.resolve": true, - "path.sep": true, - "url.URL": true - }, - "globals": { - "__dirname": true, - "assert": true, - "console.warn": true, - "process.cwd": true, - "process.env.NODE_ENV": true, - "process.stdout.columns": true, - "process.stdout.isTTY": true - }, + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>string_decoder": { + "packages": { + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream>safe-buffer": true + } + }, + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>string_decoder": { "packages": { - "chalk": true, - "del>slash": true, - "eslint>ignore": true, - "eslint>imurmurhash": true, - "fast-glob>micromatch": true, - "globby": true, - "lodash": true, - "mocha>log-symbols": true, - "nock>debug": true, - "nyc>resolve-from": true, - "stylelint>@stylelint/postcss-css-in-js": true, - "stylelint>@stylelint/postcss-markdown": true, - "stylelint>autoprefixer": true, - "stylelint>balanced-match": true, - "stylelint>cosmiconfig": true, - "stylelint>execall": true, - "stylelint>file-entry-cache": true, - "stylelint>global-modules": true, - "stylelint>globjoin": true, - "stylelint>html-tags": true, - "stylelint>import-lazy": true, - "stylelint>known-css-properties": true, - "stylelint>leven": true, - "stylelint>mathml-tag-names": true, - "stylelint>normalize-selector": true, - "stylelint>postcss": true, - "stylelint>postcss-html": true, - "stylelint>postcss-less": true, - "stylelint>postcss-media-query-parser": true, - "stylelint>postcss-reporter": true, - "stylelint>postcss-resolve-nested-selector": true, - "stylelint>postcss-safe-parser": true, - "stylelint>postcss-sass": true, - "stylelint>postcss-scss": true, - "stylelint>postcss-selector-parser": true, - "stylelint>postcss-syntax": true, - "stylelint>postcss-value-parser": true, - "stylelint>specificity": true, - "stylelint>style-search": true, - "stylelint>sugarss": true, - "stylelint>svg-tags": true, - "stylelint>table": true, - "stylelint>write-file-atomic": true, - "yargs>string-width": true + "gulp>vinyl-fs>lead>flush-write-stream>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-css-in-js": { - "globals": { - "__dirname": true - }, + "gulp>vinyl-fs>glob-stream>readable-stream>string_decoder": { "packages": { - "@babel/core": true, - "stylelint>postcss": true, - "stylelint>postcss-syntax": true + "gulp>vinyl-fs>glob-stream>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown": { + "gulp-watch>readable-stream>string_decoder": { "packages": { - "stylelint>@stylelint/postcss-markdown>remark": true, - "stylelint>@stylelint/postcss-markdown>unist-util-find-all-after": true, - "stylelint>postcss-html": true, - "stylelint>postcss-syntax": true + "gulp-watch>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark": { + "gulp>vinyl-fs>lazystream>readable-stream>string_decoder": { "packages": { - "react-markdown>unified": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify": true + "gulp>vinyl-fs>lazystream>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse": { + "browserify>module-deps>readable-stream>string_decoder": { "packages": { - "react-syntax-highlighter>refractor>parse-entities": true, - "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>collapse-white-space": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-word-character": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>state-toggle": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>trim": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>trim-trailing-lines": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unist-util-remove-position": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>vfile-location": true, - "watchify>xtend": true + "browserify>module-deps>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": { + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>string_decoder": { "packages": { - "pumpify>inherits": true, - "watchify>xtend": true + "gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unist-util-remove-position": { + "browserify>read-only-stream>readable-stream>string_decoder": { "packages": { - "react-markdown>unist-util-visit": true + "browserify>read-only-stream>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify": { + "browserify>module-deps>stream-combiner2>readable-stream>string_decoder": { "packages": { - "react-syntax-highlighter>refractor>parse-entities": true, - "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>state-toggle": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>is-alphanumeric": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>longest-streak": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>markdown-table": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>mdast-util-compact": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities": true, - "watchify>xtend": true + "browserify>module-deps>stream-combiner2>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>markdown-table": { + "labeled-stream-splicer>stream-splicer>readable-stream>string_decoder": { "packages": { - "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true + "labeled-stream-splicer>stream-splicer>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>mdast-util-compact": { + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>string_decoder": { "packages": { - "react-markdown>unist-util-visit": true + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities": { + "browserify>browser-pack>through2>readable-stream>string_decoder": { "packages": { - "react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true, - "react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true, - "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, - "react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true, - "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities>character-entities-html4": true + "browserify>browser-pack>through2>readable-stream>string_decoder>safe-buffer": true } }, - "stylelint>@stylelint/postcss-markdown>unist-util-find-all-after": { + "vinyl>cloneable-readable>through2>readable-stream>string_decoder": { "packages": { - "stylelint>@stylelint/postcss-markdown>unist-util-find-all-after>unist-util-is": true + "vinyl>cloneable-readable>through2>readable-stream>safe-buffer": true } }, - "stylelint>autoprefixer": { - "globals": { - "console": true, - "process.cwd": true, - "process.env.AUTOPREFIXER_GRID": true - }, + "browserify>deps-sort>through2>readable-stream>string_decoder": { "packages": { - "autoprefixer>caniuse-lite": true, - "autoprefixer>normalize-range": true, - "browserslist": true, - "stylelint>autoprefixer>num2fraction": true, - "stylelint>postcss": true, - "stylelint>postcss-value-parser": true, - "stylelint>postcss>picocolors": true + "browserify>deps-sort>through2>readable-stream>safe-buffer": true } }, - "stylelint>cosmiconfig": { - "builtin": { - "fs": true, - "os": true, - "path": true - }, - "globals": { - "process.cwd": true - }, + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>string_decoder": { "packages": { - "depcheck>cosmiconfig>parse-json": true, - "eslint>@eslint/eslintrc>import-fresh": true, - "globby>dir-glob>path-type": true, - "stylelint>cosmiconfig>yaml": true + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream>safe-buffer": true } }, - "stylelint>cosmiconfig>yaml": { - "globals": { - "Buffer": true, - "YAML_SILENCE_DEPRECATION_WARNINGS": true, - "YAML_SILENCE_WARNINGS": true, - "atob": true, - "btoa": true, - "console.warn": true, - "process": true + "gulp-sort>through2>readable-stream>string_decoder": { + "packages": { + "gulp-sort>through2>readable-stream>safe-buffer": true } }, - "stylelint>execall": { + "gulp-sourcemaps>through2>readable-stream>string_decoder": { "packages": { - "stylelint>execall>clone-regexp": true + "gulp-sourcemaps>through2>readable-stream>safe-buffer": true } }, - "stylelint>execall>clone-regexp": { + "browserify>insert-module-globals>through2>readable-stream>string_decoder": { "packages": { - "stylelint>execall>clone-regexp>is-regexp": true + "browserify>insert-module-globals>through2>readable-stream>safe-buffer": true } }, - "stylelint>file-entry-cache": { - "builtin": { - "crypto.createHash": true, - "fs.readFileSync": true, - "fs.statSync": true, - "path.basename": true, - "path.dirname": true - }, + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder": { "packages": { - "stylelint>file-entry-cache>flat-cache": true + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream>string_decoder>safe-buffer": true } }, - "stylelint>file-entry-cache>flat-cache": { - "builtin": { - "fs.existsSync": true, - "fs.readFileSync": true, - "path.basename": true, - "path.dirname": true, - "path.resolve": true - }, - "globals": { - "__dirname": true - }, + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>string_decoder": { "packages": { - "stylelint>file-entry-cache>flat-cache>flatted": true, - "stylelint>file-entry-cache>flat-cache>rimraf": true, - "stylelint>file-entry-cache>flat-cache>write": true + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream>safe-buffer": true } }, - "stylelint>file-entry-cache>flat-cache>rimraf": { - "builtin": { - "assert": true, - "fs": true, - "path.join": true - }, - "globals": { - "process.platform": true, - "setTimeout": true - }, + "gulp>vinyl-fs>to-through>through2>readable-stream>string_decoder": { "packages": { - "nyc>glob": true + "gulp>vinyl-fs>to-through>through2>readable-stream>safe-buffer": true } }, - "stylelint>file-entry-cache>flat-cache>write": { - "builtin": { - "fs.createWriteStream": true, - "fs.writeFile": true, - "fs.writeFileSync": true, - "path.dirname": true - }, + "vinyl-source-stream>through2>readable-stream>string_decoder": { "packages": { - "mockttp>portfinder>mkdirp": true + "vinyl-source-stream>through2>readable-stream>safe-buffer": true } }, - "stylelint>global-modules": { - "builtin": { - "path.resolve": true - }, - "globals": { - "process.env.OSTYPE": true, - "process.platform": true - }, + "gulp>vinyl-fs>readable-stream>string_decoder": { "packages": { - "stylelint>global-modules>global-prefix": true + "gulp>vinyl-fs>readable-stream>safe-buffer": true } }, - "stylelint>global-modules>global-prefix": { - "builtin": { - "fs.readFileSync": true, - "fs.realpathSync": true, - "os.homedir": true, - "path.dirname": true, - "path.join": true, - "path.resolve": true - }, - "globals": { - "process.env.APPDATA": true, - "process.env.DESTDIR": true, - "process.env.OSTYPE": true, - "process.env.PREFIX": true, - "process.execPath": true, - "process.platform": true - }, + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities": { "packages": { - "stylelint>global-modules>global-prefix>ini": true, - "stylelint>global-modules>global-prefix>which": true + "stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities>character-entities-html4": true, + "react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true, + "react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true, + "react-syntax-highlighter>refractor>parse-entities>is-decimal": true, + "react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true } }, - "stylelint>global-modules>global-prefix>ini": { - "globals": { - "process": true + "postcss-discard-font-face>postcss>chalk>strip-ansi": { + "packages": { + "postcss-discard-font-face>postcss>chalk>strip-ansi>ansi-regex": true } }, - "stylelint>global-modules>global-prefix>which": { - "builtin": { - "path.join": true - }, - "globals": { - "process.cwd": true, - "process.env.OSTYPE": true, - "process.env.PATH": true, - "process.env.PATHEXT": true, - "process.platform": true - }, + "eslint>strip-ansi": { "packages": { - "@sentry/cli>which>isexe": true + "eslint>strip-ansi>ansi-regex": true } }, - "stylelint>globjoin": { - "builtin": { - "path.join": true + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": { + "packages": { + "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi>ansi-regex": true } }, - "stylelint>normalize-selector": { - "globals": { - "define": true + "stylelint>table>string-width>strip-ansi": { + "packages": { + "stylelint>table>string-width>strip-ansi>ansi-regex": true } }, - "stylelint>postcss": { - "builtin": { - "fs": true, - "path": true - }, - "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console": true, - "process.env.NODE_ENV": true - }, + "gulp-watch>vinyl-file>strip-bom-stream": { "packages": { - "stylelint>postcss>picocolors": true, - "stylelint>postcss>source-map": true + "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": true, + "gulp-watch>vinyl-file>strip-bom": true } }, - "stylelint>postcss-html": { + "gulp-watch>vinyl-file>strip-bom": { "globals": { - "__dirname": true + "Buffer.isBuffer": true }, "packages": { - "stylelint>postcss-html>htmlparser2": true, - "stylelint>postcss-syntax": true + "gulp>vinyl-fs>remove-bom-buffer>is-utf8": true } }, - "stylelint>postcss-html>htmlparser2": { + "stylelint": { "builtin": { - "buffer.Buffer": true, - "events.EventEmitter": true, - "string_decoder.StringDecoder": true + "fs.lstatSync": true, + "fs.readFile": true, + "fs.readFileSync": true, + "fs.stat": true, + "os.EOL": true, + "path.dirname": true, + "path.isAbsolute": true, + "path.join": true, + "path.normalize": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true, + "url.URL": true + }, + "globals": { + "__dirname": true, + "assert": true, + "console.warn": true, + "process.cwd": true, + "process.env.NODE_ENV": true, + "process.stdout.columns": true, + "process.stdout.isTTY": true }, "packages": { - "pumpify>inherits": true, - "readable-stream": true, - "stylelint>postcss-html>htmlparser2>domelementtype": true, - "stylelint>postcss-html>htmlparser2>domhandler": true, - "stylelint>postcss-html>htmlparser2>domutils": true, - "stylelint>postcss-html>htmlparser2>entities": true - } - }, - "stylelint>postcss-html>htmlparser2>domhandler": { - "packages": { - "stylelint>postcss-html>htmlparser2>domelementtype": true + "stylelint>@stylelint/postcss-css-in-js": true, + "stylelint>@stylelint/postcss-markdown": true, + "stylelint>autoprefixer": true, + "stylelint>balanced-match": true, + "chalk": true, + "stylelint>cosmiconfig": true, + "nock>debug": true, + "stylelint>execall": true, + "stylelint>file-entry-cache": true, + "stylelint>global-modules": true, + "globby": true, + "stylelint>globjoin": true, + "stylelint>html-tags": true, + "eslint>ignore": true, + "stylelint>import-lazy": true, + "eslint>imurmurhash": true, + "stylelint>known-css-properties": true, + "stylelint>leven": true, + "lodash": true, + "mocha>log-symbols": true, + "stylelint>mathml-tag-names": true, + "fast-glob>micromatch": true, + "stylelint>normalize-selector": true, + "stylelint>postcss-html": true, + "stylelint>postcss-less": true, + "stylelint>postcss-media-query-parser": true, + "stylelint>postcss-reporter": true, + "stylelint>postcss-resolve-nested-selector": true, + "stylelint>postcss-safe-parser": true, + "stylelint>postcss-sass": true, + "stylelint>postcss-scss": true, + "stylelint>postcss-selector-parser": true, + "stylelint>postcss-syntax": true, + "stylelint>postcss-value-parser": true, + "stylelint>postcss": true, + "nyc>resolve-from": true, + "del>slash": true, + "stylelint>specificity": true, + "yargs>string-width": true, + "stylelint>style-search": true, + "stylelint>sugarss": true, + "stylelint>svg-tags": true, + "stylelint>table": true, + "stylelint>write-file-atomic": true } }, - "stylelint>postcss-html>htmlparser2>domutils": { + "stylelint>sugarss": { "packages": { - "stylelint>postcss-html>htmlparser2>domelementtype": true, - "stylelint>postcss-html>htmlparser2>domutils>dom-serializer": true + "stylelint>sugarss>postcss": true } }, - "stylelint>postcss-html>htmlparser2>domutils>dom-serializer": { - "packages": { - "stylelint>postcss-html>htmlparser2>domelementtype": true, - "stylelint>postcss-html>htmlparser2>entities": true + "superstruct": { + "globals": { + "console.warn": true, + "define": true } }, - "stylelint>postcss-less": { + "chalk>supports-color": { + "builtin": { + "os.release": true, + "tty.isatty": true + }, + "globals": { + "process.env": true, + "process.platform": true + }, "packages": { - "stylelint>postcss-less>postcss": true + "chalk>supports-color>has-flag": true } }, - "stylelint>postcss-less>postcss": { + "gulp-livereload>chalk>supports-color": { "builtin": { - "fs": true, - "path": true + "os.release": true }, "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console": true, - "process.env.NODE_ENV": true + "process.env": true, + "process.platform": true, + "process.stderr": true, + "process.stdout": true, + "process.versions.node.split": true }, "packages": { - "stylelint>postcss-less>postcss>picocolors": true, - "stylelint>postcss-less>postcss>source-map": true + "gulp-livereload>chalk>supports-color>has-flag": true } }, - "stylelint>postcss-less>postcss>picocolors": { + "postcss-discard-font-face>postcss>chalk>supports-color": { + "globals": { + "process.argv": true, + "process.env": true, + "process.platform": true, + "process.stdout": true + } + }, + "mocha>supports-color": { "builtin": { + "os.release": true, "tty.isatty": true }, "globals": { - "process.argv.includes": true, "process.env": true, "process.platform": true + }, + "packages": { + "chalk>supports-color>has-flag": true } }, - "stylelint>postcss-reporter": { + "postcss-discard-font-face>postcss>supports-color": { + "globals": { + "process": true + }, "packages": { - "lodash": true + "postcss-discard-font-face>postcss>supports-color>has-flag": true } }, - "stylelint>postcss-safe-parser": { + "browserify>syntax-error": { "packages": { - "stylelint>postcss-safe-parser>postcss": true + "browserify>syntax-error>acorn-node": true } }, - "stylelint>postcss-safe-parser>postcss": { - "builtin": { - "fs": true, - "path": true + "stylelint>table": { + "globals": { + "process.stdout.write": true }, + "packages": { + "eslint>ajv": true, + "lodash": true, + "stylelint>table>slice-ansi": true, + "stylelint>table>string-width": true + } + }, + "terser": { "globals": { "Buffer": true, "atob": true, "btoa": true, - "console": true, - "process.env.NODE_ENV": true + "console.log": true, + "console.warn": true, + "define": true, + "process": true }, "packages": { - "stylelint>postcss-safe-parser>postcss>picocolors": true, - "stylelint>postcss-safe-parser>postcss>source-map": true - } - }, - "stylelint>postcss-safe-parser>postcss>picocolors": { - "builtin": { - "tty.isatty": true - }, - "globals": { - "process.argv.includes": true, - "process.env": true, - "process.platform": true + "terser>@jridgewell/source-map": true, + "jsdom>acorn": true } }, - "stylelint>postcss-sass": { + "through2": { "packages": { - "stylelint>postcss-sass>gonzales-pe": true, - "stylelint>postcss-sass>postcss": true + "readable-stream": true } }, - "stylelint>postcss-sass>gonzales-pe": { - "globals": { - "console.error": true, - "define": true + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter": { + "packages": { + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2": true, + "watchify>xtend": true } }, - "stylelint>postcss-sass>postcss": { + "gulp-sourcemaps>@gulp-sourcemaps/identity-map>through2": { "builtin": { - "fs": true, - "path": true + "util.inherits": true }, "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console": true, - "process.env.NODE_ENV": true + "process.nextTick": true }, "packages": { - "stylelint>postcss-sass>postcss>picocolors": true, - "stylelint>postcss-sass>postcss>source-map": true + "readable-stream": true } }, - "stylelint>postcss-sass>postcss>picocolors": { + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": { "builtin": { - "tty.isatty": true + "util.inherits": true }, "globals": { - "process.argv.includes": true, - "process.env": true, - "process.platform": true - } - }, - "stylelint>postcss-scss": { + "process.nextTick": true + }, "packages": { - "stylelint>postcss-scss>postcss": true + "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>postcss-scss>postcss": { + "bify-module-groups>through2": { "builtin": { - "fs": true, - "path": true + "util.inherits": true }, "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console": true, - "process.env.NODE_ENV": true + "process.nextTick": true }, "packages": { - "stylelint>postcss-scss>postcss>picocolors": true, - "stylelint>postcss-scss>postcss>source-map": true + "readable-stream": true } }, - "stylelint>postcss-scss>postcss>picocolors": { + "browserify>browser-pack>through2": { "builtin": { - "tty.isatty": true + "util.inherits": true }, "globals": { - "process.argv.includes": true, - "process.env": true, - "process.platform": true - } - }, - "stylelint>postcss-selector-parser": { + "process.nextTick": true + }, "packages": { - "readable-stream>util-deprecate": true, - "stylelint>postcss-selector-parser>cssesc": true + "browserify>browser-pack>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>postcss-syntax": { + "browserify>through2": { "builtin": { - "path.isAbsolute": true, - "path.resolve": true, - "path.sep": true + "util.inherits": true + }, + "globals": { + "process.nextTick": true }, "packages": { - "stylelint>postcss": true + "browserify>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>postcss>picocolors": { + "vinyl>cloneable-readable>through2": { "builtin": { - "tty.isatty": true + "util.inherits": true }, "globals": { - "process.argv.includes": true, - "process.env": true, - "process.platform": true + "process.nextTick": true + }, + "packages": { + "vinyl>cloneable-readable>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>specificity": { + "browserify>deps-sort>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "define": true - } - }, - "stylelint>sugarss": { + "process.nextTick": true + }, "packages": { - "stylelint>sugarss>postcss": true + "browserify>deps-sort>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>sugarss>postcss": { + "gulp>vinyl-fs>fs-mkdirp-stream>through2": { "builtin": { - "fs": true, - "path": true + "util.inherits": true }, "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console": true, - "process.env.NODE_ENV": true + "process.nextTick": true }, "packages": { - "stylelint>sugarss>postcss>picocolors": true, - "stylelint>sugarss>postcss>source-map": true + "gulp>vinyl-fs>fs-mkdirp-stream>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>sugarss>postcss>picocolors": { + "gulp-sort>through2": { "builtin": { - "tty.isatty": true + "util.inherits": true }, "globals": { - "process.argv.includes": true, - "process.env": true, - "process.platform": true + "process.nextTick": true + }, + "packages": { + "gulp-sort>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>table": { + "gulp-sourcemaps>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "process.stdout.write": true + "process.nextTick": true }, "packages": { - "eslint>ajv": true, - "lodash": true, - "stylelint>table>slice-ansi": true, - "stylelint>table>string-width": true + "gulp-sourcemaps>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>table>slice-ansi": { + "gulp-stylelint>through2": { + "builtin": { + "util.inherits": true + }, + "globals": { + "process.nextTick": true + }, "packages": { - "stylelint>table>slice-ansi>ansi-styles": true, - "stylelint>table>slice-ansi>astral-regex": true, - "stylelint>table>slice-ansi>is-fullwidth-code-point": true + "readable-stream": true } }, - "stylelint>table>slice-ansi>ansi-styles": { + "gulp-zip>through2": { + "builtin": { + "util.inherits": true + }, + "globals": { + "process.nextTick": true + }, "packages": { - "@metamask/jazzicon>color>color-convert": true + "readable-stream": true } }, - "stylelint>table>string-width": { + "browserify>insert-module-globals>through2": { + "builtin": { + "util.inherits": true + }, + "globals": { + "process.nextTick": true + }, "packages": { - "stylelint>table>slice-ansi>is-fullwidth-code-point": true, - "stylelint>table>string-width>emoji-regex": true, - "stylelint>table>string-width>strip-ansi": true + "browserify>insert-module-globals>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>table>string-width>strip-ansi": { + "browserify>module-deps>through2": { + "builtin": { + "util.inherits": true + }, + "globals": { + "process.nextTick": true + }, "packages": { - "stylelint>table>string-width>strip-ansi>ansi-regex": true + "browserify>module-deps>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>write-file-atomic": { + "gulp>vinyl-fs>remove-bom-stream>through2": { "builtin": { - "fs.chmod": true, - "fs.chmodSync": true, - "fs.chown": true, - "fs.chownSync": true, - "fs.close": true, - "fs.closeSync": true, - "fs.fsync": true, - "fs.fsyncSync": true, - "fs.open": true, - "fs.openSync": true, - "fs.realpath": true, - "fs.realpathSync": true, - "fs.rename": true, - "fs.renameSync": true, - "fs.stat": true, - "fs.statSync": true, - "fs.unlink": true, - "fs.unlinkSync": true, - "fs.write": true, - "fs.writeSync": true, - "path.resolve": true, - "util.promisify": true, - "worker_threads.threadId": true + "util.inherits": true }, "globals": { - "Buffer.isBuffer": true, - "__filename": true, - "process.getuid": true, - "process.pid": true + "process.nextTick": true }, "packages": { - "eslint>imurmurhash": true, - "nyc>signal-exit": true, - "stylelint>write-file-atomic>is-typedarray": true, - "stylelint>write-file-atomic>typedarray-to-buffer": true + "gulp>vinyl-fs>remove-bom-stream>through2>readable-stream": true, + "watchify>xtend": true } }, - "stylelint>write-file-atomic>typedarray-to-buffer": { + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "Buffer.from": true + "process.nextTick": true }, "packages": { - "stylelint>write-file-atomic>is-typedarray": true + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter>through2>readable-stream": true, + "watchify>xtend": true } }, - "superstruct": { + "gulp>vinyl-fs>to-through>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "console.warn": true, - "define": true + "process.nextTick": true + }, + "packages": { + "gulp>vinyl-fs>to-through>through2>readable-stream": true, + "watchify>xtend": true } }, - "terser": { + "vinyl-buffer>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "Buffer": true, - "atob": true, - "btoa": true, - "console.log": true, - "console.warn": true, - "define": true, - "process": true + "process.nextTick": true }, "packages": { - "jsdom>acorn": true, - "terser>@jridgewell/source-map": true + "vinyl-buffer>bl>readable-stream": true, + "watchify>xtend": true } }, - "terser-webpack-plugin>@jridgewell/trace-mapping": { + "gulp>vinyl-fs>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "define": true + "process.nextTick": true }, "packages": { - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true + "gulp>vinyl-fs>readable-stream": true, + "watchify>xtend": true } }, - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { + "vinyl-source-stream>through2": { + "builtin": { + "util.inherits": true + }, "globals": { - "define": true + "process.nextTick": true + }, + "packages": { + "vinyl-source-stream>through2>readable-stream": true, + "watchify>xtend": true } }, - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": { + "debounce-stream>through": { + "builtin": { + "stream": true + }, "globals": { - "Buffer": true, - "TextDecoder": true, - "define": true + "process.nextTick": true } }, - "terser>@jridgewell/source-map": { + "gulp-sourcemaps>debug-fabulous>memoizee>timers-ext": { "packages": { - "terser-webpack-plugin>@jridgewell/trace-mapping": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true + "resolve-url-loader>es6-iterator>es5-ext": true } }, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": { + "gulp-livereload>tiny-lr": { + "builtin": { + "events": true, + "fs": true, + "http": true, + "https": true, + "url.parse": true + }, "globals": { - "define": true + "console.error": true }, "packages": { - "terser-webpack-plugin>@jridgewell/trace-mapping": true, - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": true - } - }, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping>@jridgewell/set-array": { - "globals": { - "define": true + "gulp-livereload>tiny-lr>body": true, + "gulp-livereload>tiny-lr>debug": true, + "gulp-livereload>tiny-lr>faye-websocket": true, + "react>object-assign": true, + "@storybook/addon-knobs>qs": true } }, - "terser>source-map-support": { + "gulp>vinyl-fs>glob-stream>to-absolute-glob": { "builtin": { - "fs": true, - "path.dirname": true, "path.resolve": true }, "globals": { - "XMLHttpRequest": true, - "console.error": true, - "process": true + "process.cwd": true, + "process.platform": true }, "packages": { - "terser>source-map-support>buffer-from": true, - "terser>source-map-support>source-map": true + "gulp>gulp-cli>replace-homedir>is-absolute": true, + "gulp>glob-watcher>is-negated-glob": true } }, - "terser>source-map-support>buffer-from": { - "globals": { - "Buffer": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path>kind-of": true } }, - "through2": { + "chokidar>braces>fill-range>to-regex-range": { "packages": { - "readable-stream": true + "chokidar>braces>fill-range>to-regex-range>is-number": true + } + }, + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>to-regex-range": { + "packages": { + "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": true, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>to-regex": { + "packages": { + "gulp>gulp-cli>matchdep>micromatch>to-regex>define-property": true, + "gulp-zip>plugin-error>extend-shallow": true, + "gulp>gulp-cli>matchdep>micromatch>regex-not": true, + "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": true + } + }, + "gulp>vinyl-fs>to-through": { + "packages": { + "gulp>vinyl-fs>to-through>through2": true } }, "ts-node": { @@ -8733,13 +8627,13 @@ "value": true }, "packages": { - "jsdom>acorn": true, "ts-node>@cspotcode/source-map-support": true, "ts-node>@tsconfig/node10": true, "ts-node>@tsconfig/node12": true, "ts-node>@tsconfig/node14": true, "ts-node>@tsconfig/node16": true, "ts-node>acorn-walk": true, + "jsdom>acorn": true, "ts-node>arg": true, "ts-node>create-require": true, "ts-node>diff": true, @@ -8748,63 +8642,195 @@ "ts-node>yn": true } }, - "ts-node>@cspotcode/source-map-support": { - "builtin": { - "fs": true, - "path.isAbsolute": true, - "path.join": true, - "path.normalize": true, - "path.resolve": true, - "url.fileURLToPath": true, - "url.pathToFileURL": true, - "util.inspect": true - }, - "globals": { - "Buffer.from": true, - "URL": true, - "XMLHttpRequest": true, - "console.error": true, - "process": true - }, + "eslint-plugin-import>tsconfig-paths": { + "builtin": { + "fs.existsSync": true, + "fs.lstatSync": true, + "fs.readFile": true, + "fs.readFileSync": true, + "fs.stat": true, + "fs.statSync": true, + "module._resolveFilename": true, + "module.builtinModules": true, + "path.dirname": true, + "path.isAbsolute": true, + "path.join": true, + "path.resolve": true + }, + "globals": { + "console.warn": true, + "process.argv.slice": true, + "process.cwd": true, + "process.env": true + }, + "packages": { + "eslint-plugin-import>tsconfig-paths>json5": true, + "wait-on>minimist": true, + "eslint-plugin-import>tsconfig-paths>strip-bom": true + } + }, + "tsutils": { + "packages": { + "tslib": true, + "typescript": true + } + }, + "eslint>levn>type-check": { + "packages": { + "eslint>levn>prelude-ls": true + } + }, + "stylelint>write-file-atomic>typedarray-to-buffer": { + "globals": { + "Buffer.from": true + }, + "packages": { + "stylelint>write-file-atomic>is-typedarray": true + } + }, + "typescript": { + "builtin": { + "buffer.Buffer": true, + "crypto": true, + "fs": true, + "inspector": true, + "module.findPnpApi": true, + "os.EOL": true, + "os.platform": true, + "path.dirname": true, + "path.join": true, + "path.resolve": true, + "perf_hooks.PerformanceObserver": true, + "perf_hooks.performance": true + }, + "globals": { + "Intl.Collator": true, + "PerformanceObserver": true, + "__dirname": true, + "__filename": true, + "clearTimeout": true, + "console": true, + "gc": true, + "globalThis": true, + "onProfilerEvent": true, + "performance": true, + "process": true, + "setTimeout": true + }, + "packages": { + "terser>source-map-support": true + } + }, + "browserify>insert-module-globals>undeclared-identifiers": { + "packages": { + "browserify>syntax-error>acorn-node": true, + "browserify>insert-module-globals>undeclared-identifiers>get-assigned-identifiers": true, + "watchify>xtend": true + } + }, + "gulp>undertaker": { + "builtin": { + "assert": true, + "events.EventEmitter": true, + "util.inherits": true + }, + "globals": { + "process.env.UNDERTAKER_SETTLE": true, + "process.env.UNDERTAKER_TIME_RESOLUTION": true, + "process.hrtime": true + }, + "packages": { + "gulp>undertaker>arr-flatten": true, + "gulp>undertaker>arr-map": true, + "gulp>undertaker>bach": true, + "gulp>undertaker>collection-map": true, + "gulp>undertaker>es6-weak-map": true, + "gulp>undertaker>last-run": true, + "gulp>undertaker>object.defaults": true, + "gulp>undertaker>object.reduce": true, + "gulp>undertaker>undertaker-registry": true + } + }, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unherit": { + "packages": { + "pumpify>inherits": true, + "watchify>xtend": true + } + }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript": { + "packages": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript>unicode-canonical-property-names-ecmascript": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript>unicode-property-aliases-ecmascript": true + } + }, + "react-markdown>unified": { + "packages": { + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true + } + }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>union-value": { + "packages": { + "gulp-zip>plugin-error>arr-union": true, + "gulp>gulp-cli>array-sort>get-value": true, + "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": true + } + }, + "gulp>vinyl-fs>glob-stream>unique-stream": { + "packages": { + "@lavamoat/lavapack>json-stable-stringify": true, + "gulp>vinyl-fs>glob-stream>unique-stream>through2-filter": true + } + }, + "stylelint>@stylelint/postcss-markdown>unist-util-find-all-after": { + "packages": { + "stylelint>@stylelint/postcss-markdown>unist-util-find-all-after>unist-util-is": true + } + }, + "stylelint>@stylelint/postcss-markdown>remark>remark-parse>unist-util-remove-position": { + "packages": { + "react-markdown>unist-util-visit": true + } + }, + "react-markdown>unist-util-visit>unist-util-visit-parents": { + "packages": { + "react-markdown>unist-util-visit>unist-util-is": true + } + }, + "react-markdown>unist-util-visit": { "packages": { - "ts-node>@cspotcode/source-map-support>@jridgewell/trace-mapping": true + "react-markdown>unist-util-visit>unist-util-visit-parents": true } }, - "ts-node>@cspotcode/source-map-support>@jridgewell/trace-mapping": { - "globals": { - "define": true - }, + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value": { "packages": { - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, - "terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value": true, + "gulp>gulp-cli>isobject": true } }, - "ts-node>acorn-walk": { + "uri-js": { "globals": { "define": true } }, - "ts-node>arg": { - "globals": { - "process.argv.slice": true + "resolve-url-loader>rework>css>urix": { + "builtin": { + "path.sep": true } }, - "ts-node>create-require": { - "builtin": { - "fs.lstatSync": true, - "module.Module": true, - "module.createRequire": true, - "module.createRequireFromPath": true, - "path.dirname": true, - "path.join": true - }, - "globals": { - "process.cwd": true + "gulp>gulp-cli>matchdep>micromatch>snapdragon>use": { + "packages": { + "@babel/register>clone-deep>kind-of": true } }, - "ts-node>diff": { - "globals": { - "setTimeout": true + "readable-stream>util-deprecate": { + "builtin": { + "util.deprecate": true } }, "ts-node>v8-compile-cache-lib": { @@ -8832,48 +8858,26 @@ "process": true } }, - "tsutils": { + "react-markdown>vfile>vfile-message": { "packages": { - "tslib": true, - "typescript": true + "react-markdown>vfile>unist-util-stringify-position": true } }, - "typescript": { + "react-markdown>vfile": { "builtin": { - "buffer.Buffer": true, - "crypto": true, - "fs": true, - "inspector": true, - "module.findPnpApi": true, - "os.EOL": true, - "os.platform": true, + "path.basename": true, "path.dirname": true, + "path.extname": true, "path.join": true, - "path.resolve": true, - "perf_hooks.PerformanceObserver": true, - "perf_hooks.performance": true + "path.sep": true }, "globals": { - "Intl.Collator": true, - "PerformanceObserver": true, - "__dirname": true, - "__filename": true, - "clearTimeout": true, - "console": true, - "gc": true, - "globalThis": true, - "onProfilerEvent": true, - "performance": true, - "process": true, - "setTimeout": true + "process.cwd": true }, "packages": { - "terser>source-map-support": true - } - }, - "uri-js": { - "globals": { - "define": true + "react-markdown>vfile>is-buffer": true, + "react-markdown>vfile>replace-ext": true, + "react-markdown>vfile>vfile-message": true } }, "vinyl": { @@ -8891,9 +8895,9 @@ "process.cwd": true }, "packages": { - "vinyl>clone": true, "vinyl>clone-buffer": true, "vinyl>clone-stats": true, + "vinyl>clone": true, "vinyl>cloneable-readable": true, "vinyl>remove-trailing-separator": true, "vinyl>replace-ext": true @@ -8905,67 +8909,7 @@ "vinyl-buffer>through2": true } }, - "vinyl-buffer>bl": { - "builtin": { - "util.inherits": true - }, - "packages": { - "koa>content-disposition>safe-buffer": true, - "vinyl-buffer>bl>readable-stream": true - } - }, - "vinyl-buffer>bl>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true, - "vinyl-buffer>bl>readable-stream>isarray": true, - "vinyl-buffer>bl>readable-stream>safe-buffer": true, - "vinyl-buffer>bl>readable-stream>string_decoder": true - } - }, - "vinyl-buffer>bl>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "vinyl-buffer>bl>readable-stream>string_decoder": { - "packages": { - "vinyl-buffer>bl>readable-stream>string_decoder>safe-buffer": true - } - }, - "vinyl-buffer>bl>readable-stream>string_decoder>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "vinyl-buffer>through2": { - "builtin": { - "util.inherits": true - }, - "globals": { - "process.nextTick": true - }, - "packages": { - "vinyl-buffer>bl>readable-stream": true, - "watchify>xtend": true - } - }, - "vinyl-source-stream": { + "gulp-watch>vinyl-file": { "builtin": { "path.resolve": true }, @@ -8973,163 +8917,103 @@ "process.cwd": true }, "packages": { - "vinyl": true, - "vinyl-source-stream>through2": true + "del>graceful-fs": true, + "gulp-watch>vinyl-file>pify": true, + "gh-pages>globby>pinkie-promise": true, + "gulp-watch>vinyl-file>strip-bom-stream": true, + "gulp-watch>vinyl-file>strip-bom": true, + "gulp-watch>vinyl-file>vinyl": true } }, - "vinyl-source-stream>through2": { + "gulp>vinyl-fs": { "builtin": { + "os.platform": true, + "path.relative": true, + "path.resolve": true, "util.inherits": true }, "globals": { + "Buffer.isBuffer": true, + "process.cwd": true, + "process.geteuid": true, + "process.getuid": true, "process.nextTick": true }, "packages": { - "vinyl-source-stream>through2>readable-stream": true, - "watchify>xtend": true - } - }, - "vinyl-source-stream>through2>readable-stream": { - "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true - }, - "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true - }, - "packages": { - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true, - "vinyl-source-stream>through2>readable-stream>isarray": true, - "vinyl-source-stream>through2>readable-stream>safe-buffer": true, - "vinyl-source-stream>through2>readable-stream>string_decoder": true - } - }, - "vinyl-source-stream>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true - } - }, - "vinyl-source-stream>through2>readable-stream>string_decoder": { - "packages": { - "vinyl-source-stream>through2>readable-stream>safe-buffer": true - } - }, - "vinyl-sourcemaps-apply": { - "packages": { - "vinyl-sourcemaps-apply>source-map": true - } - }, - "vinyl>clone": { - "globals": { - "Buffer": true - } - }, - "vinyl>clone-buffer": { - "builtin": { - "buffer.Buffer": true - } - }, - "vinyl>clone-stats": { - "builtin": { - "fs.Stats": true - } - }, - "vinyl>cloneable-readable": { - "packages": { - "pumpify>inherits": true, - "vinyl>cloneable-readable>process-nextick-args": true, - "vinyl>cloneable-readable>through2": true - } - }, - "vinyl>cloneable-readable>process-nextick-args": { - "globals": { - "process.nextTick": true, - "process.version": true + "gulp>vinyl-fs>fs-mkdirp-stream": true, + "gulp>vinyl-fs>glob-stream": true, + "del>graceful-fs": true, + "gulp>vinyl-fs>is-valid-glob": true, + "gulp>vinyl-fs>lazystream": true, + "gulp>vinyl-fs>lead": true, + "gulp>vinyl-fs>object.assign": true, + "gulp>vinyl-fs>pumpify": true, + "gulp>vinyl-fs>readable-stream": true, + "gulp>vinyl-fs>remove-bom-buffer": true, + "gulp>vinyl-fs>remove-bom-stream": true, + "gulp>vinyl-fs>resolve-options": true, + "gulp>vinyl-fs>through2": true, + "gulp>vinyl-fs>to-through": true, + "gulp>vinyl-fs>value-or-function": true, + "vinyl": true, + "gulp>vinyl-fs>vinyl-sourcemap": true } }, - "vinyl>cloneable-readable>through2": { + "vinyl-source-stream": { "builtin": { - "util.inherits": true + "path.resolve": true }, "globals": { - "process.nextTick": true + "process.cwd": true }, "packages": { - "vinyl>cloneable-readable>through2>readable-stream": true, - "watchify>xtend": true + "vinyl-source-stream>through2": true, + "vinyl": true } }, - "vinyl>cloneable-readable>through2>readable-stream": { + "gulp>vinyl-fs>vinyl-sourcemap": { "builtin": { - "events.EventEmitter": true, - "stream": true, - "util": true + "path.dirname": true, + "path.join": true, + "path.relative": true, + "path.resolve": true }, "globals": { - "process.browser": true, - "process.env.READABLE_STREAM": true, - "process.stderr": true, - "process.stdout": true, - "process.version.slice": true, - "setImmediate": true + "Buffer": true }, "packages": { - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream>util-deprecate": true, - "vinyl>cloneable-readable>through2>readable-stream>isarray": true, - "vinyl>cloneable-readable>through2>readable-stream>process-nextick-args": true, - "vinyl>cloneable-readable>through2>readable-stream>safe-buffer": true, - "vinyl>cloneable-readable>through2>readable-stream>string_decoder": true - } - }, - "vinyl>cloneable-readable>through2>readable-stream>process-nextick-args": { - "globals": { - "process": true - } - }, - "vinyl>cloneable-readable>through2>readable-stream>safe-buffer": { - "builtin": { - "buffer": true + "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": true, + "nyc>convert-source-map": true, + "del>graceful-fs": true, + "gulp-watch>anymatch>normalize-path": true, + "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true, + "gulp>vinyl-fs>remove-bom-buffer": true, + "vinyl": true } }, - "vinyl>cloneable-readable>through2>readable-stream>string_decoder": { + "vinyl-sourcemaps-apply": { "packages": { - "vinyl>cloneable-readable>through2>readable-stream>safe-buffer": true - } - }, - "vinyl>remove-trailing-separator": { - "globals": { - "process.platform": true + "vinyl-sourcemaps-apply>source-map": true } }, - "vinyl>replace-ext": { + "gulp-watch>vinyl-file>vinyl": { "builtin": { + "buffer.Buffer": true, "path.basename": true, "path.dirname": true, "path.extname": true, "path.join": true, - "path.sep": true - } - }, - "wait-on>rxjs": { + "path.relative": true, + "stream.PassThrough": true, + "stream.Stream": true + }, "globals": { - "cancelAnimationFrame": true, - "clearInterval": true, - "clearTimeout": true, - "performance": true, - "requestAnimationFrame": true, - "setInterval.apply": true, - "setTimeout.apply": true + "process.cwd": true + }, + "packages": { + "gulp-watch>vinyl-file>vinyl>clone-stats": true, + "@metamask/jazzicon>color>clone": true, + "gulp-watch>vinyl-file>vinyl>replace-ext": true } }, "watchify": { @@ -9141,8 +9025,8 @@ "setTimeout": true }, "packages": { - "chokidar": true, "chokidar>anymatch": true, + "chokidar": true, "through2": true, "watchify>xtend": true } @@ -9160,20 +9044,93 @@ "process.version": true }, "packages": { - "koa>content-disposition>safe-buffer": true, "webpack-dev-server>sockjs>websocket-driver>http-parser-js": true, + "koa>content-disposition>safe-buffer": true, "webpack-dev-server>sockjs>websocket-driver>websocket-extensions": true } }, - "webpack-dev-server>sockjs>websocket-driver>http-parser-js": { + "stylelint>global-modules>global-prefix>which": { "builtin": { - "assert.equal": true, - "assert.ok": true + "path.join": true + }, + "globals": { + "process.cwd": true, + "process.env.OSTYPE": true, + "process.env.PATH": true, + "process.env.PATHEXT": true, + "process.platform": true + }, + "packages": { + "@sentry/cli>which>isexe": true } }, - "webpack>json-parse-even-better-errors": { + "@storybook/react>@storybook/node-logger>npmlog>gauge>wide-align": { + "packages": { + "yargs>string-width": true + } + }, + "yargs>cliui>wrap-ansi": { + "packages": { + "chalk>ansi-styles": true, + "yargs>string-width": true, + "eslint>strip-ansi": true + } + }, + "stylelint>write-file-atomic": { + "builtin": { + "fs.chmod": true, + "fs.chmodSync": true, + "fs.chown": true, + "fs.chownSync": true, + "fs.close": true, + "fs.closeSync": true, + "fs.fsync": true, + "fs.fsyncSync": true, + "fs.open": true, + "fs.openSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.rename": true, + "fs.renameSync": true, + "fs.stat": true, + "fs.statSync": true, + "fs.unlink": true, + "fs.unlinkSync": true, + "fs.write": true, + "fs.writeSync": true, + "path.resolve": true, + "util.promisify": true, + "worker_threads.threadId": true + }, "globals": { - "Buffer.isBuffer": true + "Buffer.isBuffer": true, + "__filename": true, + "process.getuid": true, + "process.pid": true + }, + "packages": { + "eslint>imurmurhash": true, + "stylelint>write-file-atomic>is-typedarray": true, + "nyc>signal-exit": true, + "stylelint>write-file-atomic>typedarray-to-buffer": true + } + }, + "stylelint>file-entry-cache>flat-cache>write": { + "builtin": { + "fs.createWriteStream": true, + "fs.writeFile": true, + "fs.writeFileSync": true, + "path.dirname": true + }, + "packages": { + "mockttp>portfinder>mkdirp": true + } + }, + "yargs>y18n": { + "builtin": { + "fs": true, + "path": true, + "util": true } }, "yaml": { @@ -9187,6 +9144,28 @@ "process": true } }, + "stylelint>cosmiconfig>yaml": { + "globals": { + "Buffer": true, + "YAML_SILENCE_DEPRECATION_WARNINGS": true, + "YAML_SILENCE_WARNINGS": true, + "atob": true, + "btoa": true, + "console.warn": true, + "process": true + } + }, + "gulp-postcss>postcss-load-config>yaml": { + "globals": { + "Buffer": true, + "YAML_SILENCE_DEPRECATION_WARNINGS": true, + "YAML_SILENCE_WARNINGS": true, + "atob": true, + "btoa": true, + "console.warn": true, + "process": true + } + }, "yargs": { "builtin": { "assert": true, @@ -9201,13 +9180,13 @@ "process": true }, "packages": { - "yargs-parser": true, "yargs>cliui": true, "yargs>escalade": true, "yargs>get-caller-file": true, "yargs>require-directory": true, "yargs>string-width": true, - "yargs>y18n": true + "yargs>y18n": true, + "yargs-parser": true } }, "yargs-parser": { @@ -9221,62 +9200,34 @@ "process": true } }, - "yargs>cliui": { - "globals": { - "process": true - }, - "packages": { - "eslint>strip-ansi": true, - "yargs>cliui>wrap-ansi": true, - "yargs>string-width": true - } - }, - "yargs>cliui>wrap-ansi": { - "packages": { - "chalk>ansi-styles": true, - "eslint>strip-ansi": true, - "yargs>string-width": true - } - }, - "yargs>escalade": { - "builtin": { - "fs.readdirSync": true, - "fs.statSync": true, - "path.dirname": true, - "path.resolve": true - } - }, - "yargs>require-directory": { - "builtin": { - "fs.readdirSync": true, - "fs.statSync": true, - "path.dirname": true, - "path.join": true, - "path.resolve": true - } - }, - "yargs>string-width": { - "packages": { - "eslint>strip-ansi": true, - "yargs>string-width>emoji-regex": true, - "yargs>string-width>is-fullwidth-code-point": true - } - }, - "yargs>y18n": { + "yargs>yargs-parser": { "builtin": { "fs": true, "path": true, "util": true + }, + "globals": { + "process": true } }, - "yargs>yargs-parser": { + "gulp-zip>yazl": { "builtin": { - "fs": true, - "path": true, - "util": true + "events.EventEmitter": true, + "fs.createReadStream": true, + "fs.stat": true, + "stream.PassThrough": true, + "stream.Transform": true, + "util.inherits": true, + "zlib.DeflateRaw": true, + "zlib.deflateRaw": true }, "globals": { - "process": true + "Buffer": true, + "setImmediate": true, + "utf8FileName.length": true + }, + "packages": { + "gulp-zip>yazl>buffer-crc32": true } } } diff --git a/offscreen/scripts/ledger.ts b/offscreen/scripts/ledger.ts index c08fdc6ac85a..4cff2f3a6748 100644 --- a/offscreen/scripts/ledger.ts +++ b/offscreen/scripts/ledger.ts @@ -96,7 +96,7 @@ function setupMessageListeners(iframe: HTMLIFrameElement) { export default async function init() { return new Promise((resolve) => { const iframe = document.createElement('iframe'); - iframe.src = 'https://metamask.github.io/eth-ledger-bridge-keyring'; + iframe.src = 'https://metamask.github.io/ledger-iframe-bridge/8.0.0/'; iframe.allow = 'hid'; iframe.onload = () => { setupMessageListeners(iframe); diff --git a/package.json b/package.json index b7adb1fcdcee..9dd1e1b9942d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "12.7.0", + "version": "12.10.1", "private": true, "repository": { "type": "git", @@ -28,7 +28,7 @@ "start:test:mv2:flask": "ENABLE_MV3=false yarn start:test:flask --apply-lavamoat=false --snow=false", "start:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom yarn start:test --apply-lavamoat=false --snow=false", "benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/benchmark.js", - "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/mv3-perf-stats/index.js", + "bundle-size": "SELENIUM_BROWSER=chrome ts-node test/e2e/mv3-perf-stats/bundle-size.js", "user-actions-benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/user-actions-benchmark.js", "benchmark:firefox": "SELENIUM_BROWSER=firefox ts-node test/e2e/benchmark.js", "build:test": "yarn env:e2e build test", @@ -129,15 +129,11 @@ "update-mock-cdn": "node test/e2e/mock-cdn/update-mock-cdn-files.js", "attributions:check": "./development/attributions-check.sh", "attributions:generate": "./development/generate-attributions.sh", - "ci-rerun-from-failed": "tsx .circleci/scripts/rerun-ci-workflow-from-failed.ts" + "ci-rerun-from-failed": "tsx .circleci/scripts/rerun-ci-workflow-from-failed.ts", + "git-diff-default-branch": "tsx .circleci/scripts/git-diff-default-branch.ts" }, "resolutions": { "chokidar": "^3.6.0", - "gridplus-sdk/elliptic": "^6.5.7", - "gridplus-sdk/secp256k1": "^5.0.1", - "eth-lattice-keyring/@ethereumjs/tx": "^4.2.0", - "@ethersproject/signing-key/elliptic": "^6.5.7", - "ganache/secp256k1": "^4.0.4", "simple-update-notifier@^1.0.0": "^2.0.0", "@types/react": "^16.9.53", "analytics-node/axios": "^0.21.2", @@ -178,8 +174,6 @@ "await-semaphore@^0.1.1": "patch:await-semaphore@npm%3A0.1.3#./.yarn/patches/await-semaphore-npm-0.1.3-b7a0001fab.patch", "await-semaphore@^0.1.3": "patch:await-semaphore@npm%3A0.1.3#./.yarn/patches/await-semaphore-npm-0.1.3-b7a0001fab.patch", "eslint@npm:^8.7.0": "patch:eslint@npm%3A8.57.0#~/.yarn/patches/eslint-npm-8.57.0-4286e12a3a.patch", - "eth-query@^2.1.2": "patch:eth-query@npm%3A2.1.2#./.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch", - "eth-query@^2.1.0": "patch:eth-query@npm%3A2.1.2#./.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch", "ethereumjs-util@^7.0.10": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch", "ethereumjs-util@^7.1.5": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch", "ethereumjs-util@^7.1.4": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch", @@ -211,8 +205,6 @@ "watchify@^4.0.0": "patch:watchify@npm%3A4.0.0#./.yarn/patches/watchify-npm-4.0.0-4fd965dd49.patch", "undeclared-identifiers@^1.1.2": "patch:undeclared-identifiers@npm%3A1.1.2#./.yarn/patches/undeclared-identifiers-npm-1.1.2-13d6792e9e.patch", "stylelint@^13.6.1": "patch:stylelint@npm%3A13.6.1#./.yarn/patches/stylelint-npm-13.6.1-47aaddf62b.patch", - "luxon@^3.0.1": "patch:luxon@npm%3A3.2.1#./.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch", - "luxon@^3.2.1": "patch:luxon@npm%3A3.2.1#./.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch", "symbol-observable": "^2.0.3", "async-done@~1.3.2": "patch:async-done@npm%3A1.3.2#./.yarn/patches/async-done-npm-1.3.2-1f0a4a8997.patch", "async-done@^1.2.0": "patch:async-done@npm%3A1.3.2#./.yarn/patches/async-done-npm-1.3.2-1f0a4a8997.patch", @@ -229,7 +221,8 @@ "semver@7.3.8": "^7.5.4", "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "@metamask/snaps-sdk": "^6.12.0", + "lavamoat-core@npm:^16.2.2": "patch:lavamoat-core@npm%3A16.2.2#~/.yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch", + "@metamask/snaps-sdk": "^6.15.0", "@swc/types@0.1.5": "^0.1.6", "@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch", "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -240,12 +233,12 @@ "@expo/config/glob": "^10.3.10", "@expo/config-plugins/glob": "^10.3.10", "@solana/web3.js/rpc-websockets": "^8.0.1", - "@metamask/message-manager": "^10.1.0", - "@metamask/gas-fee-controller@npm:^15.1.1": "patch:@metamask/gas-fee-controller@npm%3A15.1.2#~/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch", - "@metamask/nonce-tracker@npm:^5.0.0": "patch:@metamask/nonce-tracker@npm%3A5.0.0#~/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch", - "@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", + "@json-schema-spec/json-pointer@npm:^0.1.2": "patch:@json-schema-spec/json-pointer@npm%3A0.1.2#~/.yarn/patches/@json-schema-spec-json-pointer-npm-0.1.2-3d06119887.patch", + "@json-schema-tools/reference-resolver@npm:^1.2.6": "patch:@json-schema-tools/reference-resolver@npm%3A1.2.6#~/.yarn/patches/@json-schema-tools-reference-resolver-npm-1.2.6-4e1497c16d.patch", + "@json-schema-tools/reference-resolver@npm:1.2.4": "patch:@json-schema-tools/reference-resolver@npm%3A1.2.6#~/.yarn/patches/@json-schema-tools-reference-resolver-npm-1.2.6-4e1497c16d.patch", + "@json-schema-tools/reference-resolver@npm:^1.2.4": "patch:@json-schema-tools/reference-resolver@npm%3A1.2.6#~/.yarn/patches/@json-schema-tools-reference-resolver-npm-1.2.6-4e1497c16d.patch", + "@json-schema-tools/reference-resolver@npm:^1.2.1": "patch:@json-schema-tools/reference-resolver@npm%3A1.2.6#~/.yarn/patches/@json-schema-tools-reference-resolver-npm-1.2.6-4e1497c16d.patch", + "@metamask/network-controller@npm:^22.0.2": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch", "path-to-regexp": "1.9.0", "@ledgerhq/cryptoassets-evm-signatures/axios": "^0.28.0", "@ledgerhq/domain-service/axios": "^0.28.0", @@ -253,7 +246,20 @@ "@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", - "cross-spawn@npm:^5.0.1": "^7.0.5" + "cross-spawn@npm:^5.0.1": "^7.0.6", + "@solana/web3.js@npm:^1.95.0": "^1.95.8", + "secp256k1@npm:^4.0.0": "4.0.4", + "secp256k1@npm:^4.0.1": "4.0.4", + "secp256k1@npm:4.0.2": "4.0.4", + "secp256k1@npm:4.0.3": "4.0.4", + "tslib@npm:^2.0.0": "~2.6.0", + "tslib@npm:^2.0.1": "~2.6.0", + "tslib@npm:^2.0.3": "~2.6.0", + "tslib@npm:^2.1.0": "~2.6.0", + "tslib@npm:^2.3.0": "~2.6.0", + "tslib@npm:^2.3.1": "~2.6.0", + "tslib@npm:^2.4.0": "~2.6.0", + "tslib@npm:^2.6.2": "~2.6.0" }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -284,74 +290,73 @@ "@metamask-institutional/transaction-update": "^0.2.6", "@metamask-institutional/types": "^1.2.0", "@metamask/abi-utils": "^2.0.2", - "@metamask/account-watcher": "^4.1.1", - "@metamask/accounts-controller": "^20.0.0", + "@metamask/account-watcher": "^4.1.2", + "@metamask/accounts-controller": "^21.0.1", "@metamask/address-book-controller": "^6.0.0", "@metamask/announcement-controller": "^7.0.0", "@metamask/approval-controller": "^7.0.0", - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@patch%253A@metamask/assets-controllers@npm%25253A45.1.0%2523~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch%253A%253Aversion=45.1.0&hash=cfcadc%23~/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch%3A%3Aversion=45.1.0&hash=4e79dd#~/.yarn/patches/@metamask-assets-controllers-patch-d114308c1b.patch", "@metamask/base-controller": "^7.0.0", - "@metamask/bitcoin-wallet-snap": "^0.8.2", + "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/browser-passworder": "^4.3.0", "@metamask/contract-metadata": "^2.5.0", "@metamask/controller-utils": "^11.4.0", - "@metamask/design-tokens": "^4.0.0", - "@metamask/ens-controller": "^13.0.0", + "@metamask/design-tokens": "^4.2.0", + "@metamask/ens-controller": "^15.0.0", "@metamask/ens-resolver-snap": "^0.1.2", "@metamask/eth-json-rpc-filters": "^9.0.0", - "@metamask/eth-json-rpc-middleware": "patch:@metamask/eth-json-rpc-middleware@npm%3A14.0.1#~/.yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch", - "@metamask/eth-ledger-bridge-keyring": "^5.0.1", - "@metamask/eth-query": "^4.0.0", + "@metamask/eth-json-rpc-middleware": "^15.1.2", + "@metamask/eth-ledger-bridge-keyring": "^8.0.2", "@metamask/eth-sig-util": "^7.0.1", - "@metamask/eth-snap-keyring": "^5.0.1", - "@metamask/eth-token-tracker": "^8.0.0", - "@metamask/eth-trezor-keyring": "^3.1.3", + "@metamask/eth-snap-keyring": "^8.1.0", + "@metamask/eth-token-tracker": "^9.0.0", + "@metamask/eth-trezor-keyring": "^6.0.0", "@metamask/etherscan-link": "^3.0.0", - "@metamask/ethjs": "^0.6.0", - "@metamask/ethjs-contract": "^0.4.1", - "@metamask/ethjs-query": "^0.7.1", - "@metamask/gas-fee-controller": "^18.0.0", + "@metamask/gas-fee-controller": "^22.0.1", "@metamask/jazzicon": "^2.0.0", "@metamask/json-rpc-engine": "^10.0.0", "@metamask/json-rpc-middleware-stream": "^8.0.4", - "@metamask/keyring-api": "^10.1.0", - "@metamask/keyring-controller": "^19.0.0", + "@metamask/keyring-api": "^13.0.0", + "@metamask/keyring-controller": "^19.0.3", + "@metamask/keyring-internal-api": "^2.0.0", + "@metamask/keyring-snap-client": "^2.0.0", "@metamask/logging-controller": "^6.0.0", "@metamask/logo": "^3.1.2", - "@metamask/message-manager": "^10.1.0", - "@metamask/message-signing-snap": "^0.4.0", + "@metamask/message-manager": "^11.0.0", + "@metamask/message-signing-snap": "^0.6.0", "@metamask/metamask-eth-abis": "^3.1.1", + "@metamask/multichain": "^2.0.0", "@metamask/name-controller": "^8.0.0", - "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A21.0.0#~/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch", - "@metamask/notification-controller": "^6.0.0", - "@metamask/notification-services-controller": "^0.14.0", + "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch", + "@metamask/notification-services-controller": "^0.15.0", "@metamask/object-multiplex": "^2.0.0", "@metamask/obs-store": "^9.0.0", - "@metamask/permission-controller": "^10.0.0", - "@metamask/permission-log-controller": "^2.0.1", + "@metamask/permission-controller": "^11.0.0", + "@metamask/permission-log-controller": "^3.0.1", "@metamask/phishing-controller": "^12.3.0", - "@metamask/polling-controller": "^10.0.1", - "@metamask/post-message-stream": "^8.0.0", - "@metamask/ppom-validator": "0.35.1", - "@metamask/preinstalled-example-snap": "^0.2.0", - "@metamask/profile-sync-controller": "^2.0.0", - "@metamask/providers": "^14.0.2", + "@metamask/polling-controller": "^12.0.1", + "@metamask/post-message-stream": "^9.0.0", + "@metamask/ppom-validator": "0.36.0", + "@metamask/preinstalled-example-snap": "^0.3.0", + "@metamask/profile-sync-controller": "^4.1.0", + "@metamask/providers": "^18.2.0", "@metamask/queued-request-controller": "^7.0.1", "@metamask/rate-limit-controller": "^6.0.0", + "@metamask/remote-feature-flag-controller": "^1.3.0", "@metamask/rpc-errors": "^7.0.0", "@metamask/safe-event-emitter": "^3.1.1", "@metamask/scure-bip39": "^2.0.3", - "@metamask/selected-network-controller": "^18.0.2", - "@metamask/signature-controller": "^23.0.0", - "@metamask/smart-transactions-controller": "^13.0.0", - "@metamask/snaps-controllers": "^9.14.0", - "@metamask/snaps-execution-environments": "^6.10.0", - "@metamask/snaps-rpc-methods": "^11.6.0", - "@metamask/snaps-sdk": "^6.12.0", - "@metamask/snaps-utils": "^8.6.0", - "@metamask/solana-wallet-snap": "^0.1.9", - "@metamask/transaction-controller": "^40.1.0", - "@metamask/user-operation-controller": "^13.0.0", + "@metamask/selected-network-controller": "^19.0.0", + "@metamask/signature-controller": "^23.1.0", + "@metamask/smart-transactions-controller": "^16.0.1", + "@metamask/snaps-controllers": "^9.17.0", + "@metamask/snaps-execution-environments": "^6.12.0", + "@metamask/snaps-rpc-methods": "^11.9.1", + "@metamask/snaps-sdk": "^6.15.0", + "@metamask/snaps-utils": "^8.8.0", + "@metamask/solana-wallet-snap": "^1.1.0", + "@metamask/transaction-controller": "^43.0.0", + "@metamask/user-operation-controller": "^22.0.0", "@metamask/utils": "^10.0.1", "@ngraveio/bc-ur": "^1.1.12", "@noble/hashes": "^1.3.3", @@ -401,7 +406,7 @@ "loglevel": "^1.8.1", "lottie-web": "^5.12.2", "luxon": "^3.2.1", - "nanoid": "^2.1.6", + "nanoid": "^3.3.8", "pify": "^5.0.0", "promise-to-callback": "^1.0.0", "prop-types": "^15.6.1", @@ -429,11 +434,12 @@ "redux": "^4.0.5", "redux-thunk": "^2.3.0", "remove-trailing-slash": "^0.1.1", - "reselect": "^3.0.1", + "reselect": "^5.1.1", "ses": "^1.1.0", "simple-git": "^3.20.0", "single-call-balance-checker-abi": "^1.0.0", "ts-mixer": "patch:ts-mixer@npm%3A6.0.4#~/.yarn/patches/ts-mixer-npm-6.0.4-5d9747bdf5.patch", + "tslib": "~2.6.0", "unicode-confusables": "^0.1.1", "uri-js": "^4.4.1", "uuid": "^8.3.2", @@ -453,9 +459,9 @@ "@babel/preset-typescript": "^7.25.9", "@babel/register": "^7.25.9", "@jest/globals": "^29.7.0", - "@lavamoat/allow-scripts": "^3.0.4", + "@lavamoat/allow-scripts": "^3.3.1", "@lavamoat/lavadome-core": "0.0.10", - "@lavamoat/lavapack": "^6.1.0", + "@lavamoat/lavapack": "^7.0.5", "@lgbot/madge": "^6.2.0", "@lydell/node-pty": "^1.0.1", "@metamask/api-specs": "^0.9.3", @@ -470,9 +476,9 @@ "@metamask/eth-json-rpc-provider": "^4.1.6", "@metamask/forwarder": "^1.1.0", "@metamask/phishing-warning": "^4.1.0", - "@metamask/preferences-controller": "^13.0.2", + "@metamask/preferences-controller": "^15.0.1", "@metamask/test-bundler": "^1.0.0", - "@metamask/test-dapp": "8.13.0", + "@metamask/test-dapp": "9.0.0", "@octokit/core": "^3.6.0", "@open-rpc/meta-schema": "^1.14.6", "@open-rpc/mock-server": "^1.7.5", @@ -558,7 +564,6 @@ "concurrently": "^8.2.2", "copy-webpack-plugin": "^12.0.2", "core-js-pure": "^3.38.0", - "cross-spawn": "^7.0.5", "crypto-browserify": "^3.12.0", "css-loader": "^6.10.0", "css-to-xpath": "^0.1.0", @@ -613,8 +618,8 @@ "jsdom": "^16.7.0", "json-schema-to-ts": "^3.0.1", "koa": "^2.7.0", - "lavamoat": "^8.0.2", - "lavamoat-browserify": "^17.0.4", + "lavamoat": "^9.0.5", + "lavamoat-browserify": "^18.1.2", "lavamoat-viz": "^7.0.5", "level": "^8.0.1", "lockfile-lint": "^4.10.6", @@ -695,10 +700,17 @@ "@eth-optimism/contracts>@ethersproject/hardware-wallets>@ledgerhq/hw-transport-node-hid>@ledgerhq/hw-transport-node-hid-noevents>node-hid": false, "@eth-optimism/contracts>@ethersproject/hardware-wallets>@ledgerhq/hw-transport-node-hid>node-hid": false, "@eth-optimism/contracts>@ethersproject/hardware-wallets>@ledgerhq/hw-transport-node-hid>usb": false, + "@metamask/controllers>web3-provider-engine>ethereumjs-util>keccak": false, + "@metamask/controllers>web3-provider-engine>ethereumjs-util>secp256k1": false, + "@metamask/controllers>web3-provider-engine>ethereumjs-vm>merkle-patricia-tree>ethereumjs-util>keccak": false, + "@metamask/controllers>web3-provider-engine>ethereumjs-vm>merkle-patricia-tree>ethereumjs-util>secp256k1": false, + "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": false, "@storybook/api>core-js": false, "@storybook/core>@storybook/core-client>@storybook/ui>core-js-pure": false, "@storybook/test-runner>@storybook/core-common>esbuild": false, - "eth-lattice-keyring>gridplus-sdk": true, + "eth-json-rpc-filters>eth-json-rpc-middleware>ethereumjs-util>keccak": false, + "eth-json-rpc-filters>eth-json-rpc-middleware>ethereumjs-util>secp256k1": false, + "eth-lattice-keyring>gridplus-sdk": false, "ethereumjs-util>ethereum-cryptography>keccak": false, "ganache>@trufflesuite/bigint-buffer": false, "ganache>@trufflesuite/uws-js-unofficial>bufferutil": false, @@ -708,10 +720,13 @@ "ganache>leveldown": false, "ganache>secp256k1": false, "ganache>utf-8-validate": false, + "ethereumjs-util>ethereum-cryptography>secp256k1": false, "gulp-watch>chokidar>fsevents": false, "gulp>glob-watcher>chokidar>fsevents": false, "webpack>watchpack>watchpack-chokidar2>chokidar>fsevents": false, + "@keystonehq/bc-ur-registry-eth>hdkey>secp256k1": false, "eth-lattice-keyring>gridplus-sdk>secp256k1": false, + "eth-lattice-keyring>secp256k1": false, "@storybook/react>@pmmmwh/react-refresh-webpack-plugin>core-js-pure": false, "@testing-library/jest-dom>aria-query>@babel/runtime-corejs3>core-js-pure": false, "web3": false, @@ -720,6 +735,7 @@ "web3>web3-core>web3-core-requestmanager>web3-providers-ws>websocket>es5-ext": false, "web3>web3-core>web3-core-requestmanager>web3-providers-ws>websocket>utf-8-validate": false, "web3>web3-shh": false, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>hdkey>secp256k1": false, "@metamask/base-controller>simple-git-hooks": false, "@storybook/core>@storybook/core-server>webpack>watchpack>watchpack-chokidar2>chokidar>fsevents": false, "resolve-url-loader>es6-iterator>es5-ext": false, @@ -753,7 +769,8 @@ "resolve-url-loader>es6-iterator>d>es5-ext>esniff>es5-ext": false, "level>classic-level": false, "jest-preview": false, - "@metamask/solana-wallet-snap>@solana/web3.js>bigint-buffer": false + "@metamask/solana-wallet-snap>@solana/web3.js>bigint-buffer": false, + "@lavamoat/allow-scripts>@lavamoat/preinstall-always-fail": false } }, "packageManager": "yarn@4.5.1" diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 36249b132bca..190d8803e4f6 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -1,10 +1,13 @@ [ "*.btc*.quiknode.pro", + "*solana*.mainnet.rpcpool.com", "accounts.api.cx.metamask.io", "acl.execution.metamask.io", "api.blockchair.com", + "api.devnet.solana.com", "api.lens.dev", "api.segment.io", + "api.simplehash.com", "api.web3modal.com", "app.ens.domains", "arbitrum-mainnet.infura.io", @@ -16,6 +19,7 @@ "cdn.segment.io", "cdnjs.cloudflare.com", "chainid.network", + "client-config.api.cx.metamask.io", "client-side-detection.api.cx.metamask.io", "configuration.dev.metamask-institutional.io", "configuration.metamask-institutional.io", @@ -33,6 +37,7 @@ "linea-sepolia.infura.io", "localhost:8000", "localhost:8545", + "mainnet.helius-rpc.com", "mainnet.infura.io", "metamask-sdk.api.cx.metamask.io", "metamask.eth", @@ -45,6 +50,7 @@ "on-ramp-content.uat-api.cx.metamask.io", "phishing-detection.api.cx.metamask.io", "portfolio.metamask.io", + "price-api.metamask-institutional.io", "price.api.cx.metamask.io", "proxy.api.cx.metamask.io", "proxy.dev-api.cx.metamask.io", @@ -55,7 +61,9 @@ "security-alerts.dev-api.cx.metamask.io", "sentry.io", "sepolia.infura.io", + "signature-insights.api.cx.metamask.io", "snaps.metamask.io", + "solana.rpc.grove.city", "sourcify.dev", "start.metamask.io", "static.cx.metamask.io", @@ -70,5 +78,6 @@ "unresponsive-rpc.test", "unresponsive-rpc.url", "user-storage.api.cx.metamask.io", + "verify.walletconnect.com", "www.4byte.directory" ] diff --git a/shared/constants/alerts.ts b/shared/constants/alerts.ts index bbf6318f448e..71b246db2fb2 100644 --- a/shared/constants/alerts.ts +++ b/shared/constants/alerts.ts @@ -2,6 +2,7 @@ export enum AlertTypes { unconnectedAccount = 'unconnectedAccount', web3ShimUsage = 'web3ShimUsage', invalidCustomNetwork = 'invalidCustomNetwork', + smartTransactionsMigration = 'smartTransactionsMigration', } /** @@ -10,6 +11,7 @@ export enum AlertTypes { export const TOGGLEABLE_ALERT_TYPES = [ AlertTypes.unconnectedAccount, AlertTypes.web3ShimUsage, + AlertTypes.smartTransactionsMigration, ]; export enum Web3ShimUsageAlertStates { diff --git a/shared/constants/app-state.ts b/shared/constants/app-state.ts index 82424edfe57f..b66dd153cb54 100644 --- a/shared/constants/app-state.ts +++ b/shared/constants/app-state.ts @@ -18,3 +18,13 @@ export const ACCOUNT_OVERVIEW_TAB_KEY_TO_TRACE_NAME_MAP = { [AccountOverviewTabKey.Nfts]: TraceName.AccountOverviewNftsTab, [AccountOverviewTabKey.Activity]: TraceName.AccountOverviewActivityTab, } as const; + +export type CarouselSlide = { + id: string; + title: string; + description: string; + image: string; + dismissed?: boolean; + href?: string; + undismissable?: boolean; +}; diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts index 8ad27dce4944..06c54dbf3a1d 100644 --- a/shared/constants/bridge.ts +++ b/shared/constants/bridge.ts @@ -1,4 +1,7 @@ -import { CHAIN_IDS } from './network'; +///: BEGIN:ONLY_INCLUDE_IF(solana-swaps) +import { MultichainNetworks } from './multichain/networks'; +///: END:ONLY_INCLUDE_IF +import { CHAIN_IDS, NETWORK_TO_NAME_MAP } from './network'; // TODO read from feature flags export const ALLOWED_BRIDGE_CHAIN_IDS = [ @@ -11,6 +14,9 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [ CHAIN_IDS.ARBITRUM, CHAIN_IDS.LINEA_MAINNET, CHAIN_IDS.BASE, + ///: BEGIN:ONLY_INCLUDE_IF(solana-swaps) + MultichainNetworks.SOLANA, + ///: END:ONLY_INCLUDE_IF ]; export type AllowedBridgeChainIds = (typeof ALLOWED_BRIDGE_CHAIN_IDS)[number]; @@ -27,6 +33,32 @@ export const ETH_USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7'; export const METABRIDGE_ETHEREUM_ADDRESS = '0x0439e60F02a8900a951603950d8D4527f400C3f1'; export const BRIDGE_QUOTE_MAX_ETA_SECONDS = 60 * 60; // 1 hour -export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.8; // if a quote returns in x times less return than the best quote, ignore it +export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.5; // if a quote returns in x times less return than the best quote, ignore it -export const BRIDGE_PREFERRED_GAS_ESTIMATE = 'medium'; +export const BRIDGE_PREFERRED_GAS_ESTIMATE = 'high'; +export const BRIDGE_DEFAULT_SLIPPAGE = 0.5; + +export const NETWORK_TO_SHORT_NETWORK_NAME_MAP: Record< + AllowedBridgeChainIds, + string +> = { + [CHAIN_IDS.MAINNET]: 'Ethereum', + [CHAIN_IDS.LINEA_MAINNET]: 'Linea', + [CHAIN_IDS.POLYGON]: NETWORK_TO_NAME_MAP[CHAIN_IDS.POLYGON], + [CHAIN_IDS.AVALANCHE]: 'Avalanche', + [CHAIN_IDS.BSC]: NETWORK_TO_NAME_MAP[CHAIN_IDS.BSC], + [CHAIN_IDS.ARBITRUM]: NETWORK_TO_NAME_MAP[CHAIN_IDS.ARBITRUM], + [CHAIN_IDS.OPTIMISM]: NETWORK_TO_NAME_MAP[CHAIN_IDS.OPTIMISM], + [CHAIN_IDS.ZKSYNC_ERA]: 'ZkSync Era', + [CHAIN_IDS.BASE]: 'Base', + ///: BEGIN:ONLY_INCLUDE_IF(solana-swaps) + [MultichainNetworks.SOLANA]: 'Solana', + [MultichainNetworks.SOLANA_TESTNET]: 'Solana Testnet', + [MultichainNetworks.SOLANA_DEVNET]: 'Solana Devnet', + [MultichainNetworks.BITCOIN]: 'Bitcoin', + [MultichainNetworks.BITCOIN_TESTNET]: 'Bitcoin Testnet', + ///: END:ONLY_INCLUDE_IF +}; +export const BRIDGE_MM_FEE_RATE = 0.875; +export const REFRESH_INTERVAL_MS = 30 * 1000; +export const DEFAULT_MAX_REFRESH_COUNT = 5; diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 760e3c26a31f..8b59e95e650c 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -594,6 +594,10 @@ export enum MetaMetricsUserTrait { * Identifies if the Privacy Mode is enabled */ PrivacyModeEnabled = 'privacy_mode_toggle', + /** + * Identified when the user prefers to see all tokens or current network tokens in wallet list + */ + NetworkFilterPreference = 'selected_network_filter', } /** @@ -636,10 +640,14 @@ export enum MetaMetricsEventName { ActivityDetailsClosed = 'Activity Details Closed', AnalyticsPreferenceSelected = 'Analytics Preference Selected', AppInstalled = 'App Installed', + AppOpened = 'App Opened', AppUnlocked = 'App Unlocked', AppUnlockedFailed = 'App Unlocked Failed', AppLocked = 'App Locked', AppWindowExpanded = 'App Window Expanded', + BannerDisplay = 'Banner Display', + BannerCloseAll = 'Banner Close All', + BannerSelect = 'Banner Select', BridgeLinkClicked = 'Bridge Link Clicked', BitcoinSupportToggled = 'Bitcoin Support Toggled', BitcoinTestnetSupportToggled = 'Bitcoin Testnet Support Toggled', @@ -761,6 +769,7 @@ export enum MetaMetricsEventName { TokenAdded = 'Token Added', TokenRemoved = 'Token Removed', TokenSortPreference = 'Token Sort Preference', + TokenListRefreshed = 'Token List Refreshed', NFTRemoved = 'NFT Removed', TokenDetected = 'Token Detected', TokenHidden = 'Token Hidden', @@ -857,7 +866,6 @@ export enum MetaMetricsEventName { NotificationsActivated = 'Notifications Activated', PushNotificationReceived = 'Push Notification Received', PushNotificationClicked = 'Push Notification Clicked', - // Send sendAssetSelected = 'Send Asset Selected', sendFlowExited = 'Send Flow Exited', @@ -866,6 +874,19 @@ export enum MetaMetricsEventName { sendSwapQuoteRequested = 'Send Swap Quote Requested', sendSwapQuoteReceived = 'Send Swap Quote Received', sendTokenModalOpened = 'Send Token Modal Opened', + // Cross Chain Swaps + ActionCompleted = 'Action Completed', + ActionFailed = 'Action Failed', + ActionOpened = 'Action Opened', + ActionSubmitted = 'Action Submitted', + AllQuotesOpened = 'All Quotes Opened', + AllQuotesSorted = 'All Quotes Sorted', + InputChanged = 'Input Changed', + InputSourceDestinationFlipped = 'Source and Destination Flipped', + CrossChainSwapsQuoteError = 'Cross-chain Quote Error', + QuoteSelected = 'Quote Selected', + CrossChainSwapsQuotesReceived = 'Cross-chain Quotes Received', + CrossChainSwapsQuotesRequested = 'Cross-chain Quotes Requested', } export enum MetaMetricsEventAccountType { @@ -894,6 +915,7 @@ export enum MetaMetricsEventCategory { App = 'App', Auth = 'Auth', Background = 'Background', + Banner = 'Banner', // The TypeScript ESLint rule is incorrectly marking this line. /* eslint-disable-next-line @typescript-eslint/no-shadow */ Error = 'Error', @@ -927,6 +949,7 @@ export enum MetaMetricsEventCategory { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) MMI = 'Institutional', ///: END:ONLY_INCLUDE_IF + CrossChainSwaps = 'Cross Chain Swaps', } export enum MetaMetricsEventLinkType { @@ -1016,3 +1039,8 @@ export enum DeleteRegulationStatus { Running = 'RUNNING', Unknown = 'UNKNOWN', } + +export enum MetaMetricsEventTransactionEstimateType { + DappProposed = 'dapp_proposed', + DefaultEstimate = 'default_estimate', +} diff --git a/shared/constants/multichain/accounts.ts b/shared/constants/multichain/accounts.ts new file mode 100644 index 000000000000..b08c77417e2b --- /dev/null +++ b/shared/constants/multichain/accounts.ts @@ -0,0 +1,12 @@ +import { BtcAccountType, SolAccountType } from '@metamask/keyring-api'; +import { BITCOIN_WALLET_SNAP_ID } from '../../lib/accounts/bitcoin-wallet-snap'; +import { SOLANA_WALLET_SNAP_ID } from '../../lib/accounts/solana-wallet-snap'; + +export const MULTICHAIN_ACCOUNT_TYPE_TO_SNAP_ID = { + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + [BtcAccountType.P2wpkh]: BITCOIN_WALLET_SNAP_ID, + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(solana) + [SolAccountType.DataAccount]: SOLANA_WALLET_SNAP_ID, + ///: END:ONLY_INCLUDE_IF +}; diff --git a/shared/constants/network.test.ts b/shared/constants/network.test.ts index 9d9ca72b46cd..1fe489488f8f 100644 --- a/shared/constants/network.test.ts +++ b/shared/constants/network.test.ts @@ -87,11 +87,11 @@ describe('NetworkConstants', () => { expect(zksyncEraRpc.rpcEndpoints[0].url).not.toContain('infura.io'); }); - it('base entry should not use Infura', () => { + it('base entry should use Infura', () => { const [baseRpc] = FEATURED_RPCS.filter( (rpc) => rpc.chainId === CHAIN_IDS.BASE, ); - expect(baseRpc.rpcEndpoints[0].url).not.toContain('infura.io'); + expect(baseRpc.rpcEndpoints[0].url).toContain('infura.io'); }); }); }); diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 41f1d9fd0d95..a33fec836b20 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -127,8 +127,18 @@ export const CHAIN_IDS = { HARMONY: '0x63564c40', PALM: '0x2a15c308d', SEPOLIA: '0xaa36a7', + HOLESKY: '0x4268', LINEA_GOERLI: '0xe704', LINEA_SEPOLIA: '0xe705', + AMOY: '0x13882', + BASE_SEPOLIA: '0x14a34', + BLAST_SEPOLIA: '0xa0c71fd', + OPTIMISM_SEPOLIA: '0xaa37dc', + PALM_TESTNET: '0x2a15c3083', + CELO_TESTNET: '0xaef3', + ZK_SYNC_ERA_TESTNET: '0x12c', + MANTA_SEPOLIA: '0x138b', + UNICHAIN_SEPOLIA: '0x515', LINEA_MAINNET: '0xe708', AURORA: '0x4e454152', MOONBEAM: '0x504', @@ -155,8 +165,14 @@ export const CHAIN_IDS = { ARBITRUM_SEPOLIA: '0x66eee', NEAR: '0x18d', NEAR_TESTNET: '0x18e', + B3: '0x208d', + B3_TESTNET: '0x7c9', GRAVITY_ALPHA_MAINNET: '0x659', GRAVITY_ALPHA_TESTNET_SEPOLIA: '0x34c1', + LISK: '0x46f', + LISK_SEPOLIA: '0x106a', + INK_SEPOLIA: '0xba5eD', + INK: '0xdef1', } as const; export const CHAINLIST_CHAIN_IDS_MAP = { @@ -189,7 +205,7 @@ export const CHAINLIST_CHAIN_IDS_MAP = { HAQQ_NETWORK: '0x2be3', IOTEX_MAINNET: '0x1251', KCC_MAINNET: '0x141', - KLAYTN_MAINNET_CYPRESS: '0x2019', + KAIA_MAINNET: '0x2019', KROMA_MAINNET: '0xff', LIGHTLINK_PHOENIX_MAINNET: '0x762', MANTA_PACIFIC_MAINNET: '0xa9', @@ -212,9 +228,15 @@ export const CHAINLIST_CHAIN_IDS_MAP = { ZORA_MAINNET: '0x76adf1', FILECOIN: '0x13a', NUMBERS: '0x290b', + B3: '0x208d', + B3_TESTNET: '0x7c9', APE: '0x8173', GRAVITY_ALPHA_MAINNET: '0x659', GRAVITY_ALPHA_TESTNET_SEPOLIA: '0x34c1', + INK_SEPOLIA: '0xba5ed', + INK: '0xdef1', + SONEIUM_MAINNET: '0x74c', + SONEIUM_TESTNET: '0x79a', } as const; // To add a deprecation warning to a network, add it to the array @@ -264,7 +286,11 @@ export const SCROLL_SEPOLIA_DISPLAY_NAME = 'Scroll Sepolia'; export const OP_BNB_DISPLAY_NAME = 'opBNB'; export const BERACHAIN_DISPLAY_NAME = 'Berachain Artio'; export const METACHAIN_ONE_DISPLAY_NAME = 'Metachain One Mainnet'; - +export const LISK_DISPLAY_NAME = 'Lisk'; +export const LISK_SEPOLIA_DISPLAY_NAME = 'Lisk Sepolia'; +export const INK_SEPOLIA_DISPLAY_NAME = 'Ink Sepolia'; +export const INK_DISPLAY_NAME = 'Ink Mainnet'; +export const SONEIUM_DISPLAY_NAME = 'Soneium Mainnet'; export const infuraProjectId = process.env.INFURA_PROJECT_ID; export const getRpcUrl = ({ network, @@ -355,7 +381,7 @@ const CHAINLIST_CURRENCY_SYMBOLS_MAP = { NEAR_AURORA_MAINNET: 'ETH', KROMA_MAINNET: 'ETH', NEBULA_MAINNET: 'sFUEL', - KLAYTN_MAINNET_CYPRESS: 'KLAY', + KAIA_MAINNET: 'KAIA', ENDURANCE_SMART_CHAIN_MAINNET: 'ACE', CRONOS_MAINNET_BETA: 'CRO', FLARE_MAINNET: 'FLR', @@ -386,6 +412,9 @@ const CHAINLIST_CURRENCY_SYMBOLS_MAP = { ACALA_NETWORK: 'ACA', IOTEX_MAINNET: 'IOTX', APE: 'APE', + LISK: 'ETH', + SONEIUM_MAINNET: 'ETH', + SONEIUM_TESTNET: 'ETH', } as const; export const CHAINLIST_CURRENCY_SYMBOLS_MAP_NETWORK_COLLISION = { @@ -433,7 +462,7 @@ export const IOTEX_MAINNET_IMAGE_URL = './images/iotex.svg'; export const IOTEX_TOKEN_IMAGE_URL = './images/iotex-token.svg'; export const APE_TOKEN_IMAGE_URL = './images/ape-token.svg'; export const KCC_MAINNET_IMAGE_URL = './images/kcc-mainnet.svg'; -export const KLAYTN_MAINNET_IMAGE_URL = './images/klaytn.svg'; +export const KAIA_MAINNET_IMAGE_URL = './images/kaia.svg'; export const KROMA_MAINNET_IMAGE_URL = './images/kroma.svg'; export const LIGHT_LINK_IMAGE_URL = './images/lightlink.svg'; export const MANTA_PACIFIC_MAINNET_IMAGE_URL = './images/manta.svg'; @@ -465,9 +494,15 @@ export const NUMBERS_MAINNET_IMAGE_URL = './images/numbers-mainnet.svg'; export const NUMBERS_TOKEN_IMAGE_URL = './images/numbers-token.png'; export const SEI_IMAGE_URL = './images/sei.svg'; export const NEAR_IMAGE_URL = './images/near.svg'; +export const B3_IMAGE_URL = './images/b3.svg'; export const APE_IMAGE_URL = './images/ape.svg'; export const GRAVITY_ALPHA_MAINNET_IMAGE_URL = './images/gravity.svg'; export const GRAVITY_ALPHA_TESTNET_SEPOLIA_IMAGE_URL = './images/gravity.svg'; +export const LISK_IMAGE_URL = './images/lisk.svg'; +export const LISK_SEPOLIA_IMAGE_URL = './images/lisk_sepolia.svg'; +export const INK_SEPOLIA_IMAGE_URL = './images/ink-sepolia.svg'; +export const INK_IMAGE_URL = './images/ink.svg'; +export const SONEIUM_IMAGE_URL = './images/soneium.svg'; export const INFURA_PROVIDER_TYPES = [ NETWORK_TYPES.MAINNET, @@ -578,6 +613,8 @@ export const NETWORK_TO_NAME_MAP = { [CHAIN_IDS.ZKSYNC_ERA]: ZK_SYNC_ERA_DISPLAY_NAME, [CHAIN_IDS.BERACHAIN]: BERACHAIN_DISPLAY_NAME, [CHAIN_IDS.METACHAIN_ONE]: METACHAIN_ONE_DISPLAY_NAME, + [CHAIN_IDS.LISK]: LISK_DISPLAY_NAME, + [CHAIN_IDS.LISK_SEPOLIA]: LISK_SEPOLIA_DISPLAY_NAME, } as const; export const CHAIN_ID_TO_CURRENCY_SYMBOL_MAP = { @@ -640,8 +677,8 @@ export const CHAIN_ID_TO_CURRENCY_SYMBOL_MAP = { CHAINLIST_CURRENCY_SYMBOLS_MAP.KROMA_MAINNET, [CHAINLIST_CHAIN_IDS_MAP.NEBULA_MAINNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.NEBULA_MAINNET, - [CHAINLIST_CHAIN_IDS_MAP.KLAYTN_MAINNET_CYPRESS]: - CHAINLIST_CURRENCY_SYMBOLS_MAP.KLAYTN_MAINNET_CYPRESS, + [CHAINLIST_CHAIN_IDS_MAP.KAIA_MAINNET]: + CHAINLIST_CURRENCY_SYMBOLS_MAP.KAIA_MAINNET, [CHAINLIST_CHAIN_IDS_MAP.MOONRIVER]: CHAINLIST_CURRENCY_SYMBOLS_MAP.MOONRIVER, [CHAINLIST_CHAIN_IDS_MAP.ENDURANCE_SMART_CHAIN_MAINNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.ENDURANCE_SMART_CHAIN_MAINNET, @@ -695,6 +732,10 @@ export const CHAIN_ID_TO_CURRENCY_SYMBOL_MAP = { CHAINLIST_CURRENCY_SYMBOLS_MAP.ACALA_NETWORK, [CHAINLIST_CHAIN_IDS_MAP.IOTEX_MAINNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.IOTEX_MAINNET, + [CHAINLIST_CHAIN_IDS_MAP.SONEIUM_MAINNET]: + CHAINLIST_CURRENCY_SYMBOLS_MAP.SONEIUM_MAINNET, + [CHAINLIST_CHAIN_IDS_MAP.SONEIUM_TESTNET]: + CHAINLIST_CURRENCY_SYMBOLS_MAP.SONEIUM_TESTNET, } as const; /** @@ -775,7 +816,7 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAINLIST_CHAIN_IDS_MAP.IOTEX_MAINNET]: IOTEX_MAINNET_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.HAQQ_NETWORK]: HAQQ_NETWORK_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.KCC_MAINNET]: KCC_MAINNET_IMAGE_URL, - [CHAINLIST_CHAIN_IDS_MAP.KLAYTN_MAINNET_CYPRESS]: KLAYTN_MAINNET_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.KAIA_MAINNET]: KAIA_MAINNET_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.KROMA_MAINNET]: KROMA_MAINNET_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.LIGHTLINK_PHOENIX_MAINNET]: LIGHT_LINK_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.MANTA_PACIFIC_MAINNET]: @@ -806,10 +847,18 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAINLIST_CHAIN_IDS_MAP.BASE]: BASE_TOKEN_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.NUMBERS]: NUMBERS_MAINNET_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.SEI]: SEI_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.B3]: B3_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.B3_TESTNET]: B3_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.GRAVITY_ALPHA_MAINNET]: GRAVITY_ALPHA_MAINNET_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.GRAVITY_ALPHA_TESTNET_SEPOLIA]: GRAVITY_ALPHA_TESTNET_SEPOLIA_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.LISK]: LISK_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.LISK_SEPOLIA]: LISK_SEPOLIA_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.INK_SEPOLIA]: INK_SEPOLIA_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.INK]: INK_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.SONEIUM_MAINNET]: SONEIUM_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.SONEIUM_TESTNET]: SONEIUM_IMAGE_URL, } as const; export const CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP = { @@ -844,10 +893,14 @@ export const CHAIN_ID_TOKEN_IMAGE_MAP = { [CHAIN_IDS.MOONRIVER]: MOONRIVER_TOKEN_IMAGE_URL, [CHAIN_IDS.MOONBEAM]: MOONBEAM_TOKEN_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.IOTEX_MAINNET]: IOTEX_TOKEN_IMAGE_URL, + [CHAIN_IDS.B3]: B3_IMAGE_URL, + [CHAIN_IDS.B3_TESTNET]: B3_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.APE_MAINNET]: APE_TOKEN_IMAGE_URL, [CHAIN_IDS.GRAVITY_ALPHA_MAINNET]: GRAVITY_ALPHA_MAINNET_IMAGE_URL, [CHAIN_IDS.GRAVITY_ALPHA_TESTNET_SEPOLIA]: GRAVITY_ALPHA_TESTNET_SEPOLIA_IMAGE_URL, + [CHAINLIST_CHAIN_IDS_MAP.ZORA_MAINNET]: ETH_TOKEN_IMAGE_URL, + [CHAIN_IDS.INK]: ETH_TOKEN_IMAGE_URL, } as const; export const INFURA_BLOCKED_KEY = 'countryBlocked'; @@ -1067,7 +1120,7 @@ export const FEATURED_RPCS: AddNetworkFields[] = [ nativeCurrency: CURRENCY_SYMBOLS.ETH, rpcEndpoints: [ { - url: `https://mainnet.base.org`, + url: `https://base-mainnet.infura.io/v3/${infuraProjectId}`, type: RpcEndpointType.Custom, }, ], @@ -1077,6 +1130,63 @@ export const FEATURED_RPCS: AddNetworkFields[] = [ }, ]; +export const FEATURED_NETWORK_CHAIN_IDS = [ + CHAIN_IDS.MAINNET, + ...FEATURED_RPCS.map((rpc) => rpc.chainId), +]; + +export const infuraChainIdsTestNets: string[] = [ + CHAIN_IDS.SEPOLIA, + CHAIN_IDS.HOLESKY, + CHAIN_IDS.LINEA_SEPOLIA, + CHAIN_IDS.AMOY, + CHAIN_IDS.BASE_SEPOLIA, + CHAIN_IDS.OPTIMISM_SEPOLIA, + CHAIN_IDS.ARBITRUM_SEPOLIA, + CHAIN_IDS.PALM_TESTNET, + CHAIN_IDS.AVALANCHE_TESTNET, + CHAIN_IDS.CELO_TESTNET, + CHAIN_IDS.ZK_SYNC_ERA_TESTNET, + CHAIN_IDS.BSC_TESTNET, + CHAIN_IDS.MANTA_SEPOLIA, + CHAIN_IDS.OPBNB_TESTNET, + CHAIN_IDS.SCROLL_SEPOLIA, + CHAIN_IDS.UNICHAIN_SEPOLIA, +]; + +export const allowedInfuraHosts = [ + // Ethereum + 'mainnet.infura.io', + // Linea + 'linea-mainnet.infura.io', + // Polygon + 'polygon-mainnet.infura.io', + // Base + 'base-mainnet.infura.io', + // Blast + 'blast-mainnet.infura.io', + // Optimism + 'optimism-mainnet.infura.io', + // Arbitrum + 'arbitrum-mainnet.infura.io', + // Palm + 'palm-mainnet.infura.io', + // Avalanche + 'avalanche-mainnet.infura.io', + // Celo + 'celo-mainnet.infura.io', + // ZKSync + 'zksync-mainnet.infura.io', + // BSC + 'bsc-mainnet.infura.io', + // Mantle + 'mantle-mainnet.infura.io', + // OPBNB + 'opbnb-mainnet.infura.io', + // Scroll + 'scroll-mainnet.infura.io', +]; + /** * Represents the availability state of the currently selected network. */ diff --git a/shared/constants/signatures.ts b/shared/constants/signatures.ts index 3ecc56be8630..48b17afe51d9 100644 --- a/shared/constants/signatures.ts +++ b/shared/constants/signatures.ts @@ -9,6 +9,7 @@ export enum PrimaryTypePermit { PermitBatchTransferFrom = 'PermitBatchTransferFrom', PermitSingle = 'PermitSingle', PermitTransferFrom = 'PermitTransferFrom', + PermitWitnessTransferFrom = 'PermitWitnessTransferFrom', } /** diff --git a/shared/constants/smartTransactions.test.ts b/shared/constants/smartTransactions.test.ts index 47f4f8250c2b..4bd2f6f69f17 100644 --- a/shared/constants/smartTransactions.test.ts +++ b/shared/constants/smartTransactions.test.ts @@ -21,13 +21,14 @@ describe('smartTransactions', () => { expect(allowedChainIds).toStrictEqual([ CHAIN_IDS.MAINNET, CHAIN_IDS.SEPOLIA, + CHAIN_IDS.BSC, ]); }); it('should return the correct chain IDs for production environment', () => { mockIsProduction.mockReturnValue(true); const allowedChainIds = getAllowedSmartTransactionsChainIds(); - expect(allowedChainIds).toStrictEqual([CHAIN_IDS.MAINNET]); + expect(allowedChainIds).toStrictEqual([CHAIN_IDS.MAINNET, CHAIN_IDS.BSC]); }); }); }); diff --git a/shared/constants/smartTransactions.ts b/shared/constants/smartTransactions.ts index 4e39ec5ba234..35b143cb2d23 100644 --- a/shared/constants/smartTransactions.ts +++ b/shared/constants/smartTransactions.ts @@ -11,10 +11,12 @@ export const FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER: number = 2; const ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS_DEVELOPMENT: string[] = [ CHAIN_IDS.MAINNET, CHAIN_IDS.SEPOLIA, + CHAIN_IDS.BSC, ]; const ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS_PRODUCTION: string[] = [ CHAIN_IDS.MAINNET, + CHAIN_IDS.BSC, ]; export const getAllowedSmartTransactionsChainIds = (): string[] => { diff --git a/shared/constants/snaps/permissions.ts b/shared/constants/snaps/permissions.ts index 837beea4d9ff..28ae5e12a061 100644 --- a/shared/constants/snaps/permissions.ts +++ b/shared/constants/snaps/permissions.ts @@ -7,6 +7,7 @@ export const EndowmentPermissions = Object.freeze({ 'endowment:webassembly': 'endowment:webassembly', 'endowment:lifecycle-hooks': 'endowment:lifecycle-hooks', 'endowment:page-home': 'endowment:page-home', + 'endowment:page-settings': 'endowment:page-settings', 'endowment:signature-insight': 'endowment:signature-insight', 'endowment:name-lookup': 'endowment:name-lookup', ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) @@ -15,11 +16,11 @@ export const EndowmentPermissions = Object.freeze({ } as const); // Methods / permissions in external packages that we are temporarily excluding. -export const ExcludedSnapPermissions = Object.freeze({ - eth_accounts: +export const ExcludedSnapPermissions = Object.freeze({}); + +export const ExcludedSnapEndowments = Object.freeze({ + 'endowment:caip25': 'eth_accounts is disabled. For more information please see https://github.com/MetaMask/snaps/issues/990.', }); -export const ExcludedSnapEndowments = Object.freeze({}); - -export const DynamicSnapPermissions = Object.freeze(['eth_accounts']); +export const DynamicSnapPermissions = Object.freeze(['endowment:caip25']); diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index 8dfecccef6e6..545c47f06f1b 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -1,3 +1,6 @@ +///: BEGIN:ONLY_INCLUDE_IF(solana-swaps) +import { MultichainNetworks } from './multichain/networks'; +///: END:ONLY_INCLUDE_IF import { ETH_TOKEN_IMAGE_URL, TEST_ETH_TOKEN_IMAGE_URL, @@ -181,6 +184,9 @@ export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ CHAIN_IDS.ZKSYNC_ERA, CHAIN_IDS.LINEA_MAINNET, CHAIN_IDS.BASE, + ///: BEGIN:ONLY_INCLUDE_IF(solana-swaps) + MultichainNetworks.SOLANA, + ///: END:ONLY_INCLUDE_IF ] as const; export const ALLOWED_DEV_SWAPS_CHAIN_IDS = [ diff --git a/shared/lib/accounts/snaps.ts b/shared/lib/accounts/snaps.ts new file mode 100644 index 000000000000..e1b46e26a395 --- /dev/null +++ b/shared/lib/accounts/snaps.ts @@ -0,0 +1,23 @@ +import { SnapId } from '@metamask/snaps-sdk'; +import { BITCOIN_WALLET_SNAP_ID } from './bitcoin-wallet-snap'; +import { SOLANA_WALLET_SNAP_ID } from './solana-wallet-snap'; + +/** + * A constant array that contains the IDs of whitelisted multichain + * wallet Snaps. These Snaps can be used by the extension to implement + * core features (e.g. Send flow). + * + * @constant + * @type {SnapId[]} + */ +const WHITELISTED_SNAPS = [BITCOIN_WALLET_SNAP_ID, SOLANA_WALLET_SNAP_ID]; + +/** + * Checks if the given Snap ID corresponds to a multichain wallet Snap. + * + * @param id - The ID of the Snap to check. + * @returns True if the Snap ID is in the whitelist, false otherwise. + */ +export function isMultichainWalletSnap(id: SnapId): boolean { + return WHITELISTED_SNAPS.includes(id); +} diff --git a/shared/lib/confirmation.utils.test.ts b/shared/lib/confirmation.utils.test.ts index 552d78827a2e..c55d7d04952d 100644 --- a/shared/lib/confirmation.utils.test.ts +++ b/shared/lib/confirmation.utils.test.ts @@ -21,30 +21,6 @@ describe('confirmation.utils', () => { const unsupportedTransactionType = TransactionType.swap; - describe('when user setting is enabled', () => { - it('should return true for supported transaction types', () => { - supportedTransactionTypes.forEach((transactionType) => { - expect( - shouldUseRedesignForTransactions({ - transactionMetadataType: transactionType, - isRedesignedTransactionsUserSettingEnabled: true, // user setting enabled - isRedesignedConfirmationsDeveloperEnabled: false, // developer mode disabled - }), - ).toBe(true); - }); - }); - - it('should return false for unsupported transaction types', () => { - expect( - shouldUseRedesignForTransactions({ - transactionMetadataType: unsupportedTransactionType, - isRedesignedTransactionsUserSettingEnabled: true, // user setting enabled - isRedesignedConfirmationsDeveloperEnabled: false, // developer mode disabled - }), - ).toBe(false); - }); - }); - describe('when developer mode is enabled', () => { const originalEnv = process.env; @@ -63,7 +39,6 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForTransactions({ transactionMetadataType: transactionType, - isRedesignedTransactionsUserSettingEnabled: false, // user setting disabled isRedesignedConfirmationsDeveloperEnabled: false, // developer setting disabled }), ).toBe(true); @@ -75,7 +50,6 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForTransactions({ transactionMetadataType: transactionType, - isRedesignedTransactionsUserSettingEnabled: false, // user setting disabled isRedesignedConfirmationsDeveloperEnabled: true, // developer setting enabled }), ).toBe(true); @@ -88,39 +62,11 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForTransactions({ transactionMetadataType: unsupportedTransactionType, - isRedesignedTransactionsUserSettingEnabled: false, // user setting disabled isRedesignedConfirmationsDeveloperEnabled: true, // developer setting enabled }), ).toBe(false); }); }); - - describe('when both user setting and developer mode are disabled', () => { - const originalEnv = process.env; - - beforeEach(() => { - process.env = { ...originalEnv }; - process.env.ENABLE_CONFIRMATION_REDESIGN = 'false'; - }); - - afterEach(() => { - process.env = originalEnv; - }); - - it('should return false for all transaction types', () => { - [...supportedTransactionTypes, unsupportedTransactionType].forEach( - (transactionType) => { - expect( - shouldUseRedesignForTransactions({ - transactionMetadataType: transactionType, - isRedesignedTransactionsUserSettingEnabled: false, // user setting disabled - isRedesignedConfirmationsDeveloperEnabled: false, // developer setting disabled - }), - ).toBe(false); - }, - ); - }); - }); }); describe('shouldUseRedesignForSignatures', () => { @@ -146,7 +92,6 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForSignatures({ approvalType, - isRedesignedSignaturesUserSettingEnabled: true, // user setting enabled isRedesignedConfirmationsDeveloperEnabled: false, // developer setting disabled }), ).toBe(true); @@ -160,7 +105,6 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForSignatures({ approvalType, - isRedesignedSignaturesUserSettingEnabled: false, // user setting disabled isRedesignedConfirmationsDeveloperEnabled: false, // developer setting disabled }), ).toBe(true); @@ -172,7 +116,6 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForSignatures({ approvalType, - isRedesignedSignaturesUserSettingEnabled: false, // user setting disabled isRedesignedConfirmationsDeveloperEnabled: true, // developer setting enabled }), ).toBe(true); @@ -185,23 +128,21 @@ describe('confirmation.utils', () => { expect( shouldUseRedesignForSignatures({ approvalType: unsupportedApprovalType, - isRedesignedSignaturesUserSettingEnabled: true, // user setting enabled - isRedesignedConfirmationsDeveloperEnabled: true, // developer setting enabled + isRedesignedConfirmationsDeveloperEnabled: false, // developer setting enabled }), ).toBe(false); }); - it('should return false when both user setting and developer mode are disabled', () => { + it('should return true when a signature type is supported', () => { process.env.ENABLE_CONFIRMATION_REDESIGN = 'false'; supportedSignatureApprovalTypes.forEach((approvalType) => { expect( shouldUseRedesignForSignatures({ approvalType, - isRedesignedSignaturesUserSettingEnabled: false, // user setting disabled isRedesignedConfirmationsDeveloperEnabled: false, // developer setting disabled }), - ).toBe(false); + ).toBe(true); }); }); }); diff --git a/shared/lib/confirmation.utils.ts b/shared/lib/confirmation.utils.ts index 24c5f258a5d0..1f2b5b2b6283 100644 --- a/shared/lib/confirmation.utils.ts +++ b/shared/lib/confirmation.utils.ts @@ -30,23 +30,17 @@ const REDESIGN_DEV_TRANSACTION_TYPES = [...REDESIGN_USER_TRANSACTION_TYPES]; * based on user settings and developer mode * * @param opts.transactionMetadataType - The type of transaction to check - * @param opts.isRedesignedTransactionsUserSettingEnabled - Whether the user has enabled the redesigned flow * @param opts.isRedesignedConfirmationsDeveloperEnabled - Whether developer mode is enabled */ export function shouldUseRedesignForTransactions({ transactionMetadataType, - isRedesignedTransactionsUserSettingEnabled, isRedesignedConfirmationsDeveloperEnabled, }: { transactionMetadataType?: TransactionType; - isRedesignedTransactionsUserSettingEnabled: boolean; isRedesignedConfirmationsDeveloperEnabled: boolean; }): boolean { return ( - shouldUseRedesignForTransactionsUserMode( - isRedesignedTransactionsUserSettingEnabled, - transactionMetadataType, - ) || + shouldUseRedesignForTransactionsUserMode(transactionMetadataType) || shouldUseRedesignForTransactionsDeveloperMode( isRedesignedConfirmationsDeveloperEnabled, transactionMetadataType, @@ -59,28 +53,21 @@ export function shouldUseRedesignForTransactions({ * based on user settings and developer mode * * @param opts.approvalType - The type of signature approval to check - * @param opts.isRedesignedSignaturesUserSettingEnabled - Whether the user has enabled the redesigned flow * @param opts.isRedesignedConfirmationsDeveloperEnabled - Whether developer mode is enabled */ export function shouldUseRedesignForSignatures({ approvalType, - isRedesignedSignaturesUserSettingEnabled, isRedesignedConfirmationsDeveloperEnabled, }: { approvalType?: ApprovalType; - isRedesignedSignaturesUserSettingEnabled: boolean; isRedesignedConfirmationsDeveloperEnabled: boolean; }): boolean { const isRedesignedConfirmationsDeveloperSettingEnabled = process.env.ENABLE_CONFIRMATION_REDESIGN === 'true' || isRedesignedConfirmationsDeveloperEnabled; - if (!isCorrectSignatureApprovalType(approvalType)) { - return false; - } - return ( - isRedesignedSignaturesUserSettingEnabled || + isCorrectSignatureApprovalType(approvalType) || isRedesignedConfirmationsDeveloperSettingEnabled ); } @@ -155,15 +142,10 @@ function shouldUseRedesignForTransactionsDeveloperMode( * Determines if the redesigned confirmation flow should be used for transactions * when in user mode * - * @param isRedesignedTransactionsUserSettingEnabled - Whether the user has enabled the redesigned flow * @param transactionMetadataType - The type of transaction to check */ function shouldUseRedesignForTransactionsUserMode( - isRedesignedTransactionsUserSettingEnabled: boolean, transactionMetadataType?: TransactionType, ): boolean { - return ( - isRedesignedTransactionsUserSettingEnabled && - isCorrectUserTransactionType(transactionMetadataType) - ); + return isCorrectUserTransactionType(transactionMetadataType); } diff --git a/shared/lib/four-byte.test.ts b/shared/lib/four-byte.test.ts index 77271c4aeba3..2909bc169279 100644 --- a/shared/lib/four-byte.test.ts +++ b/shared/lib/four-byte.test.ts @@ -1,4 +1,4 @@ -import { HttpProvider } from '@metamask/ethjs'; +import { JsonRpcProvider } from '@ethersproject/providers'; import nock from 'nock'; import { @@ -68,10 +68,10 @@ describe('Four Byte', () => { }); describe('getMethodDataAsync', () => { - global.ethereumProvider = new HttpProvider( - 'https://mainnet.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', - ); it('returns a valid signature for setApprovalForAll when use4ByteResolution privacy setting is ON', async () => { + const provider = new JsonRpcProvider({ + url: 'https://mainnet.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', + }); nock('https://www.4byte.directory:443', { encodedQueryParams: true }) .get('/api/v1/signatures/') .query({ hex_signature: '0xa22cb465' }) @@ -96,7 +96,9 @@ describe('Four Byte', () => { }, ], }); - expect(await getMethodDataAsync('0xa22cb465', true)).toStrictEqual({ + expect( + await getMethodDataAsync('0xa22cb465', true, provider), + ).toStrictEqual({ name: 'Set Approval For All', params: [{ type: 'address' }, { type: 'bool' }], }); diff --git a/shared/lib/index.d.ts b/shared/lib/index.d.ts deleted file mode 100644 index 03952d5a6c3d..000000000000 --- a/shared/lib/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module '@metamask/ethjs'; diff --git a/shared/lib/multichain.ts b/shared/lib/multichain.ts index 26111a0970a2..49dc1add445b 100644 --- a/shared/lib/multichain.ts +++ b/shared/lib/multichain.ts @@ -6,7 +6,8 @@ import { } from '@metamask/utils'; import { validate, Network } from 'bitcoin-address-validation'; import { isAddress } from '@solana/addresses'; -import { InternalAccount, isEvmAccountType } from '@metamask/keyring-api'; +import { isEvmAccountType } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; /** * Returns whether an address is on the Bitcoin mainnet. diff --git a/shared/lib/token-util.ts b/shared/lib/token-util.ts index 18591b550ec7..37969287fd45 100644 --- a/shared/lib/token-util.ts +++ b/shared/lib/token-util.ts @@ -1,6 +1,7 @@ import { abiERC20, abiERC1155 } from '@metamask/metamask-eth-abis'; import { Contract } from '@ethersproject/contracts'; import { Web3Provider } from '@ethersproject/providers'; +import type { Provider } from '@metamask/network-controller'; /** * Gets the '_value' parameter of the given token transaction data @@ -28,9 +29,7 @@ export function getTokenIdParam(tokenData: any = {}): string | undefined { export async function fetchTokenBalance( address: string, userAddress: string, - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - provider: any, + provider: Provider, // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { diff --git a/shared/modules/bridge-utils/balance.test.ts b/shared/modules/bridge-utils/balance.test.ts index 15cf1f246c8b..03bd0c9500f1 100644 --- a/shared/modules/bridge-utils/balance.test.ts +++ b/shared/modules/bridge-utils/balance.test.ts @@ -25,8 +25,7 @@ describe('balance', () => { chainId: CHAIN_IDS.MAINNET, }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - global.ethereumProvider = provider as any; + global.ethereumProvider = provider; }); describe('calcLatestSrcBalance', () => { diff --git a/shared/modules/bridge-utils/balance.ts b/shared/modules/bridge-utils/balance.ts index 2ba5834d8df9..3900ac9400bd 100644 --- a/shared/modules/bridge-utils/balance.ts +++ b/shared/modules/bridge-utils/balance.ts @@ -1,13 +1,13 @@ import { Web3Provider } from '@ethersproject/providers'; +import type { Provider } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; import { zeroAddress } from 'ethereumjs-util'; import { getAddress } from 'ethers/lib/utils'; -import { HttpProvider } from '../../../types/global'; import { fetchTokenBalance } from '../../lib/token-util'; import { Numeric } from '../Numeric'; export const calcLatestSrcBalance = async ( - provider: HttpProvider, + provider: Provider, selectedAddress: string, tokenAddress: string, chainId: Hex, @@ -33,14 +33,14 @@ export const calcLatestSrcBalance = async ( }; export const hasSufficientBalance = async ( - provider: unknown, + provider: Provider, selectedAddress: string, tokenAddress: string, fromTokenAmount: string, chainId: Hex, ) => { const srcTokenBalance = await calcLatestSrcBalance( - provider as HttpProvider, + provider, selectedAddress, tokenAddress, chainId, diff --git a/ui/pages/bridge/bridge.util.test.ts b/shared/modules/bridge-utils/bridge.util.test.ts similarity index 76% rename from ui/pages/bridge/bridge.util.test.ts rename to shared/modules/bridge-utils/bridge.util.test.ts index 30514dbf7f96..b9fea3db1ea8 100644 --- a/ui/pages/bridge/bridge.util.test.ts +++ b/shared/modules/bridge-utils/bridge.util.test.ts @@ -1,8 +1,8 @@ -import fetchWithCache from '../../../shared/lib/fetch-with-cache'; -import { CHAIN_IDS } from '../../../shared/constants/network'; +import { zeroAddress } from 'ethereumjs-util'; +import fetchWithCache from '../../lib/fetch-with-cache'; +import { CHAIN_IDS } from '../../constants/network'; import mockBridgeQuotesErc20Erc20 from '../../../test/data/bridge/mock-quotes-erc20-erc20.json'; import mockBridgeQuotesNativeErc20 from '../../../test/data/bridge/mock-quotes-native-erc20.json'; -import { zeroAddress } from '../../__mocks__/ethereumjs-util'; import { fetchBridgeFeatureFlags, fetchBridgeQuotes, @@ -22,10 +22,34 @@ describe('Bridge utils', () => { 'extension-config': { refreshRate: 3, maxRefreshCount: 1, + support: true, + chains: { + '1': { + isActiveSrc: true, + isActiveDest: true, + }, + '10': { + isActiveSrc: true, + isActiveDest: false, + }, + '59144': { + isActiveSrc: true, + isActiveDest: true, + }, + '120': { + isActiveSrc: true, + isActiveDest: false, + }, + '137': { + isActiveSrc: false, + isActiveDest: true, + }, + '11111': { + isActiveSrc: false, + isActiveDest: true, + }, + }, }, - 'extension-support': true, - 'src-network-allowlist': [1, 10, 59144, 120], - 'dest-network-allowlist': [1, 137, 59144, 11111], }; (fetchWithCache as jest.Mock).mockResolvedValue(mockResponse); @@ -46,29 +70,54 @@ describe('Bridge utils', () => { extensionConfig: { maxRefreshCount: 1, refreshRate: 3, + support: true, + chains: { + [CHAIN_IDS.MAINNET]: { + isActiveSrc: true, + isActiveDest: true, + }, + [CHAIN_IDS.OPTIMISM]: { + isActiveSrc: true, + isActiveDest: false, + }, + [CHAIN_IDS.LINEA_MAINNET]: { + isActiveSrc: true, + isActiveDest: true, + }, + '0x78': { + isActiveSrc: true, + isActiveDest: false, + }, + [CHAIN_IDS.POLYGON]: { + isActiveSrc: false, + isActiveDest: true, + }, + '0x2b67': { + isActiveSrc: false, + isActiveDest: true, + }, + }, }, - extensionSupport: true, - srcNetworkAllowlist: [ - CHAIN_IDS.MAINNET, - CHAIN_IDS.OPTIMISM, - CHAIN_IDS.LINEA_MAINNET, - '0x78', - ], - destNetworkAllowlist: [ - CHAIN_IDS.MAINNET, - CHAIN_IDS.POLYGON, - CHAIN_IDS.LINEA_MAINNET, - '0x2b67', - ], }); }); it('should use fallback bridge feature flags if response is unexpected', async () => { const mockResponse = { - 'extension-support': 25, - 'src-network-allowlist': ['a', 'b', 1], - a: 'b', - 'dest-network-allowlist': [1, 137, 59144, 11111], + 'extension-config': { + refreshRate: 3, + maxRefreshCount: 1, + support: 25, + chains: { + a: { + isActiveSrc: 1, + isActiveDest: 'test', + }, + '2': { + isActiveSrc: 'test', + isActiveDest: 2, + }, + }, + }, }; (fetchWithCache as jest.Mock).mockResolvedValue(mockResponse); @@ -89,10 +138,9 @@ describe('Bridge utils', () => { extensionConfig: { maxRefreshCount: 5, refreshRate: 30000, + support: false, + chains: {}, }, - extensionSupport: false, - srcNetworkAllowlist: [], - destNetworkAllowlist: [], }); }); @@ -101,7 +149,7 @@ describe('Bridge utils', () => { (fetchWithCache as jest.Mock).mockRejectedValue(mockError); - await expect(fetchBridgeFeatureFlags()).rejects.toThrowError(mockError); + await expect(fetchBridgeFeatureFlags()).rejects.toThrow(mockError); }); }); @@ -119,6 +167,12 @@ describe('Bridge utils', () => { }, { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f986', + decimals: 16, + symbol: 'DEF', + aggregators: ['lifi'], + }, + { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f987', symbol: 'DEF', }, { @@ -150,6 +204,12 @@ describe('Bridge utils', () => { name: 'Ether', symbol: 'ETH', }, + '0x1f9840a85d5af5bf1d1762f925bdaddc4201f986': { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f986', + decimals: 16, + symbol: 'DEF', + aggregators: ['lifi'], + }, '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', decimals: 16, @@ -163,7 +223,7 @@ describe('Bridge utils', () => { (fetchWithCache as jest.Mock).mockRejectedValue(mockError); - await expect(fetchBridgeTokens('0xa')).rejects.toThrowError(mockError); + await expect(fetchBridgeTokens('0xa')).rejects.toThrow(mockError); }); }); diff --git a/ui/pages/bridge/bridge.util.ts b/shared/modules/bridge-utils/bridge.util.ts similarity index 74% rename from ui/pages/bridge/bridge.util.ts rename to shared/modules/bridge-utils/bridge.util.ts index 577b1827b160..b7da42ba9cc6 100644 --- a/ui/pages/bridge/bridge.util.ts +++ b/shared/modules/bridge-utils/bridge.util.ts @@ -1,36 +1,25 @@ import { Contract } from '@ethersproject/contracts'; import { Hex, add0x } from '@metamask/utils'; import { abiERC20 } from '@metamask/metamask-eth-abis'; -import { - BridgeFeatureFlagsKey, - BridgeFeatureFlags, - // TODO: Remove restricted import - // eslint-disable-next-line import/no-restricted-paths -} from '../../../app/scripts/controllers/bridge/types'; import { BRIDGE_API_BASE_URL, BRIDGE_CLIENT_ID, ETH_USDT_ADDRESS, METABRIDGE_ETHEREUM_ADDRESS, -} from '../../../shared/constants/bridge'; -import { MINUTE } from '../../../shared/constants/time'; -import fetchWithCache from '../../../shared/lib/fetch-with-cache'; -import { - decimalToHex, - hexToDecimal, -} from '../../../shared/modules/conversion.utils'; + REFRESH_INTERVAL_MS, +} from '../../constants/bridge'; +import { MINUTE } from '../../constants/time'; +import fetchWithCache from '../../lib/fetch-with-cache'; +import { decimalToHex, hexToDecimal } from '../conversion.utils'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP, SwapsTokenObject, -} from '../../../shared/constants/swaps'; +} from '../../constants/swaps'; import { isSwapsDefaultTokenAddress, isSwapsDefaultTokenSymbol, -} from '../../../shared/modules/swaps.utils'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { REFRESH_INTERVAL_MS } from '../../../app/scripts/controllers/bridge/constants'; -import { CHAIN_IDS } from '../../../shared/constants/network'; +} from '../swaps.utils'; +import { CHAIN_IDS } from '../../constants/network'; import { BridgeAsset, BridgeFlag, @@ -41,7 +30,9 @@ import { QuoteRequest, QuoteResponse, TxData, -} from './types'; + BridgeFeatureFlagsKey, + BridgeFeatureFlags, +} from '../../types/bridge'; import { FEATURE_FLAG_VALIDATORS, QUOTE_VALIDATORS, @@ -50,7 +41,7 @@ import { validateResponse, QUOTE_RESPONSE_VALIDATORS, FEE_DATA_VALIDATORS, -} from './utils/validators'; +} from './validators'; const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID }; const CACHE_REFRESH_TEN_MINUTES = 10 * MINUTE; @@ -72,16 +63,18 @@ export async function fetchBridgeFeatureFlags(): Promise { ) ) { return { - [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: - rawFeatureFlags[BridgeFlag.EXTENSION_CONFIG], - [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: - rawFeatureFlags[BridgeFlag.EXTENSION_SUPPORT], - [BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST]: rawFeatureFlags[ - BridgeFlag.NETWORK_SRC_ALLOWLIST - ].map((chainIdDec) => add0x(decimalToHex(chainIdDec))), - [BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: rawFeatureFlags[ - BridgeFlag.NETWORK_DEST_ALLOWLIST - ].map((chainIdDec) => add0x(decimalToHex(chainIdDec))), + [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: { + ...rawFeatureFlags[BridgeFlag.EXTENSION_CONFIG], + chains: Object.entries( + rawFeatureFlags[BridgeFlag.EXTENSION_CONFIG].chains, + ).reduce( + (acc, [chainId, value]) => ({ + ...acc, + [add0x(decimalToHex(chainId))]: value, + }), + {}, + ), + }, }; } @@ -89,13 +82,9 @@ export async function fetchBridgeFeatureFlags(): Promise { [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: { refreshRate: REFRESH_INTERVAL_MS, maxRefreshCount: 5, + support: false, + chains: {}, }, - // TODO set default to true once bridging is live - [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false, - // TODO set default to ALLOWED_BRIDGE_CHAIN_IDS once bridging is live - [BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST]: [], - // TODO set default to ALLOWED_BRIDGE_CHAIN_IDS once bridging is live - [BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: [], }; } @@ -126,7 +115,7 @@ export async function fetchBridgeTokens( tokens.forEach((token: unknown) => { if ( - validateResponse(TOKEN_VALIDATORS, token, url) && + validateResponse(TOKEN_VALIDATORS, token, url, false) && !( isSwapsDefaultTokenSymbol(token.symbol, chainId) || isSwapsDefaultTokenAddress(token.address, chainId) diff --git a/shared/modules/bridge-utils/quote.ts b/shared/modules/bridge-utils/quote.ts new file mode 100644 index 000000000000..3fe4406fa333 --- /dev/null +++ b/shared/modules/bridge-utils/quote.ts @@ -0,0 +1,36 @@ +import type { QuoteRequest } from '../../types/bridge'; + +export const isValidQuoteRequest = ( + partialRequest: Partial, + requireAmount = true, +): partialRequest is QuoteRequest => { + const STRING_FIELDS = ['srcTokenAddress', 'destTokenAddress']; + if (requireAmount) { + STRING_FIELDS.push('srcTokenAmount'); + } + const NUMBER_FIELDS = ['srcChainId', 'destChainId', 'slippage']; + + return ( + STRING_FIELDS.every( + (field) => + field in partialRequest && + typeof partialRequest[field as keyof typeof partialRequest] === + 'string' && + partialRequest[field as keyof typeof partialRequest] !== undefined && + partialRequest[field as keyof typeof partialRequest] !== '' && + partialRequest[field as keyof typeof partialRequest] !== null, + ) && + NUMBER_FIELDS.every( + (field) => + field in partialRequest && + typeof partialRequest[field as keyof typeof partialRequest] === + 'number' && + partialRequest[field as keyof typeof partialRequest] !== undefined && + !isNaN(Number(partialRequest[field as keyof typeof partialRequest])) && + partialRequest[field as keyof typeof partialRequest] !== null, + ) && + (requireAmount + ? Boolean((partialRequest.srcTokenAmount ?? '').match(/^[1-9]\d*$/u)) + : true) + ); +}; diff --git a/ui/pages/bridge/utils/validators.ts b/shared/modules/bridge-utils/validators.ts similarity index 80% rename from ui/pages/bridge/utils/validators.ts rename to shared/modules/bridge-utils/validators.ts index 01c716522968..edfbabdafaa3 100644 --- a/ui/pages/bridge/utils/validators.ts +++ b/shared/modules/bridge-utils/validators.ts @@ -1,10 +1,7 @@ import { isStrictHexString } from '@metamask/utils'; import { isValidHexAddress as isValidHexAddress_ } from '@metamask/controller-utils'; -import { - truthyDigitString, - validateData, -} from '../../../../shared/lib/swaps-utils'; -import { BridgeFlag, FeatureFlagResponse } from '../types'; +import { truthyDigitString, validateData } from '../../lib/swaps-utils'; +import { BridgeFlag, FeatureFlagResponse } from '../../types/bridge'; type Validator = { property: keyof ExpectedResponse | string; @@ -16,8 +13,9 @@ export const validateResponse = ( validators: Validator[], data: unknown, urlUsed: string, + logError = true, ): data is ExpectedResponse => { - return validateData(validators, data, urlUsed); + return validateData(validators, data, urlUsed, logError); }; export const isValidNumber = (v: unknown): v is number => typeof v === 'number'; @@ -39,20 +37,26 @@ export const FEATURE_FLAG_VALIDATORS = [ 'refreshRate' in v && isValidNumber(v.refreshRate) && 'maxRefreshCount' in v && - isValidNumber(v.maxRefreshCount), - }, - { property: BridgeFlag.EXTENSION_SUPPORT, type: 'boolean' }, - { - property: BridgeFlag.NETWORK_SRC_ALLOWLIST, - type: 'object', - validator: (v: unknown): v is number[] => - isValidObject(v) && Object.values(v).every(isValidNumber), + isValidNumber(v.maxRefreshCount) && + 'chains' in v && + isValidObject(v.chains) && + Object.values(v.chains).every((chain) => isValidObject(chain)) && + Object.values(v.chains).every( + (chain) => + 'isActiveSrc' in chain && + 'isActiveDest' in chain && + typeof chain.isActiveSrc === 'boolean' && + typeof chain.isActiveDest === 'boolean', + ), }, +]; + +export const TOKEN_AGGREGATOR_VALIDATORS = [ { - property: BridgeFlag.NETWORK_DEST_ALLOWLIST, + property: 'aggregators', type: 'object', validator: (v: unknown): v is number[] => - isValidObject(v) && Object.values(v).every(isValidNumber), + isValidObject(v) && Object.values(v).every(isValidString), }, ]; diff --git a/shared/modules/contract-utils.test.js b/shared/modules/contract-utils.test.js index 622f76d62948..eeac97c02dfc 100644 --- a/shared/modules/contract-utils.test.js +++ b/shared/modules/contract-utils.test.js @@ -1,27 +1,29 @@ +import { createTestProviderTools } from '../../test/stub/provider'; + const { readAddressAsContract } = require('./contract-utils'); describe('Contract Utils', () => { it('checks is an address is a contract address or not', async () => { - let mockEthQuery = { - getCode: (_, cb) => { - cb(null, '0xa'); + let mockProvider = createTestProviderTools({ + scaffold: { + eth_getCode: '0xa', }, - }; + }).provider; const { isContractAddress } = await readAddressAsContract( - mockEthQuery, + mockProvider, '0x76B4aa9Fc4d351a0062c6af8d186DF959D564A84', ); expect(isContractAddress).toStrictEqual(true); - mockEthQuery = { - getCode: (_, cb) => { - cb(null, '0x'); + mockProvider = createTestProviderTools({ + scaffold: { + eth_getCode: '0x', }, - }; + }).provider; const { isContractAddress: isNotContractAddress } = await readAddressAsContract( - mockEthQuery, + mockProvider, '0x76B4aa9Fc4d351a0062c6af8d186DF959D564A84', ); expect(isNotContractAddress).toStrictEqual(false); diff --git a/shared/modules/contract-utils.ts b/shared/modules/contract-utils.ts index 43e608150a1b..5faf6b9d1339 100644 --- a/shared/modules/contract-utils.ts +++ b/shared/modules/contract-utils.ts @@ -1,5 +1,6 @@ import pify from 'pify'; -import type EthQuery from '@metamask/eth-query'; +import type { Provider } from '@metamask/network-controller'; +import { addHexPrefix, padToEven } from 'ethereumjs-util'; export type Contract = { contractCode: string | null; @@ -7,14 +8,16 @@ export type Contract = { }; export const readAddressAsContract = async ( - ethQuery: EthQuery, + provider: Provider, address: string, ): Promise => { let contractCode: string | null = null; try { - if (ethQuery && 'getCode' in ethQuery) { - contractCode = await pify(ethQuery.getCode.bind(ethQuery))(address); - } + const { result } = await pify(provider.sendAsync.bind(provider))({ + method: 'eth_getCode', + params: [address, 'latest'], + }); + contractCode = addHexPrefix(padToEven(result.slice(2))); } catch (err) { // TODO(@dbrans): Dangerous to swallow errors here. contractCode = null; diff --git a/shared/modules/gas.utils.js b/shared/modules/gas.utils.js index 69e4f2664b14..c73881db4d39 100644 --- a/shared/modules/gas.utils.js +++ b/shared/modules/gas.utils.js @@ -43,8 +43,7 @@ export function getMaximumGasTotalInHexWei({ * transaction will cost. For gasPrice types this is the same as max. * * @param {object} options - gas fee parameters object - * @param {string} [options.gasLimit] - the maximum amount of gas to allow this - * transaction to consume. Value is a hex string + * @param {string} [options.gasLimitNoBuffer] - gas limit without buffer * @param {string} [options.gasPrice] - The fee in wei to pay per gas used. * gasPrice is only set on Legacy type transactions. Value is hex string * @param {string} [options.maxFeePerGas] - The maximum fee in wei to pay per @@ -58,7 +57,7 @@ export function getMaximumGasTotalInHexWei({ * @returns {string} The minimum total cost of transaction in hex wei string */ export function getMinimumGasTotalInHexWei({ - gasLimit = '0x0', + gasLimitNoBuffer = '0x0', gasPrice, maxPriorityFeePerGas, maxFeePerGas, @@ -91,16 +90,22 @@ export function getMinimumGasTotalInHexWei({ ); } if (isEIP1559Estimate === false) { - return getMaximumGasTotalInHexWei({ gasLimit, gasPrice }); + return getMaximumGasTotalInHexWei({ + gasLimit: gasLimitNoBuffer, + gasPrice, + }); } const minimumFeePerGas = new Numeric(baseFeePerGas, 16) .add(new Numeric(maxPriorityFeePerGas, 16)) .toString(); if (new Numeric(minimumFeePerGas, 16).greaterThan(maxFeePerGas, 16)) { - return getMaximumGasTotalInHexWei({ gasLimit, maxFeePerGas }); + return getMaximumGasTotalInHexWei({ + gasLimit: gasLimitNoBuffer, + maxFeePerGas, + }); } - return new Numeric(gasLimit, 16) + return new Numeric(gasLimitNoBuffer, 16) .times(new Numeric(minimumFeePerGas, 16)) .toPrefixedHexString(); } diff --git a/shared/modules/gas.utils.test.js b/shared/modules/gas.utils.test.js index 240183a70df8..04df90dbc481 100644 --- a/shared/modules/gas.utils.test.js +++ b/shared/modules/gas.utils.test.js @@ -48,7 +48,7 @@ describe('gas utils', () => { const gasLimitHex = addHexPrefix(gasLimit.toString(16)); const result = new Numeric( getMinimumGasTotalInHexWei({ - gasLimit: gasLimitHex, + gasLimitNoBuffer: gasLimitHex, maxFeePerGas: addHexPrefix(maxFeePerGas.toString(16)), maxPriorityFeePerGas: addHexPrefix( maxPriorityFeePerGas.toString(16), @@ -117,7 +117,7 @@ describe('gas utils', () => { expect( new Numeric( getMinimumGasTotalInHexWei({ - gasLimit: gasLimitHex, + gasLimitNoBuffer: gasLimitHex, gasPrice: addHexPrefix(gasPrice.toString(16)), }), 16, diff --git a/shared/modules/metametrics.test.ts b/shared/modules/metametrics.test.ts index 9d6d17bc8040..a77afb4132d3 100644 --- a/shared/modules/metametrics.test.ts +++ b/shared/modules/metametrics.test.ts @@ -41,7 +41,6 @@ const createTransactionMetricsRequest = (customProps = {}) => { trackEvent: jest.fn(), getIsSmartTransaction: jest.fn(), getSmartTransactionByMinedTxHash: jest.fn(), - getRedesignedTransactionsEnabled: jest.fn(), getMethodData: jest.fn(), getIsRedesignedConfirmationsDeveloperEnabled: jest.fn(), getIsConfirmationAdvancedDetailsOpen: jest.fn(), @@ -92,7 +91,6 @@ describe('getSmartTransactionMetricsProperties', () => { cancellationReason: 'not_cancelled', deadlineRatio: 0.6400288486480713, minedHash: txHash, - duplicated: true, timedOut: true, proxied: true, minedTx: 'success', @@ -112,7 +110,6 @@ describe('getSmartTransactionMetricsProperties', () => { expect(result).toStrictEqual({ gas_included: true, is_smart_transaction: true, - smart_transaction_duplicated: true, smart_transaction_proxied: true, smart_transaction_timed_out: true, }); diff --git a/shared/modules/metametrics.ts b/shared/modules/metametrics.ts index b689891da1fb..389a2778a5b8 100644 --- a/shared/modules/metametrics.ts +++ b/shared/modules/metametrics.ts @@ -6,7 +6,6 @@ import { TransactionMetricsRequest } from '../../app/scripts/lib/transaction/met type SmartTransactionMetricsProperties = { is_smart_transaction: boolean; gas_included: boolean; - smart_transaction_duplicated?: boolean; smart_transaction_timed_out?: boolean; smart_transaction_proxied?: boolean; }; @@ -31,8 +30,6 @@ export const getSmartTransactionMetricsProperties = ( if (!smartTransactionStatusMetadata) { return properties; } - properties.smart_transaction_duplicated = - smartTransactionStatusMetadata.duplicated; properties.smart_transaction_timed_out = smartTransactionStatusMetadata.timedOut; properties.smart_transaction_proxied = smartTransactionStatusMetadata.proxied; diff --git a/shared/modules/selectors/feature-flags.ts b/shared/modules/selectors/feature-flags.ts index 429df2c06da6..34358e2e0e9f 100644 --- a/shared/modules/selectors/feature-flags.ts +++ b/shared/modules/selectors/feature-flags.ts @@ -1,7 +1,5 @@ -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { getCurrentChainId } from '../../../ui/selectors/selectors'; // TODO: Migrate shared selectors to this file. import { getNetworkNameByChainId } from '../feature-flags'; +import { ProviderConfigState, getCurrentChainId } from './networks'; type FeatureFlagsMetaMaskState = { metamask: { @@ -13,7 +11,7 @@ type FeatureFlagsMetaMaskState = { smartTransactions: { expectedDeadline?: number; maxDeadline?: number; - returnTxHashAsap?: boolean; + extensionReturnTxHashAsap?: boolean; }; }; }; @@ -21,7 +19,9 @@ type FeatureFlagsMetaMaskState = { }; }; -export function getFeatureFlagsByChainId(state: FeatureFlagsMetaMaskState) { +export function getFeatureFlagsByChainId( + state: ProviderConfigState & FeatureFlagsMetaMaskState, +) { const chainId = getCurrentChainId(state); const networkName = getNetworkNameByChainId(chainId); const featureFlags = state.metamask.swapsState?.swapsFeatureFlags; diff --git a/shared/modules/selectors/index.test.ts b/shared/modules/selectors/index.test.ts index 2e40d47db102..5429bd0166a5 100644 --- a/shared/modules/selectors/index.test.ts +++ b/shared/modules/selectors/index.test.ts @@ -47,7 +47,7 @@ describe('Selectors', () => { smartTransactions: { expectedDeadline: 45, maxDeadline: 150, - returnTxHashAsap: false, + extensionReturnTxHashAsap: false, }, }, smartTransactions: { @@ -298,7 +298,7 @@ describe('Selectors', () => { smartTransactions: { expectedDeadline: 45, maxDeadline: 150, - returnTxHashAsap: false, + extensionReturnTxHashAsap: false, }, }, smartTransactions: { diff --git a/shared/modules/selectors/networks.ts b/shared/modules/selectors/networks.ts index 41aad6da6948..bc8d05b5164d 100644 --- a/shared/modules/selectors/networks.ts +++ b/shared/modules/selectors/networks.ts @@ -1,33 +1,32 @@ import { RpcEndpointType, type NetworkConfiguration, - type NetworkState as _NetworkState, + type NetworkState as InternalNetworkState, } from '@metamask/network-controller'; import { createSelector } from 'reselect'; import { NetworkStatus } from '../../constants/network'; import { createDeepEqualSelector } from './util'; -export type NetworkState = { metamask: _NetworkState }; +export type NetworkState = { + metamask: InternalNetworkState; +}; export type NetworkConfigurationsState = { metamask: { - networkConfigurations: Record< - string, - MetaMaskExtensionNetworkConfiguration - >; + networkConfigurations: Record; }; }; export type SelectedNetworkClientIdState = { - metamask: { - selectedNetworkClientId: string; - }; + metamask: Pick; }; -export type MetaMaskExtensionNetworkConfiguration = NetworkConfiguration; - export type NetworkConfigurationsByChainIdState = { - metamask: Pick<_NetworkState, 'networkConfigurationsByChainId'>; + metamask: Pick; +}; + +export type NetworksMetadataState = { + metamask: Pick; }; export type ProviderConfigState = NetworkConfigurationsByChainIdState & @@ -49,6 +48,7 @@ export function getSelectedNetworkClientId( * Get the provider configuration for the current selected network. * * @param state - Redux state object. + * @throws `new Error('Provider configuration not found')` If the provider configuration is not found. */ export const getProviderConfig = createSelector( (state: ProviderConfigState) => getNetworkConfigurationsByChainId(state), @@ -81,13 +81,13 @@ export const getProviderConfig = createSelector( } } } - return undefined; // should not be reachable + throw new Error('Provider configuration not found'); }, ); export function getNetworkConfigurations( state: NetworkConfigurationsState, -): Record { +): Record { return state.metamask.networkConfigurations; } @@ -106,9 +106,16 @@ export function isNetworkLoading(state: NetworkState) { ); } -export function getInfuraBlocked(state: NetworkState) { +export function getInfuraBlocked( + state: SelectedNetworkClientIdState & NetworksMetadataState, +) { return ( state.metamask.networksMetadata[getSelectedNetworkClientId(state)] .status === NetworkStatus.Blocked ); } + +export function getCurrentChainId(state: ProviderConfigState) { + const { chainId } = getProviderConfig(state); + return chainId; +} diff --git a/shared/modules/selectors/smart-transactions.ts b/shared/modules/selectors/smart-transactions.ts index b88d5f7c029b..f3f7bb922711 100644 --- a/shared/modules/selectors/smart-transactions.ts +++ b/shared/modules/selectors/smart-transactions.ts @@ -4,7 +4,6 @@ import { SKIP_STX_RPC_URL_CHECK_CHAIN_IDS, } from '../../constants/smartTransactions'; import { - getCurrentChainId, getCurrentNetwork, accountSupportsSmartTx, getPreferences, @@ -12,12 +11,13 @@ import { // eslint-disable-next-line import/no-restricted-paths } from '../../../ui/selectors/selectors'; // TODO: Migrate shared selectors to this file. import { isProduction } from '../environment'; -import { NetworkState } from './networks'; +import { getCurrentChainId, NetworkState } from './networks'; type SmartTransactionsMetaMaskState = { metamask: { preferences: { smartTransactionsOptInStatus?: boolean; + smartTransactionsMigrationApplied?: boolean; }; internalAccounts: { selectedAccount: string; @@ -39,7 +39,7 @@ type SmartTransactionsMetaMaskState = { smartTransactions: { expectedDeadline?: number; maxDeadline?: number; - returnTxHashAsap?: boolean; + extensionReturnTxHashAsap?: boolean; }; }; smartTransactions: { @@ -73,6 +73,25 @@ export const getSmartTransactionsOptInStatusInternal = createSelector( }, ); +/** + * Returns whether the smart transactions migration has been applied to the user's settings. + * This specifically tracks if Migration 135 has been run, which enables Smart Transactions + * by default for users who have never interacted with the feature or who previously opted out + * with no STX activity. + * + * This should only be used for internal checks of the migration status, and not + * for determining overall Smart Transactions availability. + * + * @param state - The state object. + * @returns true if the migration has been applied to the user's settings, false if not or if unset. + */ +export const getSmartTransactionsMigrationAppliedInternal = createSelector( + getPreferences, + (preferences: { smartTransactionsMigrationApplied?: boolean }): boolean => { + return preferences?.smartTransactionsMigrationApplied ?? false; + }, +); + /** * Returns the user's explicit opt-in status for the smart transactions feature. * This should only be used for metrics collection, and not for determining if the @@ -85,6 +104,7 @@ export const getSmartTransactionsOptInStatusInternal = createSelector( * @returns true if the user has explicitly opted in, false if they have opted out, * or null if they have not explicitly opted in or out. */ +// @ts-expect-error TODO: Fix types for `getSmartTransactionsOptInStatusInternal` once `getPreferences is converted to TypeScript export const getSmartTransactionsOptInStatusForMetrics = createSelector( getSmartTransactionsOptInStatusInternal, (optInStatus: boolean): boolean => optInStatus, @@ -97,6 +117,7 @@ export const getSmartTransactionsOptInStatusForMetrics = createSelector( * @param state * @returns */ +// @ts-expect-error TODO: Fix types for `getSmartTransactionsOptInStatusInternal` once `getPreferences is converted to TypeScript export const getSmartTransactionsPreferenceEnabled = createSelector( getSmartTransactionsOptInStatusInternal, (optInStatus: boolean): boolean => { @@ -108,7 +129,7 @@ export const getSmartTransactionsPreferenceEnabled = createSelector( ); export const getCurrentChainSupportsSmartTransactions = ( - state: SmartTransactionsMetaMaskState, + state: NetworkState, ): boolean => { const chainId = getCurrentChainId(state); return getAllowedSmartTransactionsChainIds().includes(chainId); diff --git a/shared/modules/selectors/util.js b/shared/modules/selectors/util.js index d44b7d905087..35153a82bc4d 100644 --- a/shared/modules/selectors/util.js +++ b/shared/modules/selectors/util.js @@ -1,9 +1,9 @@ import { TransactionStatus } from '@metamask/transaction-controller'; import { isEqual } from 'lodash'; -import { createSelectorCreator, defaultMemoize } from 'reselect'; +import { createSelectorCreator, lruMemoize } from 'reselect'; export const createDeepEqualSelector = createSelectorCreator( - defaultMemoize, + lruMemoize, isEqual, ); diff --git a/shared/modules/transaction.utils.test.js b/shared/modules/transaction.utils.test.js index 28bfaaa34ed7..c501491fce6f 100644 --- a/shared/modules/transaction.utils.test.js +++ b/shared/modules/transaction.utils.test.js @@ -1,4 +1,3 @@ -import EthQuery from '@metamask/ethjs-query'; import { TransactionType } from '@metamask/transaction-controller'; import { createTestProviderTools } from '../../test/stub/provider'; @@ -111,8 +110,7 @@ describe('Transaction.utils', function () { }); describe('determineTransactionType', function () { - const genericProvider = createTestProviderTools().provider; - const query = new EthQuery(genericProvider); + const { provider: genericProvider } = createTestProviderTools(); it('should return a simple send type when to is truthy and is not a contract address', async function () { const _providerResultStub = { @@ -121,16 +119,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0x', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0xabcabcabcabcabcabcabcabcabcabcabcabcabca', data: '', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.simpleSend, @@ -145,16 +143,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xab', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.tokenMethodTransfer, @@ -172,9 +170,9 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xab', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const resultWithEmptyValue = await determineTransactionType( { @@ -182,7 +180,7 @@ describe('Transaction.utils', function () { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', }, - new EthQuery(_provider), + provider, ); expect(resultWithEmptyValue).toMatchObject({ type: TransactionType.tokenMethodTransfer, @@ -195,7 +193,7 @@ describe('Transaction.utils', function () { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', }, - new EthQuery(_provider), + provider, ); expect(resultWithEmptyValue2).toMatchObject({ @@ -209,7 +207,7 @@ describe('Transaction.utils', function () { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', }, - new EthQuery(_provider), + provider, ); expect(resultWithValue).toMatchObject({ type: TransactionType.contractInteraction, @@ -225,16 +223,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0x', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.simpleSend, @@ -249,16 +247,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xab', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.tokenMethodApprove, @@ -272,7 +270,7 @@ describe('Transaction.utils', function () { to: '', data: '0xabd', }, - query, + genericProvider, ); expect(result).toMatchObject({ type: TransactionType.deployContract, @@ -286,7 +284,7 @@ describe('Transaction.utils', function () { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xabd', }, - query, + genericProvider, ); expect(result).toMatchObject({ type: TransactionType.simpleSend, @@ -301,16 +299,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: null, }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '0xabd', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.simpleSend, @@ -325,16 +323,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xa', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: 'abd', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.contractInteraction, @@ -349,16 +347,16 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xa', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', data: '', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.contractInteraction, @@ -373,9 +371,9 @@ describe('Transaction.utils', function () { // by default, all accounts are external accounts (not contracts) eth_getCode: '0xa', }; - const _provider = createTestProviderTools({ + const { provider } = createTestProviderTools({ scaffold: _providerResultStub, - }).provider; + }); const result = await determineTransactionType( { @@ -383,7 +381,7 @@ describe('Transaction.utils', function () { value: '0x5af3107a4000', data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', }, - new EthQuery(_provider), + provider, ); expect(result).toMatchObject({ type: TransactionType.contractInteraction, diff --git a/shared/modules/transaction.utils.ts b/shared/modules/transaction.utils.ts index 910fe9b6d4be..728edbca87c2 100644 --- a/shared/modules/transaction.utils.ts +++ b/shared/modules/transaction.utils.ts @@ -6,13 +6,13 @@ import { abiERC1155, abiFiatTokenV2, } from '@metamask/metamask-eth-abis'; -import type EthQuery from '@metamask/eth-query'; import log from 'loglevel'; import { TransactionMeta, TransactionType, } from '@metamask/transaction-controller'; import type { TransactionParams } from '@metamask/transaction-controller'; +import type { Provider } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; import { AssetType, TokenStandard } from '../constants/transaction'; @@ -141,12 +141,12 @@ export function parseStandardTokenTransactionData(data: string) { * at transaction creation. * * @param txParams - Parameters for the transaction - * @param query - EthQuery instance + * @param provider - Provider instance * @returns InferTransactionTypeResult */ export async function determineTransactionType( txParams: TransactionParams, - query: EthQuery, + provider: Provider, ): Promise { const { data, to } = txParams; let contractCode: string | null | undefined; @@ -159,7 +159,7 @@ export async function determineTransactionType( } if (to) { const { contractCode: resultCode, isContractAddress } = - await readAddressAsContract(query, to); + await readAddressAsContract(provider, to); contractCode = resultCode; @@ -211,13 +211,13 @@ type GetTokenStandardAndDetails = (to: string | undefined) => Promise<{ * is a token transaction. * * @param txMeta - transaction meta object - * @param query - EthQuery instance + * @param provider - Provider instance * @param getTokenStandardAndDetails - function to get token standards and details. * @returns assetType: AssetType, tokenStandard: TokenStandard */ export async function determineTransactionAssetType( txMeta: TransactionMeta, - query: EthQuery, + provider: Provider, getTokenStandardAndDetails: GetTokenStandardAndDetails, ): Promise<{ assetType: AssetType; @@ -230,7 +230,7 @@ export async function determineTransactionAssetType( // Because we will deal with all types of transactions (including swaps) // we want to get an inferrable type of transaction that isn't special cased // that way we can narrow the number of logic gates required. - const result = await determineTransactionType(txMeta.txParams, query); + const result = await determineTransactionType(txMeta.txParams, provider); inferrableType = result.type; } @@ -294,10 +294,10 @@ function extractLargeMessageValue(dataToParse: string): string | undefined { } /** - * JSON.parse has a limitation which coerces values to scientific notation if numbers are greator than + * JSON.parse has a limitation which coerces values to scientific notation if numbers are greater than * Number.MAX_SAFE_INTEGER. This can cause a loss in precision. * - * Aside from precision concerns, if the value returned was a large number greator than 15 digits, + * Aside from precision concerns, if the value returned was a large number greater than 15 digits, * e.g. 3.000123123123121e+26, passing the value to BigNumber will throw the error: * Error: new BigNumber() number type has more than 15 significant digits * diff --git a/shared/types/bridge-status.ts b/shared/types/bridge-status.ts index 601a2209aaf9..bd892eac82dc 100644 --- a/shared/types/bridge-status.ts +++ b/shared/types/bridge-status.ts @@ -1,5 +1,5 @@ -// eslint-disable-next-line import/no-restricted-paths -import { ChainId, Quote, QuoteResponse } from '../../ui/pages/bridge/types'; +import { TransactionMeta } from '@metamask/transaction-controller'; +import type { ChainId, Quote, QuoteMetadata, QuoteResponse } from './bridge'; // All fields need to be types not interfaces, same with their children fields // o/w you get a type error @@ -13,7 +13,7 @@ export enum StatusTypes { export type StatusRequest = { bridgeId: string; // lifi, socket, squid - srcTxHash: string; // lifi, socket, squid + srcTxHash?: string; // lifi, socket, squid, might be undefined for STX bridge: string; // lifi, socket, squid srcChainId: ChainId; // lifi, socket, squid destChainId: ChainId; // lifi, socket, squid @@ -21,18 +21,32 @@ export type StatusRequest = { refuel?: boolean; // lifi }; +export type StatusRequestDto = Omit< + StatusRequest, + 'quote' | 'srcChainId' | 'destChainId' | 'refuel' +> & { + srcChainId: string; // lifi, socket, squid + destChainId: string; // lifi, socket, squid + requestId?: string; + refuel?: string; // lifi +}; + +export type StatusRequestWithSrcTxHash = StatusRequest & { + srcTxHash: string; +}; + export type Asset = { chainId: ChainId; address: string; symbol: string; name: string; decimals: number; - icon?: string; + icon?: string | null; }; export type SrcChainStatus = { chainId: ChainId; - txHash: string; + txHash?: string; // might be undefined if this is a smart transaction (STX) amount?: string; token?: Asset; }; @@ -105,20 +119,23 @@ export type RefuelStatusResponse = object & StatusResponse; export type RefuelData = object & Step; export type BridgeHistoryItem = { + txMetaId: string; // Need this to handle STX that might not have a txHash immediately quote: Quote; status: StatusResponse; - startTime?: number; + startTime?: number; // timestamp in ms estimatedProcessingTimeInSeconds: number; slippagePercentage: number; completionTime?: number; pricingData?: { - quotedGasInUsd: number; - quotedReturnInUsd: number; - amountSentInUsd: number; - quotedRefuelSrcAmountInUsd?: number; - quotedRefuelDestAmountInUsd?: number; + amountSent: string; // This is from QuoteMetadata.sentAmount.amount, the actual amount sent by user in non-atomic decimal form + + quotedGasInUsd?: string; + quotedReturnInUsd?: string; + amountSentInUsd?: string; + quotedRefuelSrcAmountInUsd?: string; + quotedRefuelDestAmountInUsd?: string; }; - initialDestAssetBalance?: number; + initialDestAssetBalance?: string; targetContractAddress?: string; account: string; }; @@ -129,18 +146,34 @@ export enum BridgeStatusAction { GET_STATE = 'getState', } +export type QuoteMetadataSerialized = { + sentAmount: { amount: string; valueInCurrency: string | null }; +}; + export type StartPollingForBridgeTxStatusArgs = { + bridgeTxMeta: TransactionMeta; statusRequest: StatusRequest; - quoteResponse: QuoteResponse; + quoteResponse: QuoteResponse & QuoteMetadata; startTime?: BridgeHistoryItem['startTime']; slippagePercentage: BridgeHistoryItem['slippagePercentage']; - pricingData?: BridgeHistoryItem['pricingData']; initialDestAssetBalance?: BridgeHistoryItem['initialDestAssetBalance']; targetContractAddress?: BridgeHistoryItem['targetContractAddress']; }; -export type SourceChainTxHash = string; +/** + * Chrome: The BigNumber values are automatically serialized to strings when sent to the background + * Firefox: The BigNumber values are not serialized to strings when sent to the background, + * so we force the ui to do it manually, by using StartPollingForBridgeTxStatusArgsSerialized type on the startPollingForBridgeTxStatus action + */ +export type StartPollingForBridgeTxStatusArgsSerialized = Omit< + StartPollingForBridgeTxStatusArgs, + 'quoteResponse' +> & { + quoteResponse: QuoteResponse & QuoteMetadataSerialized; +}; + +export type SourceChainTxMetaId = string; export type BridgeStatusControllerState = { - txHistory: Record; + txHistory: Record; }; diff --git a/shared/types/bridge.ts b/shared/types/bridge.ts new file mode 100644 index 000000000000..b688164e9879 --- /dev/null +++ b/shared/types/bridge.ts @@ -0,0 +1,198 @@ +import type { Hex } from '@metamask/utils'; +import type { BigNumber } from 'bignumber.js'; +import type { AssetType } from '../constants/transaction'; +import type { SwapsTokenObject } from '../constants/swaps'; + +export type ChainConfiguration = { + isActiveSrc: boolean; + isActiveDest: boolean; +}; + +export type L1GasFees = { + l1GasFeesInHexWei?: string; // l1 fees for approval and trade in hex wei, appended by controller +}; +// Values derived from the quote response +// valueInCurrency values are calculated based on the user's selected currency + +export type QuoteMetadata = { + gasFee: { amount: BigNumber; valueInCurrency: BigNumber | null }; + totalNetworkFee: { amount: BigNumber; valueInCurrency: BigNumber | null }; // estimatedGasFees + relayerFees + totalMaxNetworkFee: { amount: BigNumber; valueInCurrency: BigNumber | null }; // maxGasFees + relayerFees + toTokenAmount: { amount: BigNumber; valueInCurrency: BigNumber | null }; + adjustedReturn: { valueInCurrency: BigNumber | null }; // destTokenAmount - totalNetworkFee + sentAmount: { amount: BigNumber; valueInCurrency: BigNumber | null }; // srcTokenAmount + metabridgeFee + swapRate: BigNumber; // destTokenAmount / sentAmount + cost: { valueInCurrency: BigNumber | null }; // sentAmount - adjustedReturn +}; +// Sort order set by the user + +export enum SortOrder { + COST_ASC = 'cost_ascending', + ETA_ASC = 'time_descending', +} + +export type BridgeToken = { + type: AssetType.native | AssetType.token; + address: string; + symbol: string; + image: string; + decimals: number; + chainId: Hex; + balance: string; // raw balance + string: string | undefined; // normalized balance as a stringified number + tokenFiatAmount?: number | null; +} | null; +// Types copied from Metabridge API + +export enum BridgeFlag { + EXTENSION_CONFIG = 'extension-config', +} +type DecimalChainId = string; +export type GasMultiplierByChainId = Record; + +export type FeatureFlagResponse = { + [BridgeFlag.EXTENSION_CONFIG]: { + refreshRate: number; + maxRefreshCount: number; + support: boolean; + chains: Record; + }; +}; + +export type BridgeAsset = { + chainId: ChainId; + address: string; + symbol: string; + name: string; + decimals: number; + icon?: string; +}; + +export type QuoteRequest = { + walletAddress: string; + destWalletAddress?: string; + srcChainId: ChainId; + destChainId: ChainId; + srcTokenAddress: string; + destTokenAddress: string; + srcTokenAmount: string; // This is the amount sent + slippage: number; + aggIds?: string[]; + bridgeIds?: string[]; + insufficientBal?: boolean; + resetApproval?: boolean; + refuel?: boolean; +}; +type Protocol = { + name: string; + displayName?: string; + icon?: string; +}; +enum ActionTypes { + BRIDGE = 'bridge', + SWAP = 'swap', + REFUEL = 'refuel', +} +type Step = { + action: ActionTypes; + srcChainId: ChainId; + destChainId?: ChainId; + srcAsset: BridgeAsset; + destAsset: BridgeAsset; + srcAmount: string; + destAmount: string; + protocol: Protocol; +}; +type RefuelData = Step; + +export type Quote = { + requestId: string; + srcChainId: ChainId; + srcAsset: BridgeAsset; + // Some tokens have a fee of 0, so sometimes it's equal to amount sent + srcTokenAmount: string; // Atomic amount, the amount sent - fees + destChainId: ChainId; + destAsset: BridgeAsset; + destTokenAmount: string; // Atomic amount, the amount received + feeData: Record & + Partial>; + bridgeId: string; + bridges: string[]; + steps: Step[]; + refuel?: RefuelData; +}; + +export type QuoteResponse = { + quote: Quote; + approval: TxData | null; + trade: TxData; + estimatedProcessingTimeInSeconds: number; +}; + +export enum ChainId { + ETH = 1, + OPTIMISM = 10, + BSC = 56, + POLYGON = 137, + ZKSYNC = 324, + BASE = 8453, + ARBITRUM = 42161, + AVALANCHE = 43114, + LINEA = 59144, +} + +export enum FeeType { + METABRIDGE = 'metabridge', + REFUEL = 'refuel', +} +export type FeeData = { + amount: string; + asset: BridgeAsset; +}; +export type TxData = { + chainId: ChainId; + to: string; + from: string; + value: string; + data: string; + gasLimit: number | null; +}; +export enum BridgeFeatureFlagsKey { + EXTENSION_CONFIG = 'extensionConfig', +} + +export type BridgeFeatureFlags = { + [BridgeFeatureFlagsKey.EXTENSION_CONFIG]: { + refreshRate: number; + maxRefreshCount: number; + support: boolean; + chains: Record; + }; +}; +export enum RequestStatus { + LOADING, + FETCHED, + ERROR, +} +export enum BridgeUserAction { + SELECT_DEST_NETWORK = 'selectDestNetwork', + UPDATE_QUOTE_PARAMS = 'updateBridgeQuoteRequestParams', +} +export enum BridgeBackgroundAction { + SET_FEATURE_FLAGS = 'setBridgeFeatureFlags', + RESET_STATE = 'resetState', + GET_BRIDGE_ERC20_ALLOWANCE = 'getBridgeERC20Allowance', +} +export type BridgeControllerState = { + bridgeFeatureFlags: BridgeFeatureFlags; + destTokensLoadingStatus?: RequestStatus; + destTokens: Record; + destTopAssets: { address: string }[]; + quoteRequest: Partial; + quotes: (QuoteResponse & L1GasFees)[]; + quotesInitialLoadTime?: number; + quotesLastFetched?: number; + quotesLoadingStatus?: RequestStatus; + quoteFetchError?: string; + quotesRefreshCount: number; +}; diff --git a/test/data/bridge/dummy-quotes.ts b/test/data/bridge/dummy-quotes.ts index 3328960a9b73..8b3e907d7df2 100644 --- a/test/data/bridge/dummy-quotes.ts +++ b/test/data/bridge/dummy-quotes.ts @@ -1,6 +1,10 @@ export const DummyQuotesNoApproval = { OP_0_005_ETH_TO_ARB: [ { + sentAmount: { + amount: '0.005', + valueInCurrency: null, + }, quote: { requestId: 'be448070-7849-4d14-bb35-8dcdaf7a4d69', srcChainId: 10, @@ -392,6 +396,10 @@ export const DummyQuotesNoApproval = { export const DummyQuotesWithApproval = { ETH_11_USDC_TO_ARB: [ { + sentAmount: { + amount: '1.0903750', + valueInCurrency: null, + }, quote: { requestId: '0cd5caf6-9844-465b-89ad-9c89b639f432', srcChainId: 1, @@ -812,6 +820,10 @@ export const DummyQuotesWithApproval = { ], ARB_11_USDC_TO_ETH: [ { + sentAmount: { + amount: '1.0903750', + valueInCurrency: null, + }, quote: { requestId: 'edbef62a-d3e6-4b33-aad5-9cdb81f85f53', srcChainId: 42161, @@ -911,6 +923,10 @@ export const DummyQuotesWithApproval = { ], ARB_11_USDC_TO_OP: [ { + sentAmount: { + amount: '1.0903750', + valueInCurrency: null, + }, quote: { requestId: 'dc63e7e6-dc9b-4aa8-80bb-714192ecd801', srcChainId: 42161, @@ -2461,6 +2477,10 @@ export const DummyQuotesWithApproval = { ], OP_11_USDC_TO_ARB: [ { + sentAmount: { + amount: '1.1000000', + valueInCurrency: null, + }, quote: { requestId: '01fa78fd-ed49-42b3-ab0e-94c7108feea9', srcChainId: 10, diff --git a/test/data/bridge/mock-token-data.ts b/test/data/bridge/mock-token-data.ts new file mode 100644 index 000000000000..0eafa4802ea5 --- /dev/null +++ b/test/data/bridge/mock-token-data.ts @@ -0,0 +1,102 @@ +import { CHAIN_IDS } from '@metamask/transaction-controller'; + +export const mockTokenData = { + allTokens: { + [CHAIN_IDS.MAINNET]: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': [ + { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + balance: 'a', + decimals: 6, + }, + { + address: '0x514910771af9ca656af840dff83e8264ecf986ca', + balance: 'e', + }, + ], + '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': [ + { + address: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2', + balance: 'e', + }, + ], + }, + [CHAIN_IDS.LINEA_MAINNET]: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': [ + { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + balance: 'e', + }, + ], + '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': [ + { + address: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2', + balance: 'e', + }, + ], + }, + }, + accountsByChainId: { + [CHAIN_IDS.MAINNET]: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + balance: '0xa', + }, + '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': { + address: '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b', + balance: '0xe', + }, + }, + [CHAIN_IDS.LINEA_MAINNET]: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + balance: '0xe', + }, + '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b': { + address: '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b', + balance: '0xe', + }, + }, + }, + tokensChainsCache: { + [CHAIN_IDS.MAINNET]: { + timestamp: 111111, + data: [ + { + address: '0x514910771af9ca656af840dff83e8264ecf986ca', + symbol: 'LINK', + decimals: 18, + }, + { + address: '0xc00e94cb662c3520282e6f5717214004a7f26888', + symbol: 'COMP', + decimals: 18, + }, + ], + }, + [CHAIN_IDS.LINEA_MAINNET]: { + timestamp: 111111, + data: { + '0x514910771af9ca656af840dff83e8264ecf986ca': { + address: '0x514910771af9ca656af840dff83e8264ecf986ca', + symbol: 'LINK', + decimals: 18, + }, + '0xc00e94cb662c3520282e6f5717214004a7f26888': { + address: '0xc00e94cb662c3520282e6f5717214004a7f26888', + symbol: 'COMP', + decimals: 18, + }, + }, + }, + }, + tokenBalances: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + '0x5': {}, + '0x1': { + '0x514910771af9ca656af840dff83e8264ecf986ca': '0x1', + '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': '0x738', + }, + }, + }, +}; diff --git a/test/data/confirmations/contract-interaction.ts b/test/data/confirmations/contract-interaction.ts index cbe5dd2bd2ba..b6263ca49da2 100644 --- a/test/data/confirmations/contract-interaction.ts +++ b/test/data/confirmations/contract-interaction.ts @@ -161,6 +161,7 @@ export const genUnapprovedContractInteractionConfirmation = ({ to: '0x88aa6343307ec9a652ccddda3646e62b2f1a5125', value: '0x3782dace9d900000', }, + gasLimitNoBuffer: '0xab77', type: TransactionType.contractInteraction, userEditedGasLimit: false, userFeeLevel: 'medium', diff --git a/test/data/confirmations/helper.ts b/test/data/confirmations/helper.ts index b8bd8a634588..4b090628eee7 100644 --- a/test/data/confirmations/helper.ts +++ b/test/data/confirmations/helper.ts @@ -29,8 +29,6 @@ export const getMockTypedSignConfirmState = ( ...args.metamask, preferences: { ...mockState.metamask.preferences, - redesignedTransactionsEnabled: true, - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, pendingApprovals: { @@ -56,8 +54,6 @@ export const getMockTypedSignConfirmStateForRequest = ( ...args.metamask, preferences: { ...mockState.metamask.preferences, - redesignedTransactionsEnabled: true, - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, pendingApprovals: { @@ -82,8 +78,6 @@ export const getMockPersonalSignConfirmState = ( ...args.metamask, preferences: { ...mockState.metamask.preferences, - redesignedTransactionsEnabled: true, - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, pendingApprovals: { @@ -109,8 +103,6 @@ export const getMockPersonalSignConfirmStateForRequest = ( ...args.metamask, preferences: { ...mockState.metamask.preferences, - redesignedTransactionsEnabled: true, - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, pendingApprovals: { @@ -134,8 +126,6 @@ export const getMockConfirmState = (args: RootState = { metamask: {} }) => ({ preferences: { ...mockState.metamask.preferences, ...(args.metamask?.preferences as Record), - redesignedTransactionsEnabled: true, - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }, diff --git a/test/data/confirmations/set-approval-for-all.ts b/test/data/confirmations/set-approval-for-all.ts index ca997f6212af..ff39988fe238 100644 --- a/test/data/confirmations/set-approval-for-all.ts +++ b/test/data/confirmations/set-approval-for-all.ts @@ -6,6 +6,9 @@ import { genUnapprovedContractInteractionConfirmation, } from './contract-interaction'; +export const INCREASE_ALLOWANCE_TRANSACTION_DATA = + '0x395093510000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000123'; + export const genUnapprovedSetApprovalForAllConfirmation = ({ address = CONTRACT_INTERACTION_SENDER_ADDRESS, chainId = CHAIN_ID, @@ -16,12 +19,13 @@ export const genUnapprovedSetApprovalForAllConfirmation = ({ ...genUnapprovedContractInteractionConfirmation({ chainId }), txParams: { from: address, - data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', + data: '0xa22cb4650000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', gas: '0x16a92', to: '0x076146c765189d51be3160a2140cf80bfc73ad68', value: '0x0', maxFeePerGas: '0x5b06b0c0d', maxPriorityFeePerGas: '0x59682f00', }, + gasLimitNoBuffer: '0x16a92', type: TransactionType.tokenMethodSetApprovalForAll, }); diff --git a/test/data/confirmations/token-approve.ts b/test/data/confirmations/token-approve.ts index c77d59101a99..11fd52a7f945 100644 --- a/test/data/confirmations/token-approve.ts +++ b/test/data/confirmations/token-approve.ts @@ -9,19 +9,22 @@ import { export const genUnapprovedApproveConfirmation = ({ address = CONTRACT_INTERACTION_SENDER_ADDRESS, chainId = CHAIN_ID, + amountHex = '0000000000000000000000000000000000000000000000000000000000000001', }: { address?: Hex; chainId?: string; + amountHex?: string; } = {}) => ({ ...genUnapprovedContractInteractionConfirmation({ chainId }), txParams: { from: address, - data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', + data: `0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b${amountHex}`, gas: '0x16a92', to: '0x076146c765189d51be3160a2140cf80bfc73ad68', value: '0x0', maxFeePerGas: '0x5b06b0c0d', maxPriorityFeePerGas: '0x59682f00', }, + gasLimitNoBuffer: '0x16a92', type: TransactionType.tokenMethodApprove, }); diff --git a/test/data/confirmations/token-transfer.ts b/test/data/confirmations/token-transfer.ts index 22d0cb2d00b4..b6ba2e23b8a5 100644 --- a/test/data/confirmations/token-transfer.ts +++ b/test/data/confirmations/token-transfer.ts @@ -6,25 +6,31 @@ import { genUnapprovedContractInteractionConfirmation, } from './contract-interaction'; +export const TRANSFER_FROM_TRANSACTION_DATA = + '0x23b872dd0000000000000000000000002e0D7E8c45221FcA00d74a3609A0f7097035d09B0000000000000000000000002e0D7E8c45221FcA00d74a3609A0f7097035d09C0000000000000000000000000000000000000000000000000000000000000123'; + export const genUnapprovedTokenTransferConfirmation = ({ address = CONTRACT_INTERACTION_SENDER_ADDRESS, chainId = CHAIN_ID, isWalletInitiatedConfirmation = false, + amountHex = '0000000000000000000000000000000000000000000000000000000000000001', }: { address?: Hex; chainId?: string; isWalletInitiatedConfirmation?: boolean; + amountHex?: string; } = {}) => ({ ...genUnapprovedContractInteractionConfirmation({ chainId }), txParams: { from: address, - data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', + data: `0xa9059cbb0000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b${amountHex}`, gas: '0x16a92', to: '0x076146c765189d51be3160a2140cf80bfc73ad68', value: '0x0', maxFeePerGas: '0x5b06b0c0d', maxPriorityFeePerGas: '0x59682f00', }, + gasLimitNoBuffer: '0x16a92', type: TransactionType.tokenMethodTransfer, origin: isWalletInitiatedConfirmation ? 'metamask' diff --git a/test/data/confirmations/typed_sign.ts b/test/data/confirmations/typed_sign.ts index 831d561f0cb2..b0984684f12d 100644 --- a/test/data/confirmations/typed_sign.ts +++ b/test/data/confirmations/typed_sign.ts @@ -188,6 +188,59 @@ export const permitSignatureMsg = { }, } as SignatureRequestType; +export const seaportSignatureMsg = { + chainId: '0x1', + id: 'e9297d91-aca0-11ef-9ac4-417a173450d3', + messageParams: { + data: '{"types":{"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Seaport","version":"1.1","chainId":"0x1","verifyingContract":"0x00000000006c3852cbef3e08e8df289169ede581"},"primaryType":"OrderComponents","message":{"offerer":"0x935E73EDb9fF52E23BaC7F7e043A1ecD06d05477","zone":"0x004c00500000ad104d7dbd00e3ae0a5c00560c00","offer":[{"itemType":"2","token":"0x922dc160f2ab743312a6bb19dd5152c1d3ecca33","identifierOrCriteria":"176","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"0","endAmount":"0","recipient":"0x935E73EDb9fF52E23BaC7F7e043A1ecD06d05477"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0x8de9c5a032463c561423387a9648c5c7bcc5bc90"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"50000000000000000","endAmount":"50000000000000000","recipient":"0x5c6139cd9ff1170197f13935c58f825b422c744c"}],"orderType":"3","startTime":"1660565524","endTime":"1661170320","zoneHash":"0x3000000000000000000000000000000000000000000000000000000000000000","salt":"5965482869793190759363249887602871532","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","counter":"0"}}', + from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + version: 'V4', + signatureMethod: 'eth_signTypedData_v4', + metamaskId: 'e9297d90-aca0-11ef-9ac4-417a173450d3', + origin: 'https://develop.d3bkcslj57l47p.amplifyapp.com', + requestId: 1376479613, + }, + networkClientId: 'mainnet', + securityAlertResponse: { + result_type: 'loading', + reason: 'CheckingChain', + securityAlertId: 'def3b0ef-c96b-4c87-b1b1-c69cc02a0f78', + }, + status: 'unapproved', + time: 1732699257833, + type: 'eth_signTypedData', + version: 'V4', + decodingLoading: false, + decodingData: { + stateChanges: [ + { + assetType: 'NATIVE', + changeType: 'RECEIVE', + address: '', + amount: '0', + contractAddress: '', + }, + { + assetType: 'ERC721', + changeType: 'LISTING', + address: '', + amount: '', + contractAddress: '0x922dc160f2ab743312a6bb19dd5152c1d3ecca33', + tokenID: '176', + }, + ], + }, + msgParams: { + data: '{"types":{"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Seaport","version":"1.4","chainId":"0x1","verifyingContract":"0x00000000006c3852cbef3e08e8df289169ede581"},"primaryType":"OrderComponents","message":{"offerer":"0x935E73EDb9fF52E23BaC7F7e043A1ecD06d05477","zone":"0x004c00500000ad104d7dbd00e3ae0a5c00560c00","offer":[{"itemType":"2","token":"0x922dc160f2ab743312a6bb19dd5152c1d3ecca33","identifierOrCriteria":"176","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"0","endAmount":"0","recipient":"0x935E73EDb9fF52E23BaC7F7e043A1ecD06d05477"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0x8de9c5a032463c561423387a9648c5c7bcc5bc90"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"50000000000000000","endAmount":"50000000000000000","recipient":"0x5c6139cd9ff1170197f13935c58f825b422c744c"}],"orderType":"3","startTime":"1660565524","endTime":"1661170320","zoneHash":"0x3000000000000000000000000000000000000000000000000000000000000000","salt":"5965482869793190759363249887602871532","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","counter":"0"}}', + from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + version: 'V4', + signatureMethod: 'eth_signTypedData_v4', + metamaskId: 'e9297d90-aca0-11ef-9ac4-417a173450d3', + origin: 'https://develop.d3bkcslj57l47p.amplifyapp.com', + requestId: 1376479613, + }, +} as SignatureRequestType; + export const permitNFTSignatureMsg = { id: 'c5067710-87cf-11ef-916c-71f266571322', chainId: CHAIN_IDS.GOERLI, diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts index b956dbadd6bb..c65a6b7bd162 100644 --- a/test/data/mock-accounts.ts +++ b/test/data/mock-accounts.ts @@ -1,9 +1,11 @@ import { KeyringTypes } from '@metamask/keyring-controller'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { - InternalAccount, EthAccountType, BtcMethod, BtcAccountType, + EthScopes, + BtcScopes, } from '@metamask/keyring-api'; import { ETH_EOA_METHODS, @@ -15,6 +17,7 @@ export const MOCK_ACCOUNT_EOA: InternalAccount = { address: '0x123', options: {}, methods: ETH_EOA_METHODS, + scopes: [EthScopes.Namespace], type: EthAccountType.Eoa, metadata: { name: 'Account 1', @@ -29,6 +32,8 @@ export const MOCK_ACCOUNT_ERC4337: InternalAccount = { address: '0x123', options: {}, methods: ETH_EOA_METHODS.concat(ETH_4337_METHODS), + // Smart accounts might not be available on every EVM chains, but that's ok for mock purposes. + scopes: [EthScopes.Namespace], type: EthAccountType.Erc4337, metadata: { name: 'Account 2', @@ -43,6 +48,7 @@ export const MOCK_ACCOUNT_BIP122_P2WPKH: InternalAccount = { address: 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k', options: {}, methods: [BtcMethod.SendBitcoin], + scopes: [BtcScopes.Mainnet], type: BtcAccountType.P2wpkh, metadata: { name: 'Bitcoin Account', @@ -57,6 +63,7 @@ export const MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET: InternalAccount = { address: 'tb1q6rmsq3vlfdhjdhtkxlqtuhhlr6pmj09y6w43g8', options: {}, methods: [BtcMethod.SendBitcoin], + scopes: [BtcScopes.Testnet], type: BtcAccountType.P2wpkh, metadata: { name: 'Bitcoin Testnet Account', diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index 73468aca6171..124487281ca9 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -13,7 +13,6 @@ "networkDropdownOpen": false, "importNftsModal": { "open": false }, "showPermittedNetworkToastOpen": false, - "gasIsLoading": false, "isLoading": false, "importTokensModalOpen": false, "modal": { @@ -64,6 +63,11 @@ }, "metamask": { "accountsByChainId": {}, + "tokenBalances": { + "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { + "0x5": {} + } + }, "ipfsGateway": "", "dismissSeedBackUpReminder": false, "usePhishDetect": true, @@ -128,7 +132,7 @@ } } }, - "snaps": [{}], + "snaps": {}, "preferences": { "hideZeroBalanceTokens": false, "showFiatInTestnets": false, @@ -140,11 +144,6 @@ "isAccountMenuOpen": false, "isUnlocked": true, "completedOnboarding": true, - "usedNetworks": { - "0x1": true, - "0x5": true, - "0x539": true - }, "showTestnetMessageInDropdown": true, "alertEnabledness": { "unconnectedAccount": true diff --git a/test/data/mock-state.json b/test/data/mock-state.json index b315dfa203eb..9705fb279694 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -10,12 +10,17 @@ "url": "https://metamask.github.io/test-dapp/" }, "appState": { + "isAccountMenuOpen": false, + "confirmationExchangeRates": {}, + "nextNonce": 71, + "welcomeScreenSeen": false, + "pendingTokens": {}, + "customTokenAmount": "10", "networkDropdownOpen": false, "importNftsModal": { "open": false }, "showPermittedNetworkToastOpen": false, - "gasIsLoading": false, "isLoading": false, "modal": { "open": false, @@ -27,8 +32,7 @@ "name": null } }, - "warning": null, - "customTokenAmount": "10" + "warning": null }, "confirmAlerts": { "alerts": [], @@ -370,6 +374,39 @@ "version": "5.1.2" } ] + }, + "local:snap-id": { + "id": "local:snap-id", + "origin": "local:snap-id", + "version": "5.1.2", + "iconUrl": null, + "initialPermissions": {}, + "manifest": { + "description": "mock snap description", + "proposedName": "mock snap name", + "repository": { + "type": "git", + "url": "https://127.0.0.1" + }, + "source": { + "location": { + "npm": { + "filePath": "dist/bundle.js", + "packageName": "@metamask/test-snap-dialog", + "registry": "https://registry.npmjs.org" + } + }, + "shasum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + }, + "version": "5.1.2" + }, + "versionHistory": [ + { + "date": 1680686075921, + "origin": "https://metamask.github.io", + "version": "5.1.2" + } + ] } }, "preferences": { @@ -386,11 +423,9 @@ "key": "tokenFiatAmount", "order": "dsc", "sortCallback": "stringNumeric" - }, - "tokenNetworkFilter": {} + } }, "ensResolutionsByAddress": {}, - "isAccountMenuOpen": false, "isUnlocked": true, "alertEnabledness": { "unconnectedAccount": true @@ -448,6 +483,7 @@ "eth_signTypedData_v3", "eth_signTypedData_v4" ], + "scopes": ["eip155"], "type": "eip155:eoa" }, "07c2cfec-36c9-46c4-8115-3836d3ac9047": { @@ -468,6 +504,7 @@ "eth_signTypedData_v3", "eth_signTypedData_v4" ], + "scopes": ["eip155"], "type": "eip155:eoa" }, "15e69915-2a1a-4019-93b3-916e11fd432f": { @@ -488,6 +525,7 @@ "eth_signTypedData_v3", "eth_signTypedData_v4" ], + "scopes": ["eip155"], "type": "eip155:eoa" }, "784225f4-d30b-4e77-a900-c8bbce735b88": { @@ -508,6 +546,7 @@ "eth_signTypedData_v3", "eth_signTypedData_v4" ], + "scopes": ["eip155"], "type": "eip155:eoa" }, "694225f4-d30b-4e77-a900-c8bbce735b42": { @@ -528,6 +567,7 @@ "eth_signTypedData_v3", "eth_signTypedData_v4" ], + "scopes": ["eip155"], "type": "eip155:eoa" }, "c3deeb99-ba0d-4a4e-a0aa-033fc1f79ae3": { @@ -541,7 +581,7 @@ }, "snap": { "enabled": true, - "id": "snap-id", + "id": "local:snap-id", "name": "snap-name" } }, @@ -553,6 +593,7 @@ "eth_signTypedData_v3", "eth_signTypedData_v4" ], + "scopes": ["eip155"], "type": "eip155:eoa" } }, @@ -646,22 +687,6 @@ "lastUpdated": "September 26, 2024" } }, - "notifications": { - "test": { - "id": "test", - "origin": "local:http://localhost:8086/", - "createdDate": 1652967897732, - "readDate": null, - "message": "Hello, http://localhost:8086!" - }, - "test2": { - "id": "test2", - "origin": "local:http://localhost:8086/", - "createdDate": 1652967897732, - "readDate": 1652967897732, - "message": "Hello, http://localhost:8086!" - } - }, "accountsByChainId": { "0x5": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { @@ -1131,6 +1156,7 @@ "gas": "0x5208", "gasPrice": "0x3b9aca00" }, + "gasLimitNoBuffer": "0x5208", "history": [ { "id": 8393540981007587, @@ -1180,6 +1206,7 @@ "gasPrice": "0x3b9aca00", "nonce": "0xb5" }, + "gasLimitNoBuffer": "0xcf08", "history": [ { "id": 3387511061307736, @@ -1312,6 +1339,7 @@ "gasPrice": "0x3b9aca00", "nonce": "0xb6" }, + "gasLimitNoBuffer": "0xcf08", "history": [ { "id": 3387511061307737, @@ -1444,6 +1472,7 @@ "gasPrice": "0x12a05f200", "nonce": "0xb7" }, + "gasLimitNoBuffer": "0xcf08", "history": [ { "id": 3387511061307738, @@ -1577,6 +1606,7 @@ "gas": "0x6169e", "nonce": "0xb8" }, + "gasLimitNoBuffer": "0x6169e", "history": [ { "id": 3387511061307739, @@ -1714,6 +1744,7 @@ "gasPrice": "0x3b9aca00", "nonce": "0xb9" }, + "gasLimitNoBuffer": "0xd508", "history": [ { "id": 3387511061307740, @@ -1846,6 +1877,7 @@ "gasPrice": "0x3b9aca00", "gas": "0x5208" }, + "gasLimitNoBuffer": "0x5208", "history": [ { "id": 3387511061307741, @@ -1907,6 +1939,7 @@ "watchEthereumAccountEnabled": false, "bitcoinSupportEnabled": false, "bitcoinTestnetSupportEnabled": false, + "solanaSupportEnabled": false, "pendingApprovals": { "testApprovalId": { "id": "testApprovalId", diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts index 7123d3e4114a..23c52a91c653 100644 --- a/test/e2e/constants.ts +++ b/test/e2e/constants.ts @@ -20,7 +20,7 @@ export const BUNDLER_URL = 'http://localhost:3000/rpc'; /* URL of the 4337 account snap site. */ export const ERC_4337_ACCOUNT_SNAP_URL = - 'https://metamask.github.io/snap-account-abstraction-keyring/0.4.2/'; + 'https://metamask.github.io/snap-account-abstraction-keyring/0.5.0/'; /* Salt used to generate the 4337 account. */ export const ERC_4337_ACCOUNT_SALT = '0x1'; @@ -48,6 +48,9 @@ export const DAPP_ONE_URL = 'http://127.0.0.1:8081'; /* Default BTC address created using test SRP */ export const DEFAULT_BTC_ACCOUNT = 'bc1qg6whd6pc0cguh6gpp3ewujm53hv32ta9hdp252'; +/* Default (mocked) block number. */ +export const DEFAULT_BTC_BLOCK_NUMBER = 101100110; + /* Default (mocked) BTC balance used by the Bitcoin RPC provider */ export const DEFAULT_BTC_BALANCE = 1; // BTC @@ -57,6 +60,9 @@ export const DEFAULT_BTC_FEES_RATE = 0.00001; // BTC /* Default BTC conversion rate to USD */ export const DEFAULT_BTC_CONVERSION_RATE = 62000; // USD +/* Default SOL conversion rate to USD */ +export const DEFAULT_SOL_CONVERSION_RATE = 226; // USD + /* Default BTC transaction ID */ export const DEFAULT_BTC_TRANSACTION_ID = 'e4111a707317da67d49a71af4cbcf6c0546f900ca32c3842d2254e315d1fca18'; @@ -70,3 +76,17 @@ export const DEFAULT_SOLANA_ACCOUNT = /* Default (mocked) SOLANA balance used by the Solana RPC provider */ export const DEFAULT_SOLANA_BALANCE = 1; // SOL + +/* Title of Portfolio page */ +export const PORTFOLIO_PAGE_TITLE = 'MetaMask Portfolio'; + +/* Account types */ +export enum ACCOUNT_TYPE { + Ethereum, + Bitcoin, + Solana, +} + +/* Meta metricsId generated by generateMetaMetricsId */ +export const MOCK_META_METRICS_ID = + '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420'; diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index c2fba9d63424..674b1146688b 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -115,25 +115,31 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { trezorModel: null, newPrivacyPolicyToastClickedOrClosed: true, newPrivacyPolicyToastShownDate: Date.now(), - usedNetworks: { - [CHAIN_IDS.MAINNET]: true, - [CHAIN_IDS.LINEA_MAINNET]: true, - [CHAIN_IDS.GOERLI]: true, - [CHAIN_IDS.LOCALHOST]: true, - }, snapsInstallPrivacyWarningShown: true, }, BridgeController: { bridgeState: { bridgeFeatureFlags: { - extensionSupport: false, - srcNetworkAllowlist: ['0x1', '0xa', '0xe708'], - destNetworkAllowlist: ['0x1', '0xa', '0xe708'], + extensionConfig: { + support: false, + chains: { + '0x1': { + isActiveSrc: true, + isActiveDest: true, + }, + '0xa': { + isActiveSrc: true, + isActiveDest: true, + }, + '0xe708': { + isActiveSrc: true, + isActiveDest: true, + }, + }, + }, }, destTokens: {}, destTopAssets: [], - srcTokens: {}, - srcTopAssets: [], }, }, CurrencyController: { @@ -236,7 +242,6 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { useTokenDetection: false, useCurrencyRateCheck: true, useMultiAccountBalanceChecker: true, - useRequestQueue: true, isMultiAccountBalancesEnabled: true, showIncomingTransactions: { [ETHERSCAN_SUPPORTED_CHAIN_IDS.MAINNET]: true, diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 844c4766db3e..9e83c8f2db0b 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -16,7 +16,6 @@ const { DAPP_URL_LOCALHOST, DAPP_ONE_URL, DEFAULT_FIXTURE_ACCOUNT, - ERC_4337_ACCOUNT, } = require('./constants'); const { defaultFixture, @@ -40,12 +39,6 @@ function onboardingFixture() { '__FIXTURE_SUBSTITUTION__currentDateInMilliseconds', showTestnetMessageInDropdown: true, trezorModel: null, - usedNetworks: { - [CHAIN_IDS.MAINNET]: true, - [CHAIN_IDS.LINEA_MAINNET]: true, - [CHAIN_IDS.GOERLI]: true, - [CHAIN_IDS.LOCALHOST]: true, - }, }, NetworkController: { ...mockNetworkStateOld({ @@ -101,7 +94,6 @@ function onboardingFixture() { useTokenDetection: false, useCurrencyRateCheck: true, useMultiAccountBalanceChecker: true, - useRequestQueue: true, isMultiAccountBalancesEnabled: true, showIncomingTransactions: { [ETHERSCAN_SUPPORTED_CHAIN_IDS.MAINNET]: true, @@ -241,6 +233,12 @@ class FixtureBuilder { }); } + withUseBasicFunctionalityDisabled() { + return this.withPreferencesController({ + useExternalServices: false, + }); + } + withGasFeeController(data) { merge(this.fixture.data.GasFeeController, data); return this; @@ -305,6 +303,22 @@ class FixtureBuilder { }); } + withNetworkControllerOnPolygon() { + return this.withNetworkController({ + networkConfigurations: { + networkConfigurationId: { + chainId: CHAIN_IDS.POLYGON, + nickname: 'Polygon Mainnet', + rpcPrefs: {}, + rpcUrl: 'https://mainnet.infura.io', + ticker: 'ETH', + networkConfigurationId: 'networkConfigurationId', + id: 'networkConfigurationId', + }, + }, + }); + } + withNetworkControllerDoubleGanache() { const ganacheNetworks = mockNetworkStateOld({ id: '76e9cd59-d8e2-47e7-b369-9c205ccb602c', @@ -436,21 +450,19 @@ class FixtureBuilder { this.fixture.data.BridgeController = { bridgeState: { bridgeFeatureFlags: { - destNetworkAllowlist: [], - extensionSupport: false, - srcNetworkAllowlist: [], + extensionConfig: { + support: false, + chains: {}, + }, }, destTokens: {}, destTopAssets: [], - srcTokens: {}, - srcTopAssets: [], }, }; return this; } withPermissionControllerConnectedToTestDapp({ - restrictReturnedAccounts = true, account = '', useLocalhostHostname = false, } = {}) { @@ -464,14 +476,10 @@ class FixtureBuilder { id: 'ZaqPEWxyhNCJYACFw93jE', parentCapability: 'eth_accounts', invoker: DAPP_URL, - caveats: restrictReturnedAccounts && [ + caveats: [ { type: 'restrictReturnedAccounts', - value: [ - selectedAccount.toLowerCase(), - '0x09781764c08de8ca82e156bbf156a3ca217c7950', - ERC_4337_ACCOUNT.toLowerCase(), - ], + value: [selectedAccount.toLowerCase()], }, ], date: 1664388714636, @@ -482,9 +490,71 @@ class FixtureBuilder { }); } - withPermissionControllerSnapAccountConnectedToTestDapp( - restrictReturnedAccounts = true, - ) { + withPermissionControllerConnectedToTestDappWithChain() { + return this.withPermissionController({ + subjects: { + [DAPP_URL]: { + origin: DAPP_URL, + permissions: { + eth_accounts: { + id: 'ZaqPEWxyhNCJYACFw93jE', + parentCapability: 'eth_accounts', + invoker: DAPP_URL, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [DEFAULT_FIXTURE_ACCOUNT.toLowerCase()], + }, + ], + date: 1664388714636, + }, + 'endowment:permitted-chains': { + id: 'D7cac0a2e3BD8f349506a', + parentCapability: 'endowment:permitted-chains', + invoker: DAPP_URL, + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0x539'], + }, + ], + date: 1664388714637, + }, + }, + }, + }, + }); + } + + withPermissionControllerConnectedToTestDappWithTwoAccounts() { + const subjects = { + [DAPP_URL]: { + origin: DAPP_URL, + permissions: { + eth_accounts: { + id: 'ZaqPEWxyhNCJYACFw93jE', + parentCapability: 'eth_accounts', + invoker: DAPP_URL, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + '0x09781764c08de8ca82e156bbf156a3ca217c7950', + ], + }, + ], + date: 1664388714636, + }, + }, + }, + }; + return this.withPermissionController({ + subjects, + }); + } + + withPermissionControllerSnapAccountConnectedToTestDapp() { return this.withPermissionController({ subjects: { [DAPP_URL]: { @@ -494,7 +564,7 @@ class FixtureBuilder { id: 'ZaqPEWxyhNCJYACFw93jE', parentCapability: 'eth_accounts', invoker: DAPP_URL, - caveats: restrictReturnedAccounts && [ + caveats: [ { type: 'restrictReturnedAccounts', value: ['0x09781764c08de8ca82e156bbf156a3ca217c7950'], @@ -508,9 +578,7 @@ class FixtureBuilder { }); } - withPermissionControllerConnectedToTwoTestDapps( - restrictReturnedAccounts = true, - ) { + withPermissionControllerConnectedToTwoTestDapps() { return this.withPermissionController({ subjects: { [DAPP_URL]: { @@ -520,13 +588,10 @@ class FixtureBuilder { id: 'ZaqPEWxyhNCJYACFw93jE', parentCapability: 'eth_accounts', invoker: DAPP_URL, - caveats: restrictReturnedAccounts && [ + caveats: [ { type: 'restrictReturnedAccounts', - value: [ - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - '0x09781764c08de8ca82e156bbf156a3ca217c7950', - ], + value: ['0x5cfe73b6021e818b776b421b1c4db2474086a7e1'], }, ], date: 1664388714636, @@ -540,13 +605,10 @@ class FixtureBuilder { id: 'AqPEWxyhNCJYACFw93jE4', parentCapability: 'eth_accounts', invoker: DAPP_ONE_URL, - caveats: restrictReturnedAccounts && [ + caveats: [ { type: 'restrictReturnedAccounts', - value: [ - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - '0x09781764c08de8ca82e156bbf156a3ca217c7950', - ], + value: ['0x5cfe73b6021e818b776b421b1c4db2474086a7e1'], }, ], date: 1664388714636, @@ -671,9 +733,13 @@ class FixtureBuilder { }); } - withPreferencesControllerAndFeatureFlag(flags) { - merge(this.fixture.data.PreferencesController, flags); - return this; + withPreferencesControllerSmartTransactionsOptedOut() { + return this.withPreferencesController({ + preferences: { + smartTransactionsOptInStatus: false, + tokenNetworkFilter: {}, + }, + }); } withAccountsController(data) { @@ -819,15 +885,7 @@ class FixtureBuilder { [DAPP_ONE_URL]: '76e9cd59-d8e2-47e7-b369-9c205ccb602c', }, }), - this.withPreferencesControllerUseRequestQueueEnabled(), - ); - } - - withPreferencesControllerUseRequestQueueEnabled() { - return merge( - this.withPreferencesController({ - useRequestQueue: true, - }), + this, ); } @@ -1465,6 +1523,24 @@ class FixtureBuilder { }); } + withIncomingTransactionsPreferences(incomingTransactionsPreferences) { + return this.withPreferencesController({ + featureFlags: { + showIncomingTransactions: incomingTransactionsPreferences, + }, + }); + } + + withIncomingTransactionsCache(cache) { + return this.withTransactionController({ lastFetchedBlockNumbers: cache }); + } + + withTransactions(transactions) { + return this.withTransactionController({ + transactions, + }); + } + build() { this.fixture.meta = { version: FIXTURE_STATE_METADATA_VERSION, diff --git a/test/e2e/flask/README.md b/test/e2e/flask/README.md index abb07a11e910..42d0e6a28236 100644 --- a/test/e2e/flask/README.md +++ b/test/e2e/flask/README.md @@ -3,4 +3,4 @@ Add `.spec.js` or `.spec.ts` files to run only in flask here. > [!IMPORTANT] > Ensure to update your files in `FLASK_ONLY_TESTS` in -> [`run-all.js`](https://github.com/MetaMask/metamask-extension/blob/develop/test/e2e/run-all.js) \ No newline at end of file +> [`run-all.js`](https://github.com/MetaMask/metamask-extension/blob/main/test/e2e/run-all.js) diff --git a/test/e2e/flask/btc/btc-account-overview.spec.ts b/test/e2e/flask/btc/btc-account-overview.spec.ts index 418c9d736078..963820bf26ac 100644 --- a/test/e2e/flask/btc/btc-account-overview.spec.ts +++ b/test/e2e/flask/btc/btc-account-overview.spec.ts @@ -1,9 +1,11 @@ +import { strict as assert } from 'assert'; import { Suite } from 'mocha'; import { DEFAULT_BTC_BALANCE } from '../../constants'; +import BitcoinHomepage from '../../page-objects/pages/home/bitcoin-homepage'; import { withBtcAccountSnap } from './common-btc'; describe('BTC Account - Overview', function (this: Suite) { - it('has portfolio button enabled for BTC accounts', async function () { + it('has balance displayed and has portfolio button enabled for BTC accounts', async function () { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { @@ -33,10 +35,8 @@ describe('BTC Account - Overview', function (this: Suite) { // buy sell button await driver.findClickableElement('[data-testid="coin-overview-buy"]'); - // receive button - await driver.findClickableElement( - '[data-testid="coin-overview-receive"]', - ); + // portfolio button + await driver.findClickableElement('[data-testid="portfolio-link"]'); }, ); }); @@ -58,6 +58,16 @@ describe('BTC Account - Overview', function (this: Suite) { tag: 'p', text: `${DEFAULT_BTC_BALANCE} BTC`, }); + const homePage = new BitcoinHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.headerNavbar.check_accountLabel('Bitcoin Account'); + await homePage.check_isExpectedBitcoinBalanceDisplayed( + DEFAULT_BTC_BALANCE, + ); + assert.equal(await homePage.check_isBridgeButtonEnabled(), false); + assert.equal(await homePage.check_isSwapButtonEnabled(), false); + assert.equal(await homePage.check_isBuySellButtonEnabled(), true); + assert.equal(await homePage.check_isReceiveButtonEnabled(), true); }, ); }); diff --git a/test/e2e/flask/btc/btc-dapp-connection.spec.ts b/test/e2e/flask/btc/btc-dapp-connection.spec.ts index 2eb33a4b7d74..2a9b413f60b0 100644 --- a/test/e2e/flask/btc/btc-dapp-connection.spec.ts +++ b/test/e2e/flask/btc/btc-dapp-connection.spec.ts @@ -1,5 +1,6 @@ import { Suite } from 'mocha'; -import { openDapp, WINDOW_TITLES } from '../../helpers'; +import BitcoinHomepage from '../../page-objects/pages/home/bitcoin-homepage'; +import TestDapp from '../../page-objects/pages/test-dapp'; import { withBtcAccountSnap } from './common-btc'; describe('BTC Account - Dapp Connection', function (this: Suite) { @@ -7,14 +8,16 @@ describe('BTC Account - Dapp Connection', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await openDapp(driver); - await driver.clickElement('#connectButton'); - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const homePage = new BitcoinHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.headerNavbar.check_accountLabel('Bitcoin Account'); - await driver.assertElementNotPresent( - '[data-testid="choose-account-list-1"]', - ); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + await testDapp.connectAccount({ + connectAccountButtonEnabled: false, + }); }, ); }); diff --git a/test/e2e/flask/btc/btc-experimental-settings.spec.ts b/test/e2e/flask/btc/btc-experimental-settings.spec.ts index 45bc7d0d1701..10a183bf87bb 100644 --- a/test/e2e/flask/btc/btc-experimental-settings.spec.ts +++ b/test/e2e/flask/btc/btc-experimental-settings.spec.ts @@ -1,47 +1,45 @@ import { Suite } from 'mocha'; - -import messages from '../../../../app/_locales/en/messages.json'; import FixtureBuilder from '../../fixture-builder'; -import { - defaultGanacheOptions, - unlockWallet, - withFixtures, -} from '../../helpers'; +import { withFixtures } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import ExperimentalSettings from '../../page-objects/pages/settings/experimental-settings'; +import HomePage from '../../page-objects/pages/home/homepage'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; describe('BTC Experimental Settings', function (this: Suite) { it('will show `Add a new Bitcoin account (Beta)` option when setting is enabled', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Experimental', tag: 'div' }); + await loginWithBalanceValidation(driver); - await driver.waitForSelector({ - text: messages.bitcoinSupportToggleTitle.message, - tag: 'span', - }); + // go to experimental settings page and enable add new Bitcoin account toggle + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); - await driver.clickElement('[data-testid="bitcoin-support-toggle-div"]'); + await new HeaderNavbar(driver).openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); - await driver.clickElement('button[aria-label="Close"]'); + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + await experimentalSettings.toggleBitcoinAccount(); + await settingsPage.closeSettingsPage(); - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.waitForSelector({ - text: messages.addNewBitcoinAccount.message, - tag: 'button', - }); + // check add new Bitcoin account button is displayed + await homePage.check_pageIsLoaded(); + await new HeaderNavbar(driver).openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addBitcoinAccountAvailable(true); }, ); }); diff --git a/test/e2e/flask/btc/btc-send.spec.ts b/test/e2e/flask/btc/btc-send.spec.ts index 71d58c5c41ea..4b657df806b7 100644 --- a/test/e2e/flask/btc/btc-send.spec.ts +++ b/test/e2e/flask/btc/btc-send.spec.ts @@ -1,98 +1,51 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; -import { Driver } from '../../webdriver/driver'; import { DEFAULT_BTC_ACCOUNT, DEFAULT_BTC_BALANCE } from '../../constants'; -import { - getTransactionRequest, - SendFlowPlaceHolders, - withBtcAccountSnap, -} from './common-btc'; - -export async function startSendFlow(driver: Driver, recipient?: string) { - // Wait a bit so the MultichainRatesController is able to fetch BTC -> USD rates. - await driver.delay(1000); - - // Start the send flow. - const sendButton = await driver.waitForSelector({ - text: 'Send', - tag: 'button', - css: '[data-testid="coin-overview-send"]', - }); - // FIXME: Firefox test is flaky without this delay. The send flow doesn't start properly. - if (driver.browser === 'firefox') { - await driver.delay(1000); - } - await sendButton.click(); - - // See the review button is disabled by default. - await driver.waitForSelector({ - text: 'Review', - tag: 'button', - css: '[disabled]', - }); - - if (recipient) { - // Set the recipient address (if any). - await driver.pasteIntoField( - `input[placeholder="${SendFlowPlaceHolders.RECIPIENT}"]`, - recipient, - ); - } -} +import ActivityListPage from '../../page-objects/pages/home/activity-list'; +import BitcoinSendPage from '../../page-objects/pages/send/bitcoin-send-page'; +import BitcoinHomepage from '../../page-objects/pages/home/bitcoin-homepage'; +import BitcoinReviewTxPage from '../../page-objects/pages/send/bitcoin-review-tx-page'; +import { getTransactionRequest, withBtcAccountSnap } from './common-btc'; describe('BTC Account - Send', function (this: Suite) { - it('can send complete the send flow', async function () { + it('can complete the send flow', async function () { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver, mockServer) => { - await startSendFlow(driver, DEFAULT_BTC_ACCOUNT); + const homePage = new BitcoinHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_isExpectedBitcoinBalanceDisplayed( + DEFAULT_BTC_BALANCE, + ); + await homePage.startSendFlow(); + // Set the recipient address and amount + const bitcoinSendPage = new BitcoinSendPage(driver); + await bitcoinSendPage.check_pageIsLoaded(); + await bitcoinSendPage.fillRecipientAddress(DEFAULT_BTC_ACCOUNT); // TODO: Remove delay here. There is a race condition if the amount and address are set too fast. await driver.delay(1000); - - // Set the amount to send. const mockAmountToSend = '0.5'; - await driver.pasteIntoField( - `input[placeholder="${SendFlowPlaceHolders.AMOUNT}"]`, - mockAmountToSend, - ); - - // From here, the "summary panel" should have some information about the fees and total. - await driver.waitForSelector({ - text: 'Total', - tag: 'p', - }); + await bitcoinSendPage.fillAmount(mockAmountToSend); - // The review button will become available. - const snapReviewButton = await driver.findClickableElement({ - text: 'Review', - tag: 'button', - css: '.snap-ui-renderer__footer-button', - }); - assert.equal(await snapReviewButton.isEnabled(), true); - await snapReviewButton.click(); + // Click the review button + await bitcoinSendPage.clickReviewButton(); // TODO: There isn't any check for the fees and total amount. This requires calculating the vbytes used in a transaction dynamically. // We already have unit tests for these calculations on the Snap. - // ------------------------------------------------------------------------------ // From here, we have moved to the confirmation screen (second part of the flow). - // We should be able to send the transaction right away. - const snapSendButton = await driver.waitForSelector({ - text: 'Send', - tag: 'button', - css: '.snap-ui-renderer__footer-button', - }); - assert.equal(await snapSendButton.isEnabled(), true); - await snapSendButton.click(); - - // Check that we are selecting the "Activity tab" right after the send. - await driver.waitForSelector({ - tag: 'div', - text: 'Bitcoin activity is not supported', - }); + // Click the send transaction button + const bitcoinReviewTxPage = new BitcoinReviewTxPage(driver); + await bitcoinReviewTxPage.check_pageIsLoaded(); + await bitcoinReviewTxPage.clickSendButton(); + // Check that we are on the activity list page and the warning message is displayed + await homePage.check_pageIsLoaded(); + await new ActivityListPage(driver).check_warningMessage( + 'Bitcoin activity is not supported', + ); const transaction = await getTransactionRequest(mockServer); assert(transaction !== undefined); }, @@ -103,36 +56,26 @@ describe('BTC Account - Send', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver, mockServer) => { - await startSendFlow(driver, DEFAULT_BTC_ACCOUNT); + const homePage = new BitcoinHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_isExpectedBitcoinBalanceDisplayed( + DEFAULT_BTC_BALANCE, + ); + await homePage.startSendFlow(); + // Use the max spendable amount of that account + const bitcoinSendPage = new BitcoinSendPage(driver); + await bitcoinSendPage.check_pageIsLoaded(); + await bitcoinSendPage.fillRecipientAddress(DEFAULT_BTC_ACCOUNT); // TODO: Remove delay here. There is a race condition if the amount and address are set too fast. await driver.delay(1000); - - // Use the max spendable amount of that account. - await driver.clickElement({ - text: 'Max', - tag: 'button', - }); + await bitcoinSendPage.selectMaxAmount(); + await bitcoinSendPage.check_amountIsDisplayed( + `${DEFAULT_BTC_BALANCE} BTC`, + ); // From here, the "summary panel" should have some information about the fees and total. - await driver.waitForSelector({ - text: 'Total', - tag: 'p', - }); - - await driver.waitForSelector({ - text: `${DEFAULT_BTC_BALANCE} BTC`, - tag: 'p', - }); - - // The review button will become available. - const snapReviewButton = await driver.findClickableElement({ - text: 'Review', - tag: 'button', - css: '.snap-ui-renderer__footer-button', - }); - assert.equal(await snapReviewButton.isEnabled(), true); - await snapReviewButton.click(); + await bitcoinSendPage.clickReviewButton(); // TODO: There isn't any check for the fees and total amount. This requires calculating the vbytes used in a transaction dynamically. // We already have unit tests for these calculations on the snap. @@ -140,21 +83,16 @@ describe('BTC Account - Send', function (this: Suite) { // ------------------------------------------------------------------------------ // From here, we have moved to the confirmation screen (second part of the flow). - // We should be able to send the transaction right away. - const snapSendButton = await driver.waitForSelector({ - text: 'Send', - tag: 'button', - css: '.snap-ui-renderer__footer-button', - }); - assert.equal(await snapSendButton.isEnabled(), true); - await snapSendButton.click(); - - // Check that we are selecting the "Activity tab" right after the send. - await driver.waitForSelector({ - tag: 'div', - text: 'Bitcoin activity is not supported', - }); + // Click the send transaction button + const bitcoinReviewTxPage = new BitcoinReviewTxPage(driver); + await bitcoinReviewTxPage.check_pageIsLoaded(); + await bitcoinReviewTxPage.clickSendButton(); + // Check that we are on the activity list page and the warning message is displayed + await homePage.check_pageIsLoaded(); + await new ActivityListPage(driver).check_warningMessage( + 'Bitcoin activity is not supported', + ); const transaction = await getTransactionRequest(mockServer); assert(transaction !== undefined); }, diff --git a/test/e2e/flask/btc/common-btc.ts b/test/e2e/flask/btc/common-btc.ts index 05f382281e29..0c76cbfbbbcd 100644 --- a/test/e2e/flask/btc/common-btc.ts +++ b/test/e2e/flask/btc/common-btc.ts @@ -2,11 +2,13 @@ import { Mockttp } from 'mockttp'; import FixtureBuilder from '../../fixture-builder'; import { withFixtures } from '../../helpers'; import { + ACCOUNT_TYPE, DEFAULT_BTC_ACCOUNT, DEFAULT_BTC_BALANCE, DEFAULT_BTC_FEES_RATE, DEFAULT_BTC_TRANSACTION_ID, DEFAULT_BTC_CONVERSION_RATE, + DEFAULT_BTC_BLOCK_NUMBER, SATS_IN_1_BTC, } from '../../constants'; import { MultichainNetworks } from '../../../../shared/constants/multichain/networks'; @@ -15,14 +17,9 @@ import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow' import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; +const SIMPLEHASH_URL = 'https://api.simplehash.com'; const QUICKNODE_URL_REGEX = /^https:\/\/.*\.btc.*\.quiknode\.pro(\/|$)/u; -export enum SendFlowPlaceHolders { - AMOUNT = 'Enter amount to send', - RECIPIENT = 'Enter receiving address', - LOADING = 'Preparing transaction', -} - export function btcToSats(btc: number): number { // Watchout, we're not using BigNumber(s) here (but that's ok for test purposes) return btc * SATS_IN_1_BTC; @@ -117,7 +114,7 @@ export async function mockGetUTXO(mockServer: Mockttp) { txid: DEFAULT_BTC_TRANSACTION_ID, vout: 0, value: btcToSats(DEFAULT_BTC_BALANCE).toString(), - height: 101100110, + height: DEFAULT_BTC_BLOCK_NUMBER, confirmations: 6, }, ], @@ -182,6 +179,36 @@ export async function mockRampsDynamicFeatureFlag( })); } +export async function mockBtcSatProtectionService( + mockServer: Mockttp, + address: string = DEFAULT_BTC_ACCOUNT, +) { + // NOTE: This endpoint is also used to compute the total balance if Sat Protection is enabled, so we have + // to compute the set of UTXOS here too. + const utxos = [ + { + output: `${DEFAULT_BTC_TRANSACTION_ID}:0`, + value: btcToSats(DEFAULT_BTC_BALANCE), + block_number: DEFAULT_BTC_BLOCK_NUMBER, + }, + ]; + + return await mockServer + .forGet(`${SIMPLEHASH_URL}/api/v0/custom/wallet_assets_by_utxo/${address}`) + .withQuery({ + without_inscriptions_runes_raresats: '1', + }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + count: utxos.length, + utxos, + }, + }; + }); +} + export async function withBtcAccountSnap( { title, @@ -192,7 +219,7 @@ export async function withBtcAccountSnap( await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ bitcoinSupportEnabled: bitcoinSupportEnabled ?? true, }) .build(), @@ -209,6 +236,8 @@ export async function withBtcAccountSnap( await mockBtcFeeCallQuote(mockServer), await mockGetUTXO(mockServer), await mockSendTransaction(mockServer), + // Sat Protection + await mockBtcSatProtectionService(mockServer), ], }, async ({ driver, mockServer }: { driver: Driver; mockServer: Mockttp }) => { @@ -217,7 +246,7 @@ export async function withBtcAccountSnap( await new HeaderNavbar(driver).openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewBtcAccount(); + await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin }); await test(driver, mockServer); }, ); diff --git a/test/e2e/flask/btc/create-btc-account.spec.ts b/test/e2e/flask/btc/create-btc-account.spec.ts index 0dfb24a8a931..68cdcd82caf5 100644 --- a/test/e2e/flask/btc/create-btc-account.spec.ts +++ b/test/e2e/flask/btc/create-btc-account.spec.ts @@ -1,12 +1,14 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; import { WALLET_PASSWORD } from '../../helpers'; +import AccountDetailsModal from '../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import LoginPage from '../../page-objects/pages/login-page'; import PrivacySettings from '../../page-objects/pages/settings/privacy-settings'; import ResetPasswordPage from '../../page-objects/pages/reset-password-page'; import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { ACCOUNT_TYPE } from '../../constants'; import { withBtcAccountSnap } from './common-btc'; describe('Create BTC Account', function (this: Suite) { @@ -34,14 +36,12 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewBtcAccount({ - btcAccountCreationEnabled: false, - }); - - // check the number of available accounts is 2 - await headerNavbar.openAccountMenu(); - await accountListPage.check_pageIsLoaded(); await accountListPage.check_numberOfAvailableAccounts(2); + await accountListPage.openAddAccountModal(); + assert.equal( + await accountListPage.isBtcAccountCreationButtonEnabled(), + false, + ); }, ); }); @@ -83,23 +83,32 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - const accountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + const accountAddress = await accountDetailsModal.getAccountAddress(); await headerNavbar.openAccountMenu(); await accountListPage.removeAccount('Bitcoin Account'); // Recreate account and check that the address is the same await headerNavbar.openAccountMenu(); - await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewBtcAccount(); + await accountListPage.openAddAccountModal(); + assert.equal( + await accountListPage.isBtcAccountCreationButtonEnabled(), + true, + ); + await accountListPage.closeAccountModal(); + await headerNavbar.openAccountMenu(); + await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin }); await headerNavbar.check_accountLabel('Bitcoin Account'); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - const recreatedAccountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + await accountDetailsModal.check_pageIsLoaded(); + const recreatedAccountAddress = + await accountDetailsModal.getAccountAddress(); assert(accountAddress === recreatedAccountAddress); }, @@ -118,9 +127,10 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - const accountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + const accountAddress = await accountDetailsModal.getAccountAddress(); // go to privacy settings page and get the SRP await headerNavbar.openSettingsPage(); @@ -146,14 +156,16 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.check_pageIsLoaded(); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewBtcAccount(); + await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin }); await headerNavbar.check_accountLabel('Bitcoin Account'); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - const recreatedAccountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + await accountDetailsModal.check_pageIsLoaded(); + const recreatedAccountAddress = + await accountDetailsModal.getAccountAddress(); + assert(accountAddress === recreatedAccountAddress); }, ); diff --git a/test/e2e/flask/create-watch-account.spec.ts b/test/e2e/flask/create-watch-account.spec.ts index 71d47327536c..6d0956ae4e72 100644 --- a/test/e2e/flask/create-watch-account.spec.ts +++ b/test/e2e/flask/create-watch-account.spec.ts @@ -1,97 +1,44 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; -import messages from '../../../app/_locales/en/messages.json'; import FixtureBuilder from '../fixture-builder'; -import { defaultGanacheOptions, unlockWallet, withFixtures } from '../helpers'; +import { withFixtures } from '../helpers'; import { Driver } from '../webdriver/driver'; +import AccountDetailsModal from '../page-objects/pages/dialog/account-details-modal'; +import AccountListPage from '../page-objects/pages/account-list-page'; +import ExperimentalSettings from '../page-objects/pages/settings/experimental-settings'; +import HeaderNavbar from '../page-objects/pages/header-navbar'; +import HomePage from '../page-objects/pages/home/homepage'; +import SettingsPage from '../page-objects/pages/settings/settings-page'; +import { loginWithBalanceValidation } from '../page-objects/flows/login.flow'; +import { watchEoaAddress } from '../page-objects/flows/watch-account.flow'; const ACCOUNT_1 = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; const EOA_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; const SHORTENED_EOA_ADDRESS = '0xd8dA6...96045'; const DEFAULT_WATCHED_ACCOUNT_NAME = 'Watched Account 1'; -/** - * Start the flow to create a watch account by clicking the account menu and selecting the option to add a watch account. - * - * @param driver - The WebDriver instance used to control the browser. - * @param unlockWalletFirst - Whether to unlock the wallet before starting the flow. - */ -async function startCreateWatchAccountFlow( - driver: Driver, - unlockWalletFirst: boolean = true, -): Promise { - if (unlockWalletFirst) { - await unlockWallet(driver); - } - - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-add-watch-only-account"]', - ); -} - -/** - * Watches an EOA address. - * - * @param driver - The WebDriver instance used to control the browser. - * @param unlockWalletFirst - Whether to unlock the wallet before watching the address. - * @param address - The EOA address to watch. - */ -async function watchEoaAddress( - driver: Driver, - unlockWalletFirst: boolean = true, - address: string = EOA_ADDRESS, -): Promise { - await startCreateWatchAccountFlow(driver, unlockWalletFirst); - await driver.fill('input#address-input[type="text"]', address); - await driver.clickElement({ text: 'Watch account', tag: 'button' }); - await driver.clickElement('[data-testid="submit-add-account-with-name"]'); -} - -/** - * Removes the selected account. - * - * @param driver - The WebDriver instance used to control the browser. - */ -async function removeSelectedAccount(driver: Driver): Promise { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', - ); - await driver.clickElement('[data-testid="account-list-menu-remove"]'); - await driver.clickElement({ text: 'Remove', tag: 'button' }); -} - describe('Account-watcher snap', function (this: Suite) { describe('Adding watched accounts', function () { it('adds watch account with valid EOA address', async function () { await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ watchEthereumAccountEnabled: true, }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); // new account should be displayed in the account list - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.findElement({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel(DEFAULT_WATCHED_ACCOUNT_NAME); + await headerNavbar.check_accountAddress(SHORTENED_EOA_ADDRESS); }, ); }); @@ -100,45 +47,34 @@ describe('Account-watcher snap', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ watchEthereumAccountEnabled: true, }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); + const homePage = new HomePage(driver); + await homePage.headerNavbar.check_accountLabel( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); // 'Send' button should be disabled - await driver.findElement( - '[data-testid="eth-overview-send"][disabled]', - ); - await driver.findElement( - '[data-testid="eth-overview-send"].icon-button--disabled', - ); + assert.equal(await homePage.check_ifSendButtonIsClickable(), false); // 'Swap' button should be disabled - await driver.findElement( - '[data-testid="token-overview-button-swap"][disabled]', - ); - await driver.findElement( - '[data-testid="token-overview-button-swap"].icon-button--disabled', - ); + assert.equal(await homePage.check_ifSwapButtonIsClickable(), false); // 'Bridge' button should be disabled - await driver.findElement( - '[data-testid="eth-overview-bridge"][disabled]', - ); - await driver.findElement( - '[data-testid="eth-overview-bridge"].icon-button--disabled', - ); + assert.equal(await homePage.check_ifBridgeButtonIsClickable(), false); // check tooltips for disabled buttons - await driver.findElement( - '.icon-button--disabled [data-tooltipped][data-original-title="Not supported with this account."]', + await homePage.check_disabledButtonTooltip( + 'Not supported with this account.', ); }, ); @@ -177,25 +113,24 @@ describe('Account-watcher snap', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ watchEthereumAccountEnabled: true, }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { - await startCreateWatchAccountFlow(driver); - - await driver.fill('input#address-input[type="text"]', input); - await driver.clickElement({ text: 'Watch account', tag: 'button' }); - - // error message should be displayed by the snap - await driver.findElement({ - css: '.snap-ui-renderer__text', - text: message, - }); + await loginWithBalanceValidation(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // error message should be displayed by snap when try to watch an EOA with invalid input + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addEoaAccount(input, message); }, ); }); @@ -211,35 +146,28 @@ describe('Account-watcher snap', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ watchEthereumAccountEnabled: true, }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address for ACCOUNT_2 - await watchEoaAddress(driver, true, ACCOUNT_2); - - // try to import private key of watched ACCOUNT_2 address - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, ACCOUNT_2); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel(DEFAULT_WATCHED_ACCOUNT_NAME); + + // try to import private key of watched ACCOUNT_2 address and check error message + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addNewImportedAccount( + PRIVATE_KEY_TWO, + 'KeyringController - The account you are trying to import is a duplicate', ); - await driver.clickElement({ text: 'Import account', tag: 'button' }); - await driver.findClickableElement('#private-key-box'); - await driver.fill('#private-key-box', PRIVATE_KEY_TWO); - await driver.clickElement( - '[data-testid="import-account-confirm-button"]', - ); - - // error message should be displayed - await driver.findElement({ - css: '.mm-box--color-error-default', - text: 'KeyringController - The account you are trying to import is a duplicate', - }); }, ); }); @@ -248,180 +176,117 @@ describe('Account-watcher snap', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ - watchEthereumAccountEnabled: true, - }) - .withNetworkControllerOnMainnet() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ driver }: { driver: Driver }) => { - // watch an EOA address - await watchEoaAddress(driver); - - // click to view account details - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement( - '[data-testid="account-list-menu-details"]', - ); - // 'Show private key' button should not be displayed - await driver.assertElementNotPresent({ - css: 'button', - text: 'Show private key', - }); - }, - ); - }); - - it('removes a watched account', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ watchEthereumAccountEnabled: true, }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); - - // remove the selected watched account - await removeSelectedAccount(driver); - - // account should be removed from the account list - await driver.assertElementNotPresent({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.assertElementNotPresent({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); + + // open account details modal in header navbar + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel(DEFAULT_WATCHED_ACCOUNT_NAME); + await headerNavbar.openAccountDetailsModal(); + + // check 'Show private key' button should not be displayed + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.check_showPrivateKeyButtonIsNotDisplayed(); }, ); }); - it('can remove and recreate a watched account', async function () { + it('removes a watched account and recreate a watched account', async function () { await withFixtures( { fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ + .withPreferencesController({ watchEthereumAccountEnabled: true, }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); + const homePage = new HomePage(driver); + await homePage.headerNavbar.check_accountLabel( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); // remove the selected watched account - await removeSelectedAccount(driver); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.removeAccount(DEFAULT_WATCHED_ACCOUNT_NAME); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); // account should be removed from the account list - await driver.assertElementNotPresent({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.assertElementNotPresent({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); - - // watch the same EOA address again - await watchEoaAddress(driver, false); - - // same account should be displayed in the account list - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.findElement({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); + await homePage.headerNavbar.openAccountMenu(); + await accountListPage.check_accountIsNotDisplayedInAccountList( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); + await accountListPage.closeAccountModal(); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // watch the same EOA address again and check the account is recreated + await watchEoaAddress(driver, EOA_ADDRESS); + await homePage.headerNavbar.check_accountLabel( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); + await homePage.headerNavbar.check_accountAddress( + SHORTENED_EOA_ADDRESS, + ); }, ); }); }); describe('Experimental toggle', function () { - const navigateToExperimentalSettings = async (driver: Driver) => { - await driver.clickElement('[data-testid="account-options-menu-button"]'); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Experimental', tag: 'div' }); - await driver.waitForSelector({ - text: messages.watchEthereumAccountsToggle.message, - tag: 'span', - }); - }; - - const getToggleState = async (driver: Driver): Promise => { - const toggleInput = await driver.findElement( - '[data-testid="watch-account-toggle"]', - ); - return toggleInput.isSelected(); - }; - - const toggleWatchAccountOptionAndCloseSettings = async (driver: Driver) => { - await driver.clickElement('[data-testid="watch-account-toggle-div"]'); - await driver.clickElement('button[aria-label="Close"]'); - }; - - const verifyWatchAccountOptionAndCloseMenu = async ( - driver: Driver, - shouldBePresent: boolean, - ) => { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - if (shouldBePresent) { - await driver.waitForSelector({ - text: messages.addEthereumWatchOnlyAccount.message, - tag: 'button', - }); - } else { - await driver.assertElementNotPresent({ - text: messages.addEthereumWatchOnlyAccount.message, - tag: 'button', - }); - } - await driver.clickElement('button[aria-label="Close"]'); - }; - it("will show the 'Watch an Ethereum account (Beta)' option when setting is enabled", async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); - await navigateToExperimentalSettings(driver); - - // verify toggle is off by default + await loginWithBalanceValidation(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // navigate to experimental settings + await homePage.headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + + // verify watch account toggle is off by default and enable the toggle assert.equal( - await getToggleState(driver), + await experimentalSettings.getWatchAccountToggleState(), false, 'Toggle should be off by default', ); - - // enable the toggle - await toggleWatchAccountOptionAndCloseSettings(driver); + await experimentalSettings.toggleWatchAccount(); + await settingsPage.closeSettingsPage(); // verify the 'Watch and Ethereum account (Beta)' option is available - await verifyWatchAccountOptionAndCloseMenu(driver, true); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addWatchAccountAvailable(true); }, ); }); @@ -430,27 +295,49 @@ describe('Account-watcher snap', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); - await navigateToExperimentalSettings(driver); - - // enable the toggle - await toggleWatchAccountOptionAndCloseSettings(driver); + await loginWithBalanceValidation(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // navigate to experimental settings and enable the toggle + await homePage.headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + await experimentalSettings.toggleWatchAccount(); + await settingsPage.closeSettingsPage(); // verify the 'Watch and Ethereum account (Beta)' option is available - await verifyWatchAccountOptionAndCloseMenu(driver, true); - - // navigate back to experimental settings - await navigateToExperimentalSettings(driver); - - // disable the toggle - await toggleWatchAccountOptionAndCloseSettings(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addWatchAccountAvailable(true); + await accountListPage.closeAccountModal(); + + // navigate back to experimental settings and disable the toggle + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openSettingsPage(); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + await experimentalSettings.check_pageIsLoaded(); + await experimentalSettings.toggleWatchAccount(); + await settingsPage.closeSettingsPage(); // verify the 'Watch and Ethereum account (Beta)' option is not available - await verifyWatchAccountOptionAndCloseMenu(driver, false); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addWatchAccountAvailable(false); }, ); }); diff --git a/test/e2e/flask/solana/check-balance.spec.ts b/test/e2e/flask/solana/check-balance.spec.ts new file mode 100644 index 000000000000..266c33b6de53 --- /dev/null +++ b/test/e2e/flask/solana/check-balance.spec.ts @@ -0,0 +1,61 @@ +import { Suite } from 'mocha'; +import NonEvmHomepage from '../../page-objects/pages/home/non-evm-homepage'; +import { withSolanaAccountSnap } from './common-solana'; + +describe('Check balance', function (this: Suite) { + this.timeout(300000); + it('Just created Solana account shows 0 SOL when native token is enabled', async function () { + await withSolanaAccountSnap( + { title: this.test?.fullTitle(), showNativeTokenAsMainBalance: true }, + async (driver) => { + await driver.refresh(); + const homePage = new NonEvmHomepage(driver); + await homePage.check_getBalance('0 SOL'); + }, + ); + }); + it.skip('Just created Solana account shows 0 USD when native token is not enabled', async function () { + await withSolanaAccountSnap( + { + title: this.test?.fullTitle(), + solanaSupportEnabled: true, + showNativeTokenAsMainBalance: false, + }, + async (driver) => { + await driver.refresh(); + const homePage = new NonEvmHomepage(driver); + await homePage.check_getBalance(`0.00\nUSD`); + }, + ); + }); + it.skip('For a non 0 balance account - SOL balance', async function () { + await withSolanaAccountSnap( + { + title: this.test?.fullTitle(), + solanaSupportEnabled: true, + showNativeTokenAsMainBalance: true, + mockCalls: true, + }, + async (driver) => { + await driver.refresh(); + const homePage = new NonEvmHomepage(driver); + await homePage.check_getBalance(`50\nSOL`); + }, + ); + }); + it.skip('For a non 0 balance account - USD balance', async function () { + await withSolanaAccountSnap( + { + title: this.test?.fullTitle(), + solanaSupportEnabled: true, + showNativeTokenAsMainBalance: false, + mockCalls: true, + }, + async (driver) => { + await driver.refresh(); + const homePage = new NonEvmHomepage(driver); + await homePage.check_getBalance(`11294\nUSD`); + }, + ); + }); +}); diff --git a/test/e2e/flask/solana/common-solana.ts b/test/e2e/flask/solana/common-solana.ts new file mode 100644 index 000000000000..51df080351d5 --- /dev/null +++ b/test/e2e/flask/solana/common-solana.ts @@ -0,0 +1,300 @@ +import { Mockttp } from 'mockttp'; +import { withFixtures } from '../../helpers'; +import { Driver } from '../../webdriver/driver'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import FixtureBuilder from '../../fixture-builder'; +import { ACCOUNT_TYPE } from '../../constants'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +const SOLANA_URL_REGEX = /.*/u; +// const SOLANA_RPC_PROVIDER = 'https://api.devnet.solana.com/'; +const SOLANA_PRICE_REGEX = + /^https:\/\/price-api\.metamask-institutional\.io\/v2\/chains\/solana:/u; +const SOLANA_BITCOIN_MIN_API = + /^https:\/\/min-api\.cryptocompare\.com\/data\/pricemulti/u; +export enum SendFlowPlaceHolders { + AMOUNT = 'Enter amount to send', + RECIPIENT = 'Enter receiving address', + LOADING = 'Preparing transaction', +} + +export const SOL_BALANCE = 50000000000; + +export const SOL_TO_USD_RATE = 225.88; + +export const USD_BALANCE = SOL_BALANCE * SOL_TO_USD_RATE; + +export const LAMPORTS_PER_SOL = 1_000_000_000; + +export async function mockMultiCoinPrice(mockServer: Mockttp) { + return await mockServer.forGet(SOLANA_BITCOIN_MIN_API).thenCallback(() => { + return { + statusCode: 200, + json: { + BTC: { + USD: 96155.06, + }, + SOL: { + USD: 180.5, + }, + }, + }; + }); +} + +export async function mockSolanaBalanceQuote(mockServer: Mockttp) { + const response = { + statusCode: 200, + json: { + result: { + context: { + apiVersion: '2.0.18', + slot: 308460925, + }, + value: SOL_BALANCE, + }, + id: 1337, + }, + }; + return await mockServer + .forPost(SOLANA_URL_REGEX) + .withJsonBodyIncluding({ + method: 'getBalance', + }) + .thenCallback(() => { + return response; + }); +} + +export async function mockGetLatestBlockhash(mockServer: Mockttp) { + const response = { + statusCode: 200, + json: { + result: { + context: { + apiVersion: '2.0.18', + slot: 308460925, + }, + value: { + blockhash: '6E9FiVcuvavWyKTfYC7N9ezJWkNgJVQsroDTHvqApncg', + lastValidBlockHeight: 341034515, + }, + }, + id: 1337, + }, + }; + return await mockServer + .forPost(SOLANA_URL_REGEX) + .withJsonBodyIncluding({ + method: 'getLatestBlockhash', + }) + .thenCallback(() => { + return response; + }); +} +export async function mockGetSignaturesForAddress(mockServer: Mockttp) { + return await mockServer + .forPost(SOLANA_URL_REGEX) + .withBodyIncluding('getSignaturesForAddress') + .thenCallback(() => { + return { + statusCode: 200, + json: { + result: [ + { + blockTime: 1734620122, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '5THAXC3pHCRwwwrMHR6PJiSqFfgSkZrBhn59C7YEbTMVbiAnjZhqpPvJYs4v5aRcqUiokunfbdTgo9HLfv6bogNR', + slot: 348093552, + }, + { + blockTime: 1734619950, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '5KHuDsTMjre6rWU5Qkf8ugG31PjWoZ8NbV21ThY8RwcHpn3dKbTafdizUkEj4sU2AfrRzVxgyGkX8MLxK5nWHJ6J', + slot: 348093088, + }, + { + blockTime: 1734619916, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '2RcW9iJCGnYuGVbDbaDi93t2f2347a6gzjoQf9idDdfFTjHsC7yMYcUvGqNzouKgA8T8tdYqjNUtDf4vR4e9iUoF', + slot: 348092996, + }, + { + blockTime: 1734619899, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '2kCcoXZxe14384c8JTvq1g63pSjmmyuDnye9y3ReBMEiaZeGWspsmooEdC4RoyzP6uTfaDyFpCupBAKXnZwXCKMg', + slot: 348092952, + }, + { + blockTime: 1734619885, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '4fzwGY4Tw5C4nYMaVAY7e3ZMwz691CbT7By4F4YFdukzBxd7yspmZEHhBtuPhFrqLj1yBn6zpc4kh1GLzgcovEbx', + slot: 348092914, + }, + { + blockTime: 1734619758, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '2vgL59tfVa2VJkf7kmsGhbdBFjHdspLa1wfL72zZqHfJuzhmKfqS4YoLofpMTnZzzZfiA6712pwURheMUh5S2RXd', + slot: 348092568, + }, + { + blockTime: 1734619697, + confirmationStatus: 'finalized', + err: null, + memo: null, + signature: + '32fqeHudeNBuDmyCrmRemFppVPpWmXwT4cbfai5D7G2Vzah1BvVguLqkNuk9Pdu4xVyBD32dhnSV8AN9k4qnffSB', + slot: 348092404, + }, + ], + }, + }; + }); +} + +export async function mockSendSolanaTransaction(mockServer: Mockttp) { + const response = { + statusCode: 200, + json: { + result: + '3nqGKH1ef8WkTgKXZ8q3xKsvjktWmHHhJpZMSdbB6hBqy5dA7aLVSAUjw5okezZjKMHiNg2MF5HAqtpmsesQtnpj', + id: 1337, + }, + }; + return await mockServer + .forPost(SOLANA_URL_REGEX) + .withJsonBodyIncluding({ + method: 'sendTransaction', + }) + .thenCallback(() => { + return response; + }); +} + +export async function mockSolanaRatesCall(mockServer: Mockttp) { + return await mockServer + .forGet(SOLANA_PRICE_REGEX) + .withQuery({ vsCurrency: 'usd' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + id: 'wrapped-solana', + price: 210.57, + marketCap: 0, + allTimeHigh: 263.68, + allTimeLow: 8.11, + totalVolume: 3141761864, + high1d: 218.26, + low1d: 200.85, + circulatingSupply: 0, + dilutedMarketCap: 124394527657, + marketCapPercentChange1d: 0, + priceChange1d: -7.68288033909846, + pricePercentChange1h: 0.5794201955743261, + pricePercentChange1d: -3.520101943578202, + pricePercentChange7d: -8.192700158252544, + pricePercentChange14d: -12.477367449577399, + pricePercentChange30d: -14.588630064677465, + pricePercentChange200d: 28.111509321033513, + pricePercentChange1y: 181.48381055890258, + }, + }; + }); +} + +export async function mockGetTokenAccountsByOwner(mockServer: Mockttp) { + return await mockServer + .forPost(SOLANA_URL_REGEX) + .withJsonBodyIncluding({ + method: 'getTokenAccountsByOwner', + }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + result: [], // Empty for now, it has been mocked to avoid network calls. + }, + }; + }); +} + +export async function withSolanaAccountSnap( + { + title, + solanaSupportEnabled, + showNativeTokenAsMainBalance, + mockCalls, + mockSendTransaction, + }: { + title?: string; + solanaSupportEnabled?: boolean; + showNativeTokenAsMainBalance?: boolean; + mockCalls?: boolean; + mockSendTransaction?: boolean; + }, + test: (driver: Driver, mockServer: Mockttp) => Promise, +) { + console.log('Starting withSolanaAccountSnap'); + let fixtures = new FixtureBuilder().withPreferencesController({ + solanaSupportEnabled: solanaSupportEnabled ?? true, + }); + if (!showNativeTokenAsMainBalance) { + fixtures = + fixtures.withPreferencesControllerShowNativeTokenAsMainBalanceDisabled(); + } + await withFixtures( + { + fixtures: fixtures.build(), + title, + dapp: true, + testSpecificMock: async (mockServer: Mockttp) => { + const mockList = []; + if (mockCalls) { + mockList.push([ + await mockSolanaBalanceQuote(mockServer), + await mockSolanaRatesCall(mockServer), + await mockGetTokenAccountsByOwner(mockServer), + await mockGetSignaturesForAddress(mockServer), + await mockMultiCoinPrice(mockServer), + await mockGetLatestBlockhash(mockServer), + ]); + } + if (mockSendTransaction) { + mockList.push(await mockSendSolanaTransaction(mockServer)); + } + return mockList; + }, + }, + async ({ driver, mockServer }: { driver: Driver; mockServer: Mockttp }) => { + await loginWithBalanceValidation(driver); + const headerComponen = new HeaderNavbar(driver); + await headerComponen.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Solana, + accountName: 'Solana 1', + }); + await test(driver, mockServer); + }, + ); +} diff --git a/test/e2e/flask/solana/create-solana-account.spec.ts b/test/e2e/flask/solana/create-solana-account.spec.ts new file mode 100644 index 000000000000..0cac9fab7375 --- /dev/null +++ b/test/e2e/flask/solana/create-solana-account.spec.ts @@ -0,0 +1,65 @@ +import { Suite } from 'mocha'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import { ACCOUNT_TYPE } from '../../constants'; +import { withSolanaAccountSnap } from './common-solana'; + +// Scenarios skipped due to https://consensyssoftware.atlassian.net/browse/SOL-87 +describe('Create Solana Account', function (this: Suite) { + it.skip('Creates 2 Solana accounts', async function () { + await withSolanaAccountSnap( + { title: this.test?.fullTitle() }, + async (driver) => { + // check that we have one Solana account + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Solana 1'); + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_accountDisplayedInAccountList('Account 1'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Solana, + accountName: 'Solana 2', + }); + await headerNavbar.check_accountLabel('Solana 2'); + await headerNavbar.openAccountMenu(); + await accountListPage.check_numberOfAvailableAccounts(3); + }, + ); + }); + it('Creates a Solana account from the menu', async function () { + await withSolanaAccountSnap( + { title: this.test?.fullTitle() }, + async (driver) => { + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Solana 1'); + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_accountDisplayedInAccountList('Account 1'); + await accountListPage.check_accountDisplayedInAccountList('Solana 1'); + }, + ); + }); +}); +it.skip('Removes Solana account after creating it', async function () { + await withSolanaAccountSnap( + { title: this.test?.fullTitle() }, + async (driver) => { + // check that we have one Solana account + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('Solana 1'); + // check user can cancel the removal of the Solana account + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_accountDisplayedInAccountList('Account 1'); + await accountListPage.removeAccount('Solana 1', true); + await headerNavbar.check_accountLabel('Account 1'); + await headerNavbar.openAccountMenu(); + await accountListPage.check_accountDisplayedInAccountList('Account 1'); + await accountListPage.check_accountIsNotDisplayedInAccountList( + 'Solana 1', + ); + }, + ); +}); diff --git a/test/e2e/flask/solana/send-flow.spec.ts b/test/e2e/flask/solana/send-flow.spec.ts new file mode 100644 index 000000000000..b110aed954e8 --- /dev/null +++ b/test/e2e/flask/solana/send-flow.spec.ts @@ -0,0 +1,447 @@ +import { strict as assert } from 'assert'; +import { Suite } from 'mocha'; + +import SendSolanaPage from '../../page-objects/pages/send/solana-send-page'; +import ConfirmSolanaTxPage from '../../page-objects/pages/send/solana-confirm-tx-page'; +import SolanaTxresultPage from '../../page-objects/pages/send/solana-tx-result-page'; +import NonEvmHomepage from '../../page-objects/pages/home/non-evm-homepage'; +import { withSolanaAccountSnap } from './common-solana'; + +const commonSolanaAddress = 'GYP1hGem9HBkYKEWNUQUxEwfmu4hhjuujRgGnj5LrHna'; +describe.skip('Send flow', function (this: Suite) { + it('with some field validation', async function () { + this.timeout(120000); + await withSolanaAccountSnap( + { title: this.test?.fullTitle(), showNativeTokenAsMainBalance: true }, + async (driver) => { + await driver.refresh(); // workaround to not get an error due to https://consensyssoftware.atlassian.net/browse/SOL-87 + const homePage = new NonEvmHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.clickOnSendButton(); + const sendSolanaPage = new SendSolanaPage(driver); + assert.equal( + await sendSolanaPage.isContinueButtonEnabled(), + false, + 'Continue button is enabled and it shouldn`t', + ); + await sendSolanaPage.setToAddress('2433asd'); + assert.equal( + await sendSolanaPage.check_validationErrorAppears( + 'Invalid Solana address', + ), + true, + 'Invalid Solana address should appear and it does not', + ); + await sendSolanaPage.setToAddress(''); + assert.equal( + await sendSolanaPage.check_validationErrorAppears( + 'To address is required', + ), + true, + 'To address is required should appear and it does not', + ); + await sendSolanaPage.setToAddress(commonSolanaAddress); + await sendSolanaPage.setAmount('0.1'); + assert.equal( + await sendSolanaPage.check_validationErrorAppears( + 'Insufficient balance', + ), + true, + 'Insufficient balance text is not displayed', + ); + await sendSolanaPage.setAmount('0'); + assert.equal( + await sendSolanaPage.check_validationErrorAppears( + 'Amount must be greater than 0', + ), + true, + 'Amount must be greater than 0 text is not displayed', + ); + }, + ); + }); +}); +describe.skip('Send full flow of USD', function (this: Suite) { + it('with a positive balance account', async function () { + // skipped due tohttps://consensyssoftware.atlassian.net/browse/SOL-100 + this.timeout(120000); + await withSolanaAccountSnap( + { + title: this.test?.fullTitle(), + showNativeTokenAsMainBalance: true, + mockCalls: true, + mockSendTransaction: true, + }, + async (driver) => { + await driver.refresh(); // workaround to not get an error due to https://consensyssoftware.atlassian.net/browse/SOL-87 + const homePage = new NonEvmHomepage(driver); + await homePage.check_pageIsLoaded(); + assert.equal( + await homePage.check_ifSendButtonIsClickable(), + true, + 'Send button is not enabled and it should', + ); + assert.equal( + await homePage.check_ifSwapButtonIsClickable(), + true, + 'Swap button is not enabled and it should', + ); + assert.equal( + await homePage.check_ifBridgeButtonIsClickable(), + true, + 'Bridge button is not enabled and it should', + ); + await homePage.clickOnSendButton(); + const sendSolanaPage = new SendSolanaPage(driver); + assert.equal( + await sendSolanaPage.isContinueButtonEnabled(), + false, + 'Continue button is enabled when no address nor amount', + ); + await sendSolanaPage.setToAddress(commonSolanaAddress); + assert.equal( + await sendSolanaPage.isContinueButtonEnabled(), + false, + 'Continue button is enabled when no address', + ); + await sendSolanaPage.clickOnSwapCurrencyButton(); + assert.equal( + await sendSolanaPage.isContinueButtonEnabled(), + false, + 'Continue button is enabled when no address nor amount', + ); + await sendSolanaPage.setAmount('0.1'); + const confirmSolanaPage = new ConfirmSolanaTxPage(driver); + await sendSolanaPage.clickOnContinue(); + assert.equal( + await confirmSolanaPage.checkAmountDisplayed('0.1', 'USD'), + true, + 'Check amount displayed is wrong', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('From'), + true, + 'From is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Amount'), + true, + 'Amount is not displayed and it should', + ); + + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Recipient'), + true, + 'Recipient is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Network'), + true, + 'Network is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed( + 'Transaction speed', + ), + true, + 'Transaction speed is not displayed and it should', + ); + + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Network fee'), + true, + 'Network fee is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Total'), + true, + 'Total is not displayed and it should', + ); + await confirmSolanaPage.clickOnSend(); + const sentTxPage = new SolanaTxresultPage(driver); + assert.equal( + await sentTxPage.check_TransactionStatusText('0.1', true), + true, + 'Transaction amount is not correct', + ); + assert.equal( + await sentTxPage.check_TransactionStatus(true), + true, + 'Transaction was not sent as expected', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('From'), + true, + 'From field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Amount'), + true, + 'Amount field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Recipient'), + true, + 'Recipient field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Network'), + true, + 'Network field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Transaction speed'), + true, + 'Transaction field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Network fee'), + true, + 'Network fee field not displayed', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Total'), + true, + 'Total field not displayed and it should', + ); + assert.equal( + await sentTxPage.check_isViewTransactionLinkDisplayed(), + true, + 'View transaction link is not displayed and it should', + ); + }, + ); + }); +}); +describe.skip('Send full flow of SOL', function (this: Suite) { + it('with a positive balance account', async function () { + this.timeout(120000); + await withSolanaAccountSnap( + { + title: this.test?.fullTitle(), + showNativeTokenAsMainBalance: true, + mockCalls: true, + mockSendTransaction: true, + }, + async (driver) => { + await driver.refresh(); // workaround to not get an error due to https://consensyssoftware.atlassian.net/browse/SOL-87 + const homePage = new NonEvmHomepage(driver); + await homePage.check_pageIsLoaded(); + assert.equal( + await homePage.check_ifSendButtonIsClickable(), + true, + 'Send button is not enabled and it should', + ); + assert.equal( + await homePage.check_ifSwapButtonIsClickable(), + true, + 'Swap button is not enabled and it should', + ); + assert.equal( + await homePage.check_ifBridgeButtonIsClickable(), + true, + 'Bridge button is not enabled and it should', + ); + await homePage.clickOnSendButton(); + const sendSolanaPage = new SendSolanaPage(driver); + assert.equal( + await sendSolanaPage.isContinueButtonEnabled(), + false, + 'Continue button is enabled when no address nor amount', + ); + await sendSolanaPage.setToAddress(commonSolanaAddress); + assert.equal( + await sendSolanaPage.isContinueButtonEnabled(), + false, + 'Continue button is enabled when no address', + ); + await sendSolanaPage.setAmount('0.1'); + const confirmSolanaPage = new ConfirmSolanaTxPage(driver); + await sendSolanaPage.clickOnContinue(); + assert.equal( + await confirmSolanaPage.checkAmountDisplayed('0.1'), + true, + 'Check amount displayed is wrong', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('From'), + true, + 'From is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Amount'), + true, + 'Amount is not displayed and it should', + ); + + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Recipient'), + true, + 'Recipient is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Network'), + true, + 'Network is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed( + 'Transaction speed', + ), + true, + 'Transaction speed is not displayed and it should', + ); + + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Network fee'), + true, + 'Network fee is not displayed and it should', + ); + assert.equal( + await confirmSolanaPage.isTransactionDetailDisplayed('Total'), + true, + 'Total is not displayed and it should', + ); + await confirmSolanaPage.clickOnSend(); + const sentTxPage = new SolanaTxresultPage(driver); + assert.equal( + await sentTxPage.check_TransactionStatusText('0.1', true), + true, + 'Transaction amount is not correct', + ); + assert.equal( + await sentTxPage.check_TransactionStatus(true), + true, + 'Transaction was not sent as expected', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('From'), + true, + 'From field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Amount'), + true, + 'Amount field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Recipient'), + true, + 'Recipient field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Network'), + true, + 'Network field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Transaction speed'), + true, + 'Transaction field not displayed and it should', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Network fee'), + true, + 'Network fee field not displayed', + ); + assert.equal( + await sentTxPage.isTransactionDetailDisplayed('Total'), + true, + 'Total field not displayed and it should', + ); + assert.equal( + await sentTxPage.check_isViewTransactionLinkDisplayed(), + true, + 'View transaction link is not displayed and it should', + ); + }, + ); + }); +}); +describe.skip('Send flow flow', function (this: Suite) { + it('and Transaction fails', async function () { + this.timeout(120000); // there is a bug open for this big timeout https://consensyssoftware.atlassian.net/browse/SOL-90 + await withSolanaAccountSnap( + { + title: this.test?.fullTitle(), + showNativeTokenAsMainBalance: true, + mockCalls: true, + mockSendTransaction: false, + }, + async (driver) => { + await driver.refresh(); // workaround to not get an error due to https://consensyssoftware.atlassian.net/browse/SOL-87 + const homePage = new NonEvmHomepage(driver); + assert.equal( + await homePage.check_ifSendButtonIsClickable(), + true, + 'Send button is not enabled and it should', + ); + assert.equal( + await homePage.check_ifSwapButtonIsClickable(), + true, + 'Swap button is not enabled and it should', + ); + assert.equal( + await homePage.check_ifBridgeButtonIsClickable(), + true, + 'Bridge button is not enabled and it should', + ); + await homePage.clickOnSendButton(); + const sendSolanaPage = new SendSolanaPage(driver); + await sendSolanaPage.setToAddress(commonSolanaAddress); + await sendSolanaPage.setAmount('0.1'); + // assert.equal(await sendSolanaPage.isContinueButtonEnabled(), true, "Continue button is not enabled when address and amount are set"); + await sendSolanaPage.clickOnContinue(); + const confirmSolanaPage = new ConfirmSolanaTxPage(driver); + + await confirmSolanaPage.clickOnSend(); + const failedTxPage = new SolanaTxresultPage(driver); + assert.equal( + await failedTxPage.check_TransactionStatusText('0.1', false), + true, + 'Transaction amount is not correct', + ); + assert.equal( + await failedTxPage.check_TransactionStatus(false), + true, + 'Transaction did not fail as expected', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('From'), + true, + 'From field not displayed and it should', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('Amount'), + true, + 'Amount field not displayed and it should', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('Recipient'), + true, + 'Recipient field not displayed and it should', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('Network'), + true, + 'Network field not displayed and it should', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('Transaction speed'), + true, + 'Transaction field not displayed and it should', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('Network fee'), + true, + 'Network fee field not displayed and it should', + ); + assert.equal( + await failedTxPage.isTransactionDetailDisplayed('Total'), + true, + 'Total field not displayed and it should', + ); + }, + ); + }); +}); diff --git a/test/e2e/flask/solana/solana-eth-networks.spec.ts b/test/e2e/flask/solana/solana-eth-networks.spec.ts new file mode 100644 index 000000000000..56a68135fad8 --- /dev/null +++ b/test/e2e/flask/solana/solana-eth-networks.spec.ts @@ -0,0 +1,24 @@ +import { Suite } from 'mocha'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import { withSolanaAccountSnap } from './common-solana'; + +describe('Solana/Evm accounts', function (this: Suite) { + it('Network picker is disabled when Solana account is selected', async function () { + await withSolanaAccountSnap( + { title: this.test?.fullTitle() }, + async (driver) => { + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Solana 1'); + await headerNavbar.check_currentSelectedNetwork('Solana'); + await headerNavbar.check_ifNetworkPickerClickable(false); + await headerNavbar.openAccountMenu(); + const accountMenu = new AccountListPage(driver); + await accountMenu.switchToAccount('Account 1'); + await headerNavbar.check_currentSelectedNetwork('Localhost 8545'); + await headerNavbar.check_ifNetworkPickerClickable(true); + }, + ); + }); +}); diff --git a/test/e2e/flask/solana/switching-network-accounts.spec.ts b/test/e2e/flask/solana/switching-network-accounts.spec.ts new file mode 100644 index 000000000000..f630495ca705 --- /dev/null +++ b/test/e2e/flask/solana/switching-network-accounts.spec.ts @@ -0,0 +1,24 @@ +import { Suite } from 'mocha'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import { withSolanaAccountSnap } from './common-solana'; + +describe('Switching between account from different networks', function (this: Suite) { + it('Switch from Solana account to another Network account', async function () { + await withSolanaAccountSnap( + { title: this.test?.fullTitle() }, + async (driver) => { + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Solana 1'); + await headerNavbar.check_ifNetworkPickerClickable(false); + await headerNavbar.check_currentSelectedNetwork('Solana'); + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.selectAccount('Account 1'); + await headerNavbar.check_ifNetworkPickerClickable(true); + await headerNavbar.check_currentSelectedNetwork('Localhost 8545'); + }, + ); + }); +}); diff --git a/test/e2e/flask/user-operations.spec.ts b/test/e2e/flask/user-operations.spec.ts index 7512e0b563c9..4e8b51043583 100644 --- a/test/e2e/flask/user-operations.spec.ts +++ b/test/e2e/flask/user-operations.spec.ts @@ -24,6 +24,7 @@ import { Driver } from '../webdriver/driver'; import { Bundler } from '../bundler'; import { SWAP_TEST_ETH_USDC_TRADES_MOCK } from '../../data/mock-data'; import { Mockttp } from '../mock-e2e'; +import TestDapp from '../page-objects/pages/test-dapp'; enum TransactionDetailRowIndex { Nonce = 0, @@ -204,9 +205,7 @@ async function withAccountSnap( ) { await withFixtures( { - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), + fixtures: new FixtureBuilder().build(), title, useBundler: true, usePaymaster: Boolean(paymaster), @@ -239,7 +238,10 @@ async function withAccountSnap( ERC_4337_ACCOUNT_SALT, ); - await driver.closeWindow(); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.connectAccount({ publicAddress: ERC_4337_ACCOUNT }); + await driver.switchToWindowWithTitle( WINDOW_TITLES.ExtensionInFullScreenView, ); diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 75eefb12f7c1..1301b111cc24 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -4,6 +4,7 @@ const BigNumber = require('bignumber.js'); const mockttp = require('mockttp'); const detectPort = require('detect-port'); const { difference } = require('lodash'); +const WebSocket = require('ws'); const createStaticServer = require('../../development/create-static-server'); const { setupMocking } = require('./mock-e2e'); const { Ganache } = require('./seeder/ganache'); @@ -184,7 +185,7 @@ async function withFixtures(options, testSuite) { } await mockServer.start(8000); - setManifestFlags(manifestFlags); + await setManifestFlags(manifestFlags); driver = (await buildWebDriver(driverOptions)).driver; webDriver = driver.driver; @@ -632,14 +633,51 @@ async function unlockWallet( await driver.navigate(); } + await driver.waitForSelector('#password', { state: 'enabled' }); await driver.fill('#password', password); await driver.press('#password', driver.Key.ENTER); - if (waitLoginSuccess) { await driver.assertElementNotPresent('[data-testid="unlock-page"]'); } } +/** + * Simulates a WebSocket connection by executing a script in the browser context. + * + * @param {WebDriver} driver - The WebDriver instance. + * @param {string} hostname - The hostname to connect to. + */ +async function createWebSocketConnection(driver, hostname) { + try { + await driver.executeScript(async (wsHostname) => { + const url = `ws://${wsHostname}:8000`; + const socket = new WebSocket(url); + socket.onopen = () => { + console.log('WebSocket connection opened'); + socket.send('Hello, server!'); + }; + socket.onerror = (error) => { + console.error( + 'WebSocket error:', + error.message || 'Connection blocked', + ); + }; + socket.onmessage = (event) => { + console.log('Message received from server:', event.data); + }; + socket.onclose = () => { + console.log('WebSocket connection closed'); + }; + }, hostname); + } catch (error) { + console.error( + `Failed to execute WebSocket connection script for ws://${hostname}:8000`, + error, + ); + throw error; + } +} + const logInWithBalanceValidation = async (driver, ganacheServer) => { await unlockWallet(driver); // Wait for balance to load @@ -677,36 +715,19 @@ function genRandInitBal(minETHBal = 10, maxETHBal = 100, decimalPlaces = 4) { * @param {WebDriver} options.driver - The WebDriver instance controlling the browser. * @param {boolean} [options.snapSigInsights] - Whether to wait for the insights snap to be ready before clicking the sign button. */ -async function clickSignOnSignatureConfirmation({ +async function clickSignOnRedesignedSignatureConfirmation({ driver, snapSigInsights = false, }) { + await driver.clickElementSafe('.confirm-scroll-to-bottom__button'); + if (snapSigInsights) { // there is no condition we can wait for to know the snap is ready, // so we have to add a small delay as the last alternative to avoid flakiness. - await driver.delay(regularDelayMs); + await driver.delay(largeDelayMs); } - await driver.clickElement({ text: 'Sign', tag: 'button' }); -} - -/** - * Some signing methods have extra security that requires the user to click a - * button to validate that they have verified the details. This method handles - * performing the necessary steps to click that button. - * - * @param {WebDriver} driver - */ -async function validateContractDetails(driver) { - const verifyDetailsBtnSelector = - '.signature-request-content__verify-contract-details'; - - await driver.clickElement(verifyDetailsBtnSelector); - await driver.clickElement({ text: 'Got it', tag: 'button' }); - - await driver.clickElementSafe( - '[data-testid="signature-request-scroll-button"]', - ); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); } /** @@ -723,25 +744,32 @@ async function switchToNotificationWindow(driver) { * mockServer method, this method will allow getting all of the seen requests * for each mock in the array. * - * @param {WebDriver} driver - * @param {import('mockttp').MockedEndpoint[]} mockedEndpoints - * @param {boolean} hasRequest + * @param {WebDriver} driver - The WebDriver instance. + * @param {import('mockttp').MockedEndpoint[]} mockedEndpoints - mockttp mocked endpoints + * @param {boolean} [waitWhilePending] - Wait until no requests are pending * @returns {Promise} */ -async function getEventPayloads(driver, mockedEndpoints, hasRequest = true) { - await driver.wait( - async () => { - let isPending = true; - - for (const mockedEndpoint of mockedEndpoints) { - isPending = await mockedEndpoint.isPending(); - } +async function getEventPayloads( + driver, + mockedEndpoints, + waitWhilePending = true, +) { + if (waitWhilePending) { + await driver.wait( + async () => { + const pendingStatuses = await Promise.all( + mockedEndpoints.map((mockedEndpoint) => mockedEndpoint.isPending()), + ); + const isSomethingPending = pendingStatuses.some( + (pendingStatus) => pendingStatus, + ); - return isPending === !hasRequest; - }, - driver.timeout, - true, - ); + return !isSomethingPending; + }, + driver.timeout, + true, + ); + } const mockedRequests = []; for (const mockedEndpoint of mockedEndpoints) { mockedRequests.push(...(await mockedEndpoint.getSeenRequests())); @@ -822,81 +850,6 @@ async function initBundler(bundlerServer, ganacheServer, usePaymaster) { } } -/** - * Rather than using the FixtureBuilder#withPreferencesController to set the setting - * we need to manually set the setting because the migration #122 overrides this. - * We should be able to remove this when we delete the redesignedConfirmationsEnabled setting. - * - * @param driver - */ -async function tempToggleSettingRedesignedConfirmations(driver) { - // Ensure we are on the extension window - await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); - - // Open settings menu button - await driver.clickElement('[data-testid="account-options-menu-button"]'); - - // fix race condition with mmi build - if (process.env.MMI) { - await driver.waitForSelector('[data-testid="global-menu-mmi-portfolio"]'); - } - - // Click settings from dropdown menu - await driver.clickElement('[data-testid="global-menu-settings"]'); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Click redesignedConfirmationsEnabled toggle - await driver.clickElement( - '[data-testid="toggle-redesigned-confirmations-container"]', - ); -} - -/** - * Rather than using the FixtureBuilder#withPreferencesController to set the setting - * we need to manually set the setting because the migration #132 overrides this. - * We should be able to remove this when we delete the redesignedTransactionsEnabled setting. - * - * @param driver - */ -async function tempToggleSettingRedesignedTransactionConfirmations(driver) { - // Ensure we are on the extension window - await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); - - // Open settings menu button - await driver.clickElement('[data-testid="account-options-menu-button"]'); - - // fix race condition with mmi build - if (process.env.MMI) { - await driver.waitForSelector('[data-testid="global-menu-mmi-portfolio"]'); - } - - // Click settings from dropdown menu - await driver.clickElement('[data-testid="global-menu-settings"]'); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Click redesigned transactions toggle - await driver.clickElement( - '[data-testid="toggle-redesigned-transactions-container"]', - ); - - // Close settings page - await driver.clickElement( - '.settings-page__header__title-container__close-button', - ); -} - /** * Opens the account options menu safely, handling potential race conditions * with the MMI build. @@ -954,8 +907,7 @@ module.exports = { convertETHToHexGwei, roundToXDecimalPlaces, generateRandNumBetween, - clickSignOnSignatureConfirmation, - validateContractDetails, + clickSignOnRedesignedSignatureConfirmation, switchToNotificationWindow, getEventPayloads, assertInAnyOrder, @@ -964,8 +916,7 @@ module.exports = { getCleanAppState, editGasFeeForm, clickNestedButton, - tempToggleSettingRedesignedConfirmations, - tempToggleSettingRedesignedTransactionConfirmations, openMenuSafe, sentryRegEx, + createWebSocketConnection, }; diff --git a/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts b/test/e2e/helpers/identity/user-storage/userStorageMockttpController.test.ts similarity index 100% rename from test/e2e/helpers/user-storage/userStorageMockttpController.test.ts rename to test/e2e/helpers/identity/user-storage/userStorageMockttpController.test.ts diff --git a/test/e2e/helpers/user-storage/userStorageMockttpController.ts b/test/e2e/helpers/identity/user-storage/userStorageMockttpController.ts similarity index 100% rename from test/e2e/helpers/user-storage/userStorageMockttpController.ts rename to test/e2e/helpers/identity/user-storage/userStorageMockttpController.ts diff --git a/test/e2e/json-rpc/eth_accounts.spec.ts b/test/e2e/json-rpc/eth_accounts.spec.ts index 149021d40a57..c4928dc04c3b 100644 --- a/test/e2e/json-rpc/eth_accounts.spec.ts +++ b/test/e2e/json-rpc/eth_accounts.spec.ts @@ -14,7 +14,7 @@ describe('eth_accounts', function () { .withKeyringControllerAdditionalAccountVault() .withPreferencesControllerAdditionalAccountIdentities() .withAccountsControllerAdditionalAccountIdentities() - .withPermissionControllerConnectedToTestDapp() + .withPermissionControllerConnectedToTestDappWithTwoAccounts() .build(), ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), diff --git a/test/e2e/json-rpc/eth_sendTransaction.spec.js b/test/e2e/json-rpc/eth_sendTransaction.spec.js index ed740d17bfc1..6d135683c3ad 100644 --- a/test/e2e/json-rpc/eth_sendTransaction.spec.js +++ b/test/e2e/json-rpc/eth_sendTransaction.spec.js @@ -4,13 +4,13 @@ const { unlockWallet, WINDOW_TITLES, generateGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('eth_sendTransaction', function () { const expectedHash = '0x855951a65dcf5949dc54beb032adfb604c52a0a548a0f616799d6873a9521470'; + it('confirms a new transaction', async function () { await withFixtures( { @@ -56,6 +56,7 @@ describe('eth_sendTransaction', function () { }, ); }); + it('rejects a new transaction', async function () { await withFixtures( { @@ -69,8 +70,6 @@ describe('eth_sendTransaction', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // eth_sendTransaction await driver.openNewPage(`http://127.0.0.1:8080`); const request = JSON.stringify({ @@ -94,7 +93,7 @@ describe('eth_sendTransaction', function () { // reject transaction in mm popup await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); await driver.switchToWindowWithTitle('E2E Test Dapp'); const result = await driver .executeScript(`return window.transactionHash;`) diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js index 6a8576b3f136..77c1589411ac 100644 --- a/test/e2e/json-rpc/switchEthereumChain.spec.js +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -8,669 +8,412 @@ const { unlockWallet, switchToNotificationWindow, WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { isManifestV3 } = require('../../../shared/modules/mv3.utils'); describe('Switch Ethereum Chain for two dapps', function () { - describe('Old confirmation screens', function () { - it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], - }, - title: this.test.fullTitle(), + it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - const dappOne = await openDapp(driver, undefined, DAPP_URL); - - // Connect Dapp One - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch and connect Dapp Two - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Click the edit button for networks - await editButtons[1].click(); - - // Disconnect Mainnet - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Switch to notification of switchEthereumChain - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - - // Switch back to dapp one - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Initiate send tx on dapp one - await driver.clickElement('#sendButton'); - await driver.delay(2000); - - // Switch to notification that should still be switchEthereumChain request but with an warning. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // THIS IS BROKEN - // await driver.findElement({ - // span: 'span', - // text: 'Switching networks will cancel all pending confirmations', - // }); - - // Cancel switchEthereumChain with queued pending tx - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Delay for second notification of the pending tx - await driver.delay(1000); - - // Switch to new pending tx notification - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findElement({ - text: 'Sending ETH', - tag: 'span', - }); - - // Confirm pending tx - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - }, - ); - }); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + // open two dapps + const dappOne = await openDapp(driver, undefined, DAPP_URL); + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Confirm switchEthereumChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Switch to Dapp One + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Wait for chain id element to change, there's a page reload. + await driver.waitForSelector({ + css: '#chainId', + text: '0x53a', + }); + + // Dapp One ChainId assertion + await driver.findElement({ css: '#chainId', text: '0x53a' }); + + // Switch to Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // Dapp Two ChainId Assertion + await driver.findElement({ css: '#chainId', text: '0x53a' }); + }, + ); }); - describe('Redesigned confirmation screens', function () { - it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappOne = await openDapp(driver, undefined, DAPP_URL); - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Confirm switchEthereumChain - await switchToNotificationWindow(driver, 4); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Switch to Dapp One - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Wait for chain id element to change, there's a page reload. - await driver.waitForSelector({ - css: '#chainId', - text: '0x53a', - }); - - // Dapp One ChainId assertion - await driver.findElement({ css: '#chainId', text: '0x53a' }); - - // Switch to Dapp Two - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // Dapp Two ChainId Assertion - await driver.findElement({ css: '#chainId', text: '0x53a' }); - }, - ); - }); - - it('queues switchEthereumChain request from second dapp after send tx request', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - await openDapp(driver, undefined, DAPP_URL); - await openDapp(driver, undefined, DAPP_ONE_URL); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch to Dapp One and connect it - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findClickableElement({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - await editButtons[1].click(); - - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch to Dapp Two - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - // Initiate send transaction on Dapp two - await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - - // Switch to Dapp One - await driver.switchToWindowWithUrl(DAPP_URL); - - // Switch Ethereum chain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - await switchToNotificationWindow(driver, 4); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - // Delay here after notification for second notification popup for switchEthereumChain - await driver.delay(1000); - - // Switch and confirm to queued notification for switchEthereumChain - await switchToNotificationWindow(driver, 4); - - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ css: '#chainId', text: '0x539' }); - }, - ); - }); - - it('queues send tx after switchEthereum request with a warning, confirming removes pending tx', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], - }, - title: this.test.fullTitle(), + it('queues switchEthereumChain request from second dapp after send tx request', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerSmartTransactionsOptedOut() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - const dappOne = await openDapp(driver, undefined, DAPP_URL); - - // Connect Dapp One - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch and connect Dapp Two - - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Click the edit button for networks - await editButtons[1].click(); - - // Disconnect Mainnet - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - // Switch back to dapp one - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Initiate send tx on dapp one - await driver.clickElement('#sendButton'); - await driver.delay(2000); - - // Switch to notification that should still be switchEthereumChain request but with a warning. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // THIS IS BROKEN - // await driver.findElement({ - // span: 'span', - // text: 'Switching networks will cancel all pending confirmations', - // }); - - // Confirm switchEthereumChain with queued pending tx - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document - // if this is an MV3 build(3 or 4 total) - await driver.wait(async () => { - const windowHandles = await driver.getAllWindowHandles(); - const numberOfWindowHandlesToExpect = isManifestV3 ? 4 : 3; - return windowHandles.length === numberOfWindowHandlesToExpect; - }); - }, - ); - }); - - it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], - }, - title: this.test.fullTitle(), + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch to Dapp One and connect it + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findClickableElement({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); + + await editButtons[1].click(); + + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement('[data-testid="connect-more-chains-button"]'); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch to Dapp Two + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + // Initiate send transaction on Dapp two + await driver.clickElement('#sendButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + + // Switch to Dapp One + await driver.switchToWindowWithUrl(DAPP_URL); + + // Switch Ethereum chain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + // Delay here after notification for second notification popup for switchEthereumChain + await driver.delay(1000); + + // Switch and confirm to queued notification for switchEthereumChain + await switchToNotificationWindow(driver, 4); + + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ css: '#chainId', text: '0x539' }); + }, + ); + }); + + it('queues send tx after switchEthereum request with a warning, confirming removes pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - const dappOne = await openDapp(driver, undefined, DAPP_URL); - - // Connect Dapp One - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch and connect Dapp Two - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Click the edit button for networks - await editButtons[1].click(); - - // Disconnect Mainnet - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Switch to notification of switchEthereumChain - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - - // Switch back to dapp one - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Initiate send tx on dapp one - await driver.clickElement('#sendButton'); - await driver.delay(2000); - - // Switch to notification that should still be switchEthereumChain request but with an warning. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Cancel switchEthereumChain with queued pending tx - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Delay for second notification of the pending tx - await driver.delay(1000); - - // Switch to new pending tx notification - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findElement({ - text: 'Transfer request', - tag: 'h3', - }); - - await driver.findElement({ - text: '0 ETH', - tag: 'h2', - }); - - // Confirm pending tx - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // open two dapps + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); + + // Connect Dapp One + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch and connect Dapp Two + + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); + + // Click the edit button for networks + await editButtons[1].click(); + + // Disconnect Mainnet + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement('[data-testid="connect-more-chains-button"]'); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + // Switch back to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + await driver.delay(2000); + + // Switch to notification that should still be switchEthereumChain request but with a warning. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // THIS IS BROKEN + // await driver.findElement({ + // span: 'span', + // text: 'Switching networks will cancel all pending confirmations', + // }); + + // Confirm switchEthereumChain with queued pending tx + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document + // if this is an MV3 build(3 or 4 total) + await driver.wait(async () => { + const windowHandles = await driver.getAllWindowHandles(); + const numberOfWindowHandlesToExpect = isManifestV3 ? 4 : 3; + return windowHandles.length === numberOfWindowHandlesToExpect; + }); + }, + ); + }); + + it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], }, - ); - }); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // open two dapps + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); + + // Connect Dapp One + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch and connect Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const editButtons = await driver.findElements('[data-testid="edit"]'); + + // Click the edit button for networks + await editButtons[1].click(); + + // Disconnect Mainnet + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement('[data-testid="connect-more-chains-button"]'); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + + // Switch back to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + await driver.delay(2000); + + // Switch to notification that should still be switchEthereumChain request but with an warning. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Cancel switchEthereumChain with queued pending tx + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // Delay for second notification of the pending tx + await driver.delay(1000); + + // Switch to new pending tx notification + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findElement({ + text: 'Transfer request', + tag: 'h3', + }); + + await driver.findElement({ + text: '0 ETH', + tag: 'h2', + }); + + // Confirm pending tx + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + }, + ); }); }); diff --git a/test/e2e/json-rpc/wallet_requestPermissions.spec.js b/test/e2e/json-rpc/wallet_requestPermissions.spec.js deleted file mode 100644 index 5484fdf73d80..000000000000 --- a/test/e2e/json-rpc/wallet_requestPermissions.spec.js +++ /dev/null @@ -1,59 +0,0 @@ -const { strict: assert } = require('assert'); -const { - defaultGanacheOptions, - withFixtures, - switchToNotificationWindow, - switchToOrOpenDapp, - unlockWallet, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -describe('wallet_requestPermissions', function () { - it('executes a request permissions on eth_accounts event', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - }, - async ({ driver }) => { - await unlockWallet(driver); - - // wallet_requestPermissions - await driver.openNewPage(`http://127.0.0.1:8080`); - - const requestPermissionsRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_requestPermissions', - params: [{ eth_accounts: {} }], - }); - - await driver.executeScript( - `window.ethereum.request(${requestPermissionsRequest})`, - ); - - await switchToNotificationWindow(driver); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await switchToOrOpenDapp(driver); - - const getPermissionsRequest = JSON.stringify({ - method: 'wallet_getPermissions', - }); - - const getPermissions = await driver.executeScript( - `return window.ethereum.request(${getPermissionsRequest})`, - ); - - assert.strictEqual(getPermissions[0].parentCapability, 'eth_accounts'); - }, - ); - }); -}); diff --git a/test/e2e/json-rpc/wallet_requestPermissions.spec.ts b/test/e2e/json-rpc/wallet_requestPermissions.spec.ts new file mode 100644 index 000000000000..f06d28a1609d --- /dev/null +++ b/test/e2e/json-rpc/wallet_requestPermissions.spec.ts @@ -0,0 +1,54 @@ +import { strict as assert } from 'assert'; +import { PermissionConstraint } from '@metamask/permission-controller'; +import { withFixtures } from '../helpers'; +import FixtureBuilder from '../fixture-builder'; +import { loginWithBalanceValidation } from '../page-objects/flows/login.flow'; +import TestDapp from '../page-objects/pages/test-dapp'; + +describe('wallet_requestPermissions', function () { + it('executes a request permissions on eth_accounts event', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + title: this.test?.title, + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + + // wallet_requestPermissions + const requestPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {} }], + }); + await driver.executeScript( + `window.ethereum.request(${requestPermissionsRequest})`, + ); + + // confirm connect account + await testDapp.confirmConnectAccountModal(); + + const getPermissionsRequest = JSON.stringify({ + method: 'wallet_getPermissions', + }); + const getPermissions = await driver.executeScript( + `return window.ethereum.request(${getPermissionsRequest})`, + ); + + const grantedPermissionNames = getPermissions + .map( + (permission: PermissionConstraint) => permission.parentCapability, + ) + .sort(); + + assert.deepStrictEqual(grantedPermissionNames, [ + 'endowment:permitted-chains', + 'eth_accounts', + ]); + }, + ); + }); +}); diff --git a/test/e2e/json-rpc/wallet_revokePermissions.spec.js b/test/e2e/json-rpc/wallet_revokePermissions.spec.js deleted file mode 100644 index 70d2fd3ba572..000000000000 --- a/test/e2e/json-rpc/wallet_revokePermissions.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -const { strict: assert } = require('assert'); - -const { - withFixtures, - defaultGanacheOptions, - unlockWallet, - openDapp, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -describe('Revoke Dapp Permissions', function () { - it('should revoke dapp permissions ', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await openDapp(driver); - - await driver.findElement({ - text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - css: '#accounts', - }); - - // wallet_revokePermissions request - const revokePermissionsRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_revokePermissions', - params: [ - { - eth_accounts: {}, - }, - ], - }); - - const result = await driver.executeScript( - `return window.ethereum.request(${revokePermissionsRequest})`, - ); - - // Response of method call - assert.deepEqual(result, null); - - // TODO: Fix having to reload dapp as it is not the proper behavior in production, issue with test setup. - await driver.executeScript(`window.location.reload()`); - - // You cannot use driver.findElement() with an empty string, so use xpath - await driver.findElement({ xpath: '//span[@id="accounts"][.=""]' }); - }, - ); - }); -}); diff --git a/test/e2e/json-rpc/wallet_revokePermissions.spec.ts b/test/e2e/json-rpc/wallet_revokePermissions.spec.ts new file mode 100644 index 000000000000..d1ac0d39f580 --- /dev/null +++ b/test/e2e/json-rpc/wallet_revokePermissions.spec.ts @@ -0,0 +1,183 @@ +import { strict as assert } from 'assert'; +import { PermissionConstraint } from '@metamask/permission-controller'; +import { withFixtures } from '../helpers'; +import FixtureBuilder from '../fixture-builder'; +import TestDapp from '../page-objects/pages/test-dapp'; +import { loginWithBalanceValidation } from '../page-objects/flows/login.flow'; + +describe('Revoke Dapp Permissions', function () { + it('should revoke "eth_accounts" and "endowment:permitted-chains" when the dapp revokes permissions for just "eth_accounts"', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDappWithChain() + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + + const beforeGetPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_getPermissions', + }); + const beforeGetPermissionsResult = await driver.executeScript( + `return window.ethereum.request(${beforeGetPermissionsRequest})`, + ); + const beforeGetPermissionsNames = beforeGetPermissionsResult.map( + (permission: PermissionConstraint) => permission.parentCapability, + ); + assert.deepEqual(beforeGetPermissionsNames, [ + 'eth_accounts', + 'endowment:permitted-chains', + ]); + + const revokePermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_revokePermissions', + params: [ + { + eth_accounts: {}, + }, + ], + }); + const revokePermissionsResult = await driver.executeScript( + `return window.ethereum.request(${revokePermissionsRequest})`, + ); + assert.deepEqual(revokePermissionsResult, null); + + const afterGetPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_getPermissions', + }); + const afterGetPermissionsResult = await driver.executeScript( + `return window.ethereum.request(${afterGetPermissionsRequest})`, + ); + const afterGetPermissionsNames = afterGetPermissionsResult.map( + (permission: PermissionConstraint) => permission.parentCapability, + ); + assert.deepEqual(afterGetPermissionsNames, []); + }, + ); + }); + + it('should revoke "eth_accounts" and "endowment:permitted-chains" when the dapp revokes permissions for just "endowment:permitted-chains"', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDappWithChain() + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + + const beforeGetPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_getPermissions', + }); + const beforeGetPermissionsResult = await driver.executeScript( + `return window.ethereum.request(${beforeGetPermissionsRequest})`, + ); + const beforeGetPermissionsNames = beforeGetPermissionsResult.map( + (permission: PermissionConstraint) => permission.parentCapability, + ); + assert.deepEqual(beforeGetPermissionsNames, [ + 'eth_accounts', + 'endowment:permitted-chains', + ]); + + const revokePermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_revokePermissions', + params: [ + { + 'endowment:permitted-chains': {}, + }, + ], + }); + const revokePermissionsResult = await driver.executeScript( + `return window.ethereum.request(${revokePermissionsRequest})`, + ); + assert.deepEqual(revokePermissionsResult, null); + + const afterGetPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_getPermissions', + }); + const afterGetPermissionsResult = await driver.executeScript( + `return window.ethereum.request(${afterGetPermissionsRequest})`, + ); + const afterGetPermissionsNames = afterGetPermissionsResult.map( + (permission: PermissionConstraint) => permission.parentCapability, + ); + assert.deepEqual(afterGetPermissionsNames, []); + }, + ); + }); + + it('should revoke "eth_accounts" and "endowment:permitted-chains" when the dapp revokes permissions for "eth_accounts" and "endowment:permitted-chains"', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDappWithChain() + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + + const beforeGetPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_getPermissions', + }); + const beforeGetPermissionsResult = await driver.executeScript( + `return window.ethereum.request(${beforeGetPermissionsRequest})`, + ); + const beforeGetPermissionsNames = beforeGetPermissionsResult.map( + (permission: PermissionConstraint) => permission.parentCapability, + ); + assert.deepEqual(beforeGetPermissionsNames, [ + 'eth_accounts', + 'endowment:permitted-chains', + ]); + + const revokePermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_revokePermissions', + params: [ + { + eth_accounts: {}, + 'endowment:permitted-chains': {}, + }, + ], + }); + const revokePermissionsResult = await driver.executeScript( + `return window.ethereum.request(${revokePermissionsRequest})`, + ); + assert.deepEqual(revokePermissionsResult, null); + + const afterGetPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_getPermissions', + }); + const afterGetPermissionsResult = await driver.executeScript( + `return window.ethereum.request(${afterGetPermissionsRequest})`, + ); + const afterGetPermissionsNames = afterGetPermissionsResult.map( + (permission: PermissionConstraint) => permission.parentCapability, + ); + assert.deepEqual(afterGetPermissionsNames, []); + }, + ); + }); +}); diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index fc6b1ea4397a..d6d19d972d3b 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -50,6 +50,7 @@ const { mockEmptyStalelistAndHotlist, } = require('./tests/phishing-controller/mocks'); const { mockNotificationServices } = require('./tests/notifications/mocks'); +const { mockIdentityServices } = require('./tests/identity/mocks'); const emptyHtmlPage = () => ` @@ -82,7 +83,11 @@ const browserAPIRequestDomains = */ const privateHostMatchers = [ // { pattern: RegExp, host: string } - { pattern: /^.*\.btc.*\.quiknode.pro$/iu, host: '*.btc*.quiknode.pro' }, + { pattern: /^.*\.btc.*\.quiknode\.pro$/iu, host: '*.btc*.quiknode.pro' }, + { + pattern: /^.*-solana.*-.*\.mainnet\.rpcpool\.com/iu, + host: '*solana*.mainnet.rpcpool.com', + }, ]; /** @@ -125,7 +130,6 @@ async function setupMocking( }); const mockedEndpoint = await testSpecificMock(server); - // Mocks below this line can be overridden by test-specific mocks // Account link @@ -732,12 +736,53 @@ async function setupMocking( // Notification APIs await mockNotificationServices(server); + // Identity APIs + await mockIdentityServices(server); + await server.forGet(/^https:\/\/sourcify.dev\/(.*)/u).thenCallback(() => { return { statusCode: 404, }; }); + // remote feature flags + await server + .forGet('https://client-config.api.cx.metamask.io/v1/flags') + .withQuery({ + client: 'extension', + distribution: 'main', + environment: 'dev', + }) + .thenCallback(() => { + return { + ok: true, + statusCode: 200, + json: [ + { feature1: true }, + { feature2: false }, + { + feature3: [ + { + value: 'valueA', + name: 'groupA', + scope: { type: 'threshold', value: 0.3 }, + }, + { + value: 'valueB', + name: 'groupB', + scope: { type: 'threshold', value: 0.5 }, + }, + { + scope: { type: 'threshold', value: 1 }, + value: 'valueC', + name: 'groupC', + }, + ], + }, + ], + }; + }); + /** * Returns an array of alphanumerically sorted hostnames that were requested * during the current test suite. diff --git a/test/e2e/mv3-perf-stats/bundle-size.js b/test/e2e/mv3-perf-stats/bundle-size.js index d37ec561bde5..b6580d86538c 100755 --- a/test/e2e/mv3-perf-stats/bundle-size.js +++ b/test/e2e/mv3-perf-stats/bundle-size.js @@ -37,7 +37,7 @@ const UIFileRegex = /ui-[0-9]*.js/u; async function main() { const { argv } = yargs(hideBin(process.argv)).usage( '$0 [options]', - 'Run a page load benchmark', + 'Capture bundle size stats', (_yargs) => _yargs.option('out', { description: diff --git a/test/e2e/mv3-perf-stats/index.js b/test/e2e/mv3-perf-stats/index.js deleted file mode 100644 index 4e56a2385a67..000000000000 --- a/test/e2e/mv3-perf-stats/index.js +++ /dev/null @@ -1,2 +0,0 @@ -require('./init-load-stats'); -require('./bundle-size'); diff --git a/test/e2e/mv3-perf-stats/init-load-stats.js b/test/e2e/mv3-perf-stats/init-load-stats.js deleted file mode 100755 index 40584343d990..000000000000 --- a/test/e2e/mv3-perf-stats/init-load-stats.js +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env node - -/* eslint-disable node/shebang */ -const path = require('path'); -const { promises: fs } = require('fs'); -const yargs = require('yargs/yargs'); -const { hideBin } = require('yargs/helpers'); - -const { exitWithError } = require('../../../development/lib/exit-with-error'); -const { - isWritable, - getFirstParentDirectoryThatExists, -} = require('../../helpers/file'); -const { withFixtures, tinyDelayMs } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -/** - * The e2e test case is used to capture load and initialisation time statistics for extension in MV3 environment. - */ - -async function profilePageLoad() { - const parsedLogs = {}; - try { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - disableServerMochaToBackground: true, - }, - async ({ driver }) => { - await driver.delay(tinyDelayMs); - await driver.navigate(); - await driver.delay(1000); - const logs = await driver.checkBrowserForLavamoatLogs(); - - let logString = ''; - let logType = ''; - - logs.forEach((log) => { - if (log.indexOf('"version": 1') >= 0) { - // log end here - logString += log; - parsedLogs[logType] = JSON.parse(`{${logString}}`); - logString = ''; - logType = ''; - } else if (logType) { - // log string continues - logString += log; - } else if ( - log.search(/"name": ".*app\/scripts\/background.js",/u) >= 0 - ) { - // background log starts - logString += log; - logType = 'background'; - } else if (log.search(/"name": ".*app\/scripts\/ui.js",/u) >= 0) { - // ui log starts - logString += log; - logType = 'ui'; - } else if (log.search(/"name": "Total"/u) >= 0) { - // load time log starts - logString += log; - logType = 'loadTime'; - } - }); - }, - ); - } catch (error) { - console.log('Error in trying to parse logs.'); - } - return parsedLogs; -} - -async function main() { - const { argv } = yargs(hideBin(process.argv)).usage( - '$0 [options]', - 'Run a page load benchmark', - (_yargs) => - _yargs.option('out', { - description: - 'Output filename. Output printed to STDOUT of this is omitted.', - type: 'string', - normalize: true, - }), - ); - - const results = await profilePageLoad(); - const { out } = argv; - - const logCategories = [ - { key: 'background', dirPath: 'initialisation/background/stacks.json' }, - { key: 'ui', dirPath: 'initialisation/ui/stacks.json' }, - { key: 'loadTime', dirPath: 'load_time/stats.json' }, - ]; - - if (out) { - logCategories.forEach(async ({ key, dirPath }) => { - if (results[key]) { - const outPath = `${out}/${dirPath}`; - const outputDirectory = path.dirname(outPath); - const existingParentDirectory = await getFirstParentDirectoryThatExists( - outputDirectory, - ); - if (!(await isWritable(existingParentDirectory))) { - throw new Error('Specified output file directory is not writable'); - } - if (outputDirectory !== existingParentDirectory) { - await fs.mkdir(outputDirectory, { recursive: true }); - } - await fs.writeFile(outPath, JSON.stringify(results[key], null, 2)); - } - }); - } else { - console.log(JSON.stringify(results, null, 2)); - } -} - -main().catch((error) => { - exitWithError(error); -}); diff --git a/test/e2e/mv3-stats.js b/test/e2e/mv3-stats.js deleted file mode 100755 index 2dd24791e9a5..000000000000 --- a/test/e2e/mv3-stats.js +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env node - -/* eslint-disable node/shebang */ -const path = require('path'); -const { promises: fs } = require('fs'); -const yargs = require('yargs/yargs'); -const { hideBin } = require('yargs/helpers'); - -const { exitWithError } = require('../../development/lib/exit-with-error'); -const { - isWritable, - getFirstParentDirectoryThatExists, -} = require('../helpers/file'); -const { withFixtures, tinyDelayMs } = require('./helpers'); -const FixtureBuilder = require('./fixture-builder'); - -/** - * The e2e test case is used to capture load and initialisation time statistics for extension in MV3 environment. - */ - -async function profilePageLoad() { - const parsedLogs = {}; - try { - await withFixtures( - { fixtures: new FixtureBuilder().build() }, - async ({ driver }) => { - await driver.delay(tinyDelayMs); - await driver.navigate(); - await driver.delay(1000); - const logs = await driver.checkBrowserForLavamoatLogs(); - - let logString = ''; - let logType = ''; - - logs.forEach((log) => { - if (log.indexOf('"version": 1') >= 0) { - // log end here - logString += log; - parsedLogs[logType] = JSON.parse(`{${logString}}`); - logString = ''; - logType = ''; - } else if (logType) { - // log string continues - logString += log; - } else if ( - log.search(/"name": ".*app\/scripts\/background.js",/u) >= 0 - ) { - // background log starts - logString += log; - logType = 'background'; - } else if (log.search(/"name": ".*app\/scripts\/ui.js",/u) >= 0) { - // ui log starts - logString += log; - logType = 'ui'; - } else if (log.search(/"name": "Total"/u) >= 0) { - // load time log starts - logString += log; - logType = 'loadTime'; - } - }); - }, - ); - } catch (error) { - console.log('Error in trying to parse logs.'); - } - return parsedLogs; -} - -async function main() { - const { argv } = yargs(hideBin(process.argv)).usage( - '$0 [options]', - 'Run a page load benchmark', - (_yargs) => - _yargs.option('out', { - description: - 'Output filename. Output printed to STDOUT of this is omitted.', - type: 'string', - normalize: true, - }), - ); - - const results = await profilePageLoad(); - const { out } = argv; - - const logCategories = [ - { key: 'background', dirPath: 'initialisation/background/stacks.json' }, - { key: 'ui', dirPath: 'initialisation/ui/stacks.json' }, - { key: 'loadTime', dirPath: 'load_time/stats.json' }, - ]; - - if (out) { - logCategories.forEach(async ({ key, dirPath }) => { - if (results[key]) { - const outPath = `${out}/${dirPath}`; - const outputDirectory = path.dirname(outPath); - const existingParentDirectory = await getFirstParentDirectoryThatExists( - outputDirectory, - ); - if (!(await isWritable(existingParentDirectory))) { - throw new Error('Specified output file directory is not writable'); - } - if (outputDirectory !== existingParentDirectory) { - await fs.mkdir(outputDirectory, { recursive: true }); - } - await fs.writeFile(outPath, JSON.stringify(results[key], null, 2)); - } - }); - } else { - console.log(JSON.stringify(results, null, 2)); - } -} - -main().catch((error) => { - exitWithError(error); -}); diff --git a/test/e2e/page-objects/common.ts b/test/e2e/page-objects/common.ts index b1f678b80cca..5bf1a91e1859 100644 --- a/test/e2e/page-objects/common.ts +++ b/test/e2e/page-objects/common.ts @@ -1 +1,4 @@ -export type RawLocator = string | { css: string; text: string }; +export type RawLocator = + | string + | { css?: string; text?: string } + | { tag: string; text: string }; diff --git a/test/e2e/page-objects/flows/login.flow.ts b/test/e2e/page-objects/flows/login.flow.ts index f5ed61946ce8..0e5f2dd6281c 100644 --- a/test/e2e/page-objects/flows/login.flow.ts +++ b/test/e2e/page-objects/flows/login.flow.ts @@ -1,5 +1,5 @@ import LoginPage from '../pages/login-page'; -import HomePage from '../pages/homepage'; +import HomePage from '../pages/home/homepage'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; diff --git a/test/e2e/page-objects/flows/send-transaction.flow.ts b/test/e2e/page-objects/flows/send-transaction.flow.ts index 8af88f01aca6..d33302f25510 100644 --- a/test/e2e/page-objects/flows/send-transaction.flow.ts +++ b/test/e2e/page-objects/flows/send-transaction.flow.ts @@ -1,4 +1,4 @@ -import HomePage from '../pages/homepage'; +import HomePage from '../pages/home/homepage'; import ConfirmTxPage from '../pages/send/confirm-tx-page'; import SendTokenPage from '../pages/send/send-token-page'; import { Driver } from '../../webdriver/driver'; @@ -84,6 +84,42 @@ export const sendRedesignedTransactionToAddress = async ({ await transactionConfirmationPage.clickFooterConfirmButton(); }; +/** + * This function initiates the steps required to send a transaction from the homepage to final confirmation. + * + * @param params - An object containing the parameters. + * @param params.driver - The webdriver instance. + * @param params.recipientAccount - The recipient account. + * @param params.amount - The amount of the asset to be sent in the transaction. + */ +export const sendRedesignedTransactionToAccount = async ({ + driver, + recipientAccount, + amount, +}: { + driver: Driver; + recipientAccount: string; + amount: string; +}): Promise => { + console.log( + `Start flow to send amount ${amount} to recipient account ${recipientAccount} on home screen`, + ); + // click send button on homepage to start flow + const homePage = new HomePage(driver); + await homePage.startSendFlow(); + + // user should land on send token screen to fill recipient and amount + const sendToPage = new SendTokenPage(driver); + await sendToPage.check_pageIsLoaded(); + await sendToPage.selectRecipientAccount(recipientAccount); + await sendToPage.fillAmount(amount); + await sendToPage.goToNextScreen(); + + // confirm transaction when user lands on confirm transaction screen + const transactionConfirmationPage = new TransactionConfirmation(driver); + await transactionConfirmationPage.clickFooterConfirmButton(); +}; + /** * This function initiates the steps required to send a transaction from the homepage to final confirmation. * diff --git a/test/e2e/page-objects/flows/sign.flow.ts b/test/e2e/page-objects/flows/sign.flow.ts index c7d03bb4f96e..3d69273d95ab 100644 --- a/test/e2e/page-objects/flows/sign.flow.ts +++ b/test/e2e/page-objects/flows/sign.flow.ts @@ -85,7 +85,8 @@ export const signTypedDataV3WithSnapAccount = async ( ): Promise => { const testDapp = new TestDapp(driver); await testDapp.check_pageIsLoaded(); - await testDapp.signTypedDataV3(); + await testDapp.signTypedDataV3Redesign(); + if (!isSyncFlow) { await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await new SnapSimpleKeyringPage(driver).approveRejectSnapAccountTransaction( diff --git a/test/e2e/page-objects/flows/transaction.ts b/test/e2e/page-objects/flows/transaction.ts index e2fdbd652034..c75bf3601572 100644 --- a/test/e2e/page-objects/flows/transaction.ts +++ b/test/e2e/page-objects/flows/transaction.ts @@ -1,7 +1,7 @@ import { TransactionParams } from '@metamask/transaction-controller'; import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; import { Driver } from '../../webdriver/driver'; -import HomePage from '../pages/homepage'; +import HomePage from '../pages/home/homepage'; import SendTokenPage from '../pages/send/send-token-page'; import TestDapp from '../pages/test-dapp'; diff --git a/test/e2e/page-objects/flows/watch-account.flow.ts b/test/e2e/page-objects/flows/watch-account.flow.ts new file mode 100644 index 000000000000..8481c71599a6 --- /dev/null +++ b/test/e2e/page-objects/flows/watch-account.flow.ts @@ -0,0 +1,22 @@ +import { Driver } from '../../webdriver/driver'; +import HomePage from '../pages/home/homepage'; +import AccountListPage from '../pages/account-list-page'; + +/** + * Initiates the flow of watching an EOA address. + * + * @param driver - The WebDriver instance. + * @param address - The EOA address that is to be watched. + */ +export async function watchEoaAddress( + driver: Driver, + address: string, +): Promise { + // watch a new EOA + const homePage = new HomePage(driver); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addEoaAccount(address); + await homePage.check_pageIsLoaded(); +} diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts index 9f96d70f4972..8f24cfffede3 100644 --- a/test/e2e/page-objects/pages/account-list-page.ts +++ b/test/e2e/page-objects/pages/account-list-page.ts @@ -1,16 +1,20 @@ -import { strict as assert } from 'assert'; import { Driver } from '../../webdriver/driver'; -import { largeDelayMs } from '../../helpers'; +import { largeDelayMs, regularDelayMs } from '../../helpers'; import messages from '../../../../app/_locales/en/messages.json'; +import { ACCOUNT_TYPE } from '../../constants'; class AccountListPage { private readonly driver: Driver; - private readonly accountAddressText = '.qr-code__address-segments'; + private readonly accountListAddressItem = + '[data-testid="account-list-address"]'; private readonly accountListBalance = '[data-testid="second-currency-display"]'; + private readonly accountValueAndSuffix = + '[data-testid="account-value-and-suffix"]'; + private readonly accountListItem = '.multichain-account-menu-popover__list--menu-item'; @@ -22,10 +26,6 @@ class AccountListPage { private readonly accountOptionsMenuButton = '[data-testid="account-list-item-menu-button"]'; - private readonly accountQrCodeImage = '.qr-code__wrapper'; - - private readonly accountQrCodeAddress = '.qr-code__address-segments'; - private readonly addAccountConfirmButton = '[data-testid="submit-add-account-with-name"]'; @@ -34,9 +34,22 @@ class AccountListPage { tag: 'button', }; + private readonly addSolanaAccountButton = { + text: messages.addNewSolanaAccount.message, + tag: 'button', + }; + private readonly addEthereumAccountButton = '[data-testid="multichain-account-menu-popover-add-account"]'; + private readonly addEoaAccountButton = + '[data-testid="multichain-account-menu-popover-add-watch-only-account"]'; + + private readonly addHardwareWalletButton = { + text: 'Add hardware wallet', + tag: 'button', + }; + private readonly addImportedAccountButton = '[data-testid="multichain-account-menu-popover-add-imported-account"]'; @@ -45,7 +58,8 @@ class AccountListPage { tag: 'button', }; - private readonly closeAccountModalButton = 'button[aria-label="Close"]'; + private readonly closeAccountModalButton = + 'header button[aria-label="Close"]'; private readonly createAccountButton = '[data-testid="multichain-account-menu-popover-action-button"]'; @@ -53,11 +67,6 @@ class AccountListPage { private readonly currentSelectedAccount = '.multichain-account-list-item--selected'; - private readonly editableLabelButton = - '[data-testid="editable-label-button"]'; - - private readonly editableLabelInput = '[data-testid="editable-input"] input'; - private readonly hiddenAccountOptionsMenuButton = '.multichain-account-menu-popover__list--menu-item-hidden-account [data-testid="account-list-item-menu-button"]'; @@ -107,8 +116,21 @@ class AccountListPage { tag: 'button', }; - private readonly saveAccountLabelButton = - '[data-testid="save-account-label-input"]'; + private readonly watchAccountAddressInput = + 'input#address-input[type="text"]'; + + private readonly watchAccountConfirmButton = { + text: 'Watch account', + tag: 'button', + }; + + private readonly watchAccountModalTitle = { + text: 'Watch any Ethereum account', + tag: 'h4', + }; + + private readonly selectAccountSelector = + '.multichain-account-list-item__account-name'; constructor(driver: Driver) { this.driver = driver; @@ -128,70 +150,35 @@ class AccountListPage { } /** - * Adds a new account with an optional custom label. + * Watch an EOA (external owned account). * - * @param customLabel - The custom label for the new account. If not provided, a default name will be used. + * @param address - The address to watch. + * @param expectedErrorMessage - Optional error message to display if the address is invalid. */ - async addNewAccount(customLabel?: string): Promise { - if (customLabel) { - console.log(`Adding new account with custom label: ${customLabel}`); - } else { - console.log(`Adding new account with default name`); - } + async addEoaAccount( + address: string, + expectedErrorMessage: string = '', + ): Promise { + console.log(`Watch EOA account with address ${address}`); await this.driver.clickElement(this.createAccountButton); - await this.driver.clickElement(this.addEthereumAccountButton); - if (customLabel) { - await this.driver.fill(this.accountNameInput, customLabel); - } - // needed to mitigate a race condition with the state update - // there is no condition we can wait for in the UI - await this.driver.delay(largeDelayMs); + await this.driver.clickElement(this.addEoaAccountButton); + await this.driver.waitForSelector(this.watchAccountModalTitle); + await this.driver.fill(this.watchAccountAddressInput, address); await this.driver.clickElementAndWaitToDisappear( - this.addAccountConfirmButton, + this.watchAccountConfirmButton, ); - } - - /** - * Adds a new BTC account with an optional custom name. - * - * @param options - Options for adding a new BTC account. - * @param [options.btcAccountCreationEnabled] - Indicates if the BTC account creation is expected to be enabled or disabled. Defaults to true. - * @param [options.accountName] - The custom name for the BTC account. Defaults to an empty string, which means the default name will be used. - */ - async addNewBtcAccount({ - btcAccountCreationEnabled = true, - accountName = '', - }: { - btcAccountCreationEnabled?: boolean; - accountName?: string; - } = {}): Promise { - console.log( - `Adding new BTC account${ - accountName ? ` with custom name: ${accountName}` : ' with default name' - }`, - ); - await this.driver.clickElement(this.createAccountButton); - if (btcAccountCreationEnabled) { - await this.driver.clickElement(this.addBtcAccountButton); - // needed to mitigate a race condition with the state update - // there is no condition we can wait for in the UI - await this.driver.delay(largeDelayMs); - if (accountName) { - await this.driver.fill(this.accountNameInput, accountName); - } - await this.driver.clickElementAndWaitToDisappear( - this.addAccountConfirmButton, - // Longer timeout than usual, this reduces the flakiness - // around Bitcoin account creation (mainly required for - // Firefox) - 5000, + if (expectedErrorMessage) { + console.log( + `Check if error message is displayed: ${expectedErrorMessage}`, ); + await this.driver.waitForSelector({ + css: '.snap-ui-renderer__text', + text: expectedErrorMessage, + }); } else { - const createButton = await this.driver.findElement( - this.addBtcAccountButton, + await this.driver.clickElementAndWaitToDisappear( + this.addAccountConfirmButton, ); - assert.equal(await createButton.isEnabled(), false); - await this.driver.clickElement(this.closeAccountModalButton); } } @@ -223,44 +210,100 @@ class AccountListPage { } /** - * Changes the label of the current account. + * Adds a new Solana account with optional custom name. * - * @param newLabel - The new label to set for the account. + * @param options - Options for creating the Solana account + * @param [options.solanaAccountCreationEnabled] - Whether Solana account creation is enabled. If false, verifies the create button is disabled. + * @param [options.accountName] - Optional custom name for the new account + * @returns Promise that resolves when account creation is complete */ - async changeAccountLabel(newLabel: string): Promise { - console.log(`Changing account label to: ${newLabel}`); - await this.driver.clickElement(this.accountMenuButton); - await this.changeLabelFromAccountDetailsModal(newLabel); + async addNewSolanaAccount({ + solanaAccountCreationEnabled = true, + accountName = '', + }: { + solanaAccountCreationEnabled?: boolean; + accountName?: string; + } = {}): Promise { + console.log( + `Adding new Solana account${ + accountName ? ` with custom name: ${accountName}` : ' with default name' + }`, + ); + if (solanaAccountCreationEnabled) { + await this.driver.clickElement(this.addSolanaAccountButton); + // needed to mitigate a race condition with the state update + // there is no condition we can wait for in the UI + if (accountName) { + await this.driver.fill(this.accountNameInput, accountName); + } + await this.driver.clickElementAndWaitToDisappear( + this.addAccountConfirmButton, + // Longer timeout than usual, this reduces the flakiness + // around Bitcoin account creation (mainly required for + // Firefox) + 5000, + ); + } else { + const createButton = await this.driver.findElement( + this.addSolanaAccountButton, + ); + assert.equal(await createButton.isEnabled(), false); + await this.driver.clickElement(this.closeAccountModalButton); + } } /** - * Changes the account label from within an already opened account details modal. - * Note: This method assumes the account details modal is already open. - * - * Recommended usage: - * ```typescript - * await accountListPage.openAccountDetailsModal('Current Account Name'); - * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name'); - * ``` + * Adds a new account of the specified type with an optional custom name. * - * @param newLabel - The new label to set for the account - * @throws Will throw an error if the modal is not open when method is called + * @param options - Options for adding a new account + * @param options.accountType - The type of account to add (Ethereum, Bitcoin, or Solana) + * @param [options.accountName] - Optional custom name for the new account + * @throws {Error} If the specified account type is not supported * @example - * // To rename a specific account, first open its details modal: - * await accountListPage.openAccountDetailsModal('Current Account Name'); - * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name'); + * // Add a new Ethereum account with default name + * await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Ethereum }); * - * // Note: Using changeAccountLabel() alone will only work for the first account + * // Add a new Bitcoin account with custom name + * await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin, accountName: 'My BTC Wallet' }); */ - async changeLabelFromAccountDetailsModal(newLabel: string): Promise { - await this.driver.waitForSelector(this.editableLabelButton); - console.log( - `Account details modal opened, changing account label to: ${newLabel}`, + async addAccount({ + accountType, + accountName, + }: { + accountType: ACCOUNT_TYPE; + accountName?: string; + }) { + console.log(`Adding new account of type: ${ACCOUNT_TYPE[accountType]}`); + await this.driver.clickElement(this.createAccountButton); + let addAccountButton; + switch (accountType) { + case ACCOUNT_TYPE.Ethereum: + addAccountButton = this.addEthereumAccountButton; + break; + case ACCOUNT_TYPE.Bitcoin: + addAccountButton = this.addBtcAccountButton; + break; + case ACCOUNT_TYPE.Solana: + addAccountButton = this.addSolanaAccountButton; + break; + default: + throw new Error('Account type not supported'); + } + + await this.driver.clickElement(addAccountButton); + if (accountName) { + console.log( + `Customize the new account with account name: ${accountName}`, + ); + await this.driver.fill(this.accountNameInput, accountName); + } + // needed to mitigate a race condition with the state update + // there is no condition we can wait for in the UI + await this.driver.delay(largeDelayMs); + await this.driver.clickElementAndWaitToDisappear( + this.addAccountConfirmButton, + 5000, ); - await this.driver.clickElement(this.editableLabelButton); - await this.driver.fill(this.editableLabelInput, newLabel); - await this.driver.clickElement(this.saveAccountLabelButton); - await this.driver.clickElement(this.closeAccountModalButton); } async closeAccountModal(): Promise { @@ -270,23 +313,6 @@ class AccountListPage { ); } - /** - * Get the address of the specified account. - * - * @param accountLabel - The label of the account to get the address. - */ - async getAccountAddress(accountLabel: string): Promise { - console.log(`Get account address in account list`); - await this.openAccountOptionsInAccountList(accountLabel); - await this.driver.clickElement(this.accountMenuButton); - await this.driver.waitForSelector(this.accountAddressText); - const accountAddress = await ( - await this.driver.findElement(this.accountAddressText) - ).getText(); - await this.driver.clickElement(this.closeAccountModalButton); - return accountAddress; - } - async hideAccount(): Promise { console.log(`Hide account in account list`); await this.driver.clickElement(this.hideUnhideAccountButton); @@ -318,6 +344,13 @@ class AccountListPage { ); } + async isBtcAccountCreationButtonEnabled(): Promise { + const createButton = await this.driver.findElement( + this.addBtcAccountButton, + ); + return await createButton.isEnabled(); + } + /** * Open the account details modal for the specified account in account list. * @@ -345,6 +378,45 @@ class AccountListPage { ); } + /** + * Checks that the account value and suffix is displayed in the account list. + * + * @param expectedValueAndSuffix - The expected value and suffix to check. + */ + async check_accountValueAndSuffixDisplayed( + expectedValueAndSuffix: string, + ): Promise { + console.log( + `Check that account value and suffix ${expectedValueAndSuffix} is displayed in account list`, + ); + await this.driver.findElement(this.accountValueAndSuffix, 5000); + await this.driver.waitForSelector( + { + css: this.accountValueAndSuffix, + text: expectedValueAndSuffix, + }, + { + timeout: 20000, + }, + ); + } + + async check_addBitcoinAccountAvailable( + expectedAvailability: boolean, + ): Promise { + console.log( + `Check add bitcoin account button is ${ + expectedAvailability ? 'displayed ' : 'not displayed' + }`, + ); + await this.openAddAccountModal(); + if (expectedAvailability) { + await this.driver.waitForSelector(this.addBtcAccountButton); + } else { + await this.driver.assertElementNotPresent(this.addBtcAccountButton); + } + } + async openAccountOptionsMenu(): Promise { console.log(`Open account option menu`); await this.driver.waitForSelector(this.accountListItem); @@ -357,6 +429,15 @@ class AccountListPage { await this.driver.waitForSelector(this.addEthereumAccountButton); } + async openConnectHardwareWalletModal(): Promise { + console.log(`Open connect hardware wallet modal`); + await this.driver.clickElement(this.createAccountButton); + await this.driver.clickElement(this.addHardwareWalletButton); + // This delay is needed to mitigate an existing bug in FF + // See https://github.com/metamask/metamask-extension/issues/25851 + await this.driver.delay(regularDelayMs); + } + async openHiddenAccountOptions(): Promise { console.log(`Open hidden accounts options menu`); await this.driver.clickElement(this.hiddenAccountOptionsMenuButton); @@ -415,6 +496,18 @@ class AccountListPage { await this.driver.clickElement(this.pinUnpinAccountButton); } + async check_accountAddressDisplayedInAccountList( + expectedAddress: string, + ): Promise { + console.log( + `Check that account address ${expectedAddress} is displayed in account list`, + ); + await this.driver.waitForSelector({ + css: this.accountListAddressItem, + text: expectedAddress, + }); + } + /** * Checks that the account balance is displayed in the account list. * @@ -442,6 +535,18 @@ class AccountListPage { }); } + async check_accountNotDisplayedInAccountList( + expectedLabel: string = 'Account', + ): Promise { + console.log( + `Check that account label ${expectedLabel} is not displayed in account list`, + ); + await this.driver.assertElementNotPresent({ + css: this.accountListItem, + text: expectedLabel, + }); + } + /** * Checks that the account with the specified label is not displayed in the account list. * @@ -480,21 +585,37 @@ class AccountListPage { } /** - * Check that the correct address is displayed in the account details modal. + * Checks that the add watch account button is displayed in the create account modal. * - * @param expectedAddress - The expected address to check. + * @param expectedAvailability - Whether the add watch account button is expected to be displayed. */ - async check_addressInAccountDetailsModal( - expectedAddress: string, + async check_addWatchAccountAvailable( + expectedAvailability: boolean, ): Promise { console.log( - `Check that address ${expectedAddress} is displayed in account details modal`, + `Check add watch account button is ${ + expectedAvailability ? 'displayed ' : 'not displayed' + }`, ); - await this.driver.waitForSelector(this.accountQrCodeImage); - await this.driver.waitForSelector({ - css: this.accountQrCodeAddress, - text: expectedAddress, - }); + await this.openAddAccountModal(); + if (expectedAvailability) { + await this.driver.waitForSelector(this.addEoaAccountButton); + } else { + await this.driver.assertElementNotPresent(this.addEoaAccountButton); + } + } + + /** + * Verifies that all occurrences of the account balance value and symbol are displayed as private. + * + */ + async check_balanceIsPrivateEverywhere(): Promise { + console.log(`Verify all account balance occurrences are private`); + const balanceSelectors = { + tag: 'span', + text: '••••••', + }; + await this.driver.elementCountBecomesN(balanceSelectors, 6); } async check_currentAccountIsImported(): Promise { @@ -521,11 +642,17 @@ class AccountListPage { console.log( `Verify the number of accounts in the account menu is: ${expectedNumberOfAccounts}`, ); + + await this.driver.waitForSelector(this.accountListItem); await this.driver.wait(async () => { const internalAccounts = await this.driver.findElements( this.accountListItem, ); - return internalAccounts.length === expectedNumberOfAccounts; + const isValid = internalAccounts.length === expectedNumberOfAccounts; + console.log( + `Number of accounts: ${internalAccounts.length} is equal to ${expectedNumberOfAccounts}? ${isValid}`, + ); + return isValid; }, 20000); } @@ -543,6 +670,13 @@ class AccountListPage { await this.openAccountOptionsInAccountList(accountLabel); await this.driver.assertElementNotPresent(this.removeAccountButton); } + + async selectAccount(accountLabel: string): Promise { + await this.driver.clickElement({ + css: this.selectAccountSelector, + text: accountLabel, + }); + } } export default AccountListPage; diff --git a/test/e2e/page-objects/pages/confirmations/redesign/accountDetailsModal.ts b/test/e2e/page-objects/pages/confirmations/redesign/accountDetailsModal.ts new file mode 100644 index 000000000000..3c10307e8fb4 --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/accountDetailsModal.ts @@ -0,0 +1,42 @@ +import { Driver } from '../../../../webdriver/driver'; +import { RawLocator } from '../../../common'; +import Confirmation from './confirmation'; + +class AccountDetailsModal extends Confirmation { + private accountBalanceInfo: RawLocator; + + private addressCopyButton: RawLocator; + + private accountDetailsModalCloseButton: RawLocator; + + constructor(driver: Driver) { + super(driver); + + this.driver = driver; + + this.accountBalanceInfo = + '[data-testid="confirmation-account-details-modal__account-balance"]'; + + this.addressCopyButton = '[data-testid="address-copy-button-text"]'; + + this.accountDetailsModalCloseButton = + '[data-testid="confirmation-account-details-modal__close-button"]'; + } + + async clickAddressCopyButton() { + await this.driver.clickElement(this.addressCopyButton); + } + + async clickAccountDetailsModalCloseButton() { + await this.driver.clickElement(this.accountDetailsModalCloseButton); + } + + async assertHeaderInfoBalance(balance: string) { + await this.driver.waitForSelector({ + css: this.accountBalanceInfo.toString(), + text: `${balance} ETH`, + }); + } +} + +export default AccountDetailsModal; diff --git a/test/e2e/page-objects/pages/confirmations/redesign/add-token-confirmations.ts b/test/e2e/page-objects/pages/confirmations/redesign/add-token-confirmations.ts new file mode 100644 index 000000000000..fdbfbbd77ec8 --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/add-token-confirmations.ts @@ -0,0 +1,49 @@ +import { Driver } from '../../../../webdriver/driver'; + +class AddTokenConfirmation { + driver: Driver; + + private readonly addTokenConfirmationTitle = { + css: '.page-container__title', + text: 'Add suggested tokens', + }; + + private readonly confirmAddTokenButton = + '[data-testid="page-container-footer-next"]'; + + private readonly rejectAddTokenButton = + '[data-testid="page-container-footer-cancel"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForSelector(this.addTokenConfirmationTitle); + } catch (e) { + console.log( + 'Timeout while waiting for Add token confirmation page to be loaded', + e, + ); + throw e; + } + console.log('Add token confirmation page is loaded'); + } + + async confirmAddToken(): Promise { + console.log('Confirm add token'); + await this.driver.clickElementAndWaitForWindowToClose( + this.confirmAddTokenButton, + ); + } + + async rejectAddToken(): Promise { + console.log('Reject add token'); + await this.driver.clickElementAndWaitForWindowToClose( + this.rejectAddTokenButton, + ); + } +} + +export default AddTokenConfirmation; diff --git a/test/e2e/page-objects/pages/confirmations/redesign/confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/confirmation.ts index f8fc66c3fc65..9ad3e4f81007 100644 --- a/test/e2e/page-objects/pages/confirmations/redesign/confirmation.ts +++ b/test/e2e/page-objects/pages/confirmations/redesign/confirmation.ts @@ -1,3 +1,4 @@ +import { Key } from 'selenium-webdriver'; import { Driver } from '../../../../webdriver/driver'; import { RawLocator } from '../../../common'; @@ -8,11 +9,35 @@ class Confirmation { private footerConfirmButton: RawLocator; + private headerAccountDetailsButton: RawLocator; + + private footerCancelButton: RawLocator; + + private sectionCollapseButton = '[data-testid="sectionCollapseButton"]'; + + private inlineAlertButton = { + css: '[data-testid="inline-alert"]', + text: 'Alert', + }; + + private nextPageButton: RawLocator; + + private previousPageButton: RawLocator; + + private navigationTitle: RawLocator; + constructor(driver: Driver) { this.driver = driver; this.scrollToBottomButton = '.confirm-scroll-to-bottom__button'; this.footerConfirmButton = '[data-testid="confirm-footer-button"]'; + this.headerAccountDetailsButton = + '[data-testid="header-info__account-details-button"]'; + this.footerCancelButton = '[data-testid="confirm-footer-cancel-button"]'; + this.nextPageButton = '[data-testid="confirm-nav__next-confirmation"]'; + this.previousPageButton = + '[data-testid="confirm-nav__previous-confirmation"]'; + this.navigationTitle = '[data-testid="confirm-page-nav-position"]'; } async clickScrollToBottomButton() { @@ -22,6 +47,50 @@ class Confirmation { async clickFooterConfirmButton() { await this.driver.clickElement(this.footerConfirmButton); } + + async clickHeaderAccountDetailsButton() { + const accountDetailsButton = await this.driver.findElement( + this.headerAccountDetailsButton, + ); + await accountDetailsButton.sendKeys(Key.RETURN); + } + + async clickFooterCancelButtonAndAndWaitForWindowToClose() { + await this.driver.clickElementAndWaitForWindowToClose( + this.footerCancelButton, + ); + } + + async clickCollapseSectionButton() { + await this.driver.clickElement(this.sectionCollapseButton); + } + + async clickInlineAlert() { + await this.driver.clickElement(this.inlineAlertButton); + } + + async clickNextPage(): Promise { + await this.driver.clickElement(this.nextPageButton); + } + + async clickPreviousPage(): Promise { + await this.driver.clickElement(this.previousPageButton); + } + + async check_pageNumbers( + currentPage: number, + totalPages: number, + ): Promise { + try { + await this.driver.findElement({ + css: this.navigationTitle, + text: `${currentPage} of ${totalPages}`, + }); + } catch (e) { + console.log('Timeout while waiting for navigation page numbers', e); + throw e; + } + } } export default Confirmation; diff --git a/test/e2e/page-objects/pages/confirmations/redesign/permit-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/permit-confirmation.ts new file mode 100644 index 000000000000..2fe7b50f2865 --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/permit-confirmation.ts @@ -0,0 +1,142 @@ +import { strict as assert } from 'assert'; +import { Driver } from '../../../../webdriver/driver'; +import { DAPP_HOST_ADDRESS } from '../../../../constants'; +import Confirmation from './confirmation'; + +export default class PermitConfirmation extends Confirmation { + constructor(driver: Driver) { + super(driver); + + this.driver = driver; + } + + private originSelector = { text: DAPP_HOST_ADDRESS }; + + private contractPetNameSelector = { + css: '.name__value', + text: '0xCcCCc...ccccC', + }; + + private primaryTypeSelector = { text: 'Permit' }; + + private ownerSelector = { css: '.name__name', text: 'Account 1' }; + + private spenderSelector = { css: '.name__value', text: '0x5B38D...eddC4' }; + + private valueSelector = { text: '3,000' }; + + private nonceSelector = { text: '0' }; + + private deadlineSelector = { text: '09 June 3554, 16:53' }; + + private nftContractPetNameSelector = { + css: '.name__value', + text: '0x581c3...45947', + }; + + private nftTitle = { text: 'Withdrawal request' }; + + private nftDescription = { + text: 'This site wants permission to withdraw your NFTs', + }; + + private nftPrimaryType = { text: 'Permit' }; + + private nftSpender = { css: '.name__value', text: '0x581c3...45947' }; + + private nftTokenId = { text: '3606393' }; + + private nftNonce = { text: '0' }; + + private nftDeadline = { text: '23 December 2024, 23:03' }; + + async verifyOrigin() { + const origin = await this.driver.findElement(this.originSelector); + assert.ok(origin, 'Origin element is missing or incorrect'); + } + + async verifyContractPetName() { + const contractPetName = await this.driver.findElement( + this.contractPetNameSelector, + ); + assert.ok( + contractPetName, + 'Contract Pet Name element is missing or incorrect', + ); + } + + async verifyPrimaryType() { + const primaryType = await this.driver.findElement(this.primaryTypeSelector); + assert.ok(primaryType, 'Primary Type element is missing or incorrect'); + } + + async verifyOwner() { + const owner = await this.driver.findElement(this.ownerSelector); + assert.ok(owner, 'Owner element is missing or incorrect'); + } + + async verifySpender() { + const spender = await this.driver.findElement(this.spenderSelector); + assert.ok(spender, 'Spender element is missing or incorrect'); + } + + async verifyValue() { + const value = await this.driver.findElement(this.valueSelector); + assert.ok(value, 'Value element is missing or incorrect'); + } + + async verifyNonce() { + const nonce = await this.driver.findElement(this.nonceSelector); + assert.ok(nonce, 'Nonce element is missing or incorrect'); + } + + async verifyDeadline() { + const deadline = await this.driver.findElement(this.deadlineSelector); + assert.ok(deadline, 'Deadline element is missing or incorrect'); + } + + async verifyNftContractPetName() { + const nftContractPetName = await this.driver.findElement( + this.nftContractPetNameSelector, + ); + assert.ok( + nftContractPetName, + 'NFT Contract Pet Name element is missing or incorrect', + ); + } + + async verifyNftTitle() { + const element = await this.driver.findElement(this.nftTitle); + assert.ok(element, 'NFT Title element is missing or incorrect'); + } + + async verifyNftDescription() { + const element = await this.driver.findElement(this.nftDescription); + assert.ok(element, 'NFT Description element is missing or incorrect'); + } + + async verifyNftPrimaryType() { + const element = await this.driver.findElement(this.nftPrimaryType); + assert.ok(element, 'NFT PrimaryType element is missing or incorrect'); + } + + async verifyNftSpender() { + const element = await this.driver.findElement(this.nftSpender); + assert.ok(element, 'NFT Spender element is missing or incorrect'); + } + + async verifyNftTokenId() { + const element = await this.driver.findElement(this.nftTokenId); + assert.ok(element, 'NFT TokenId element is missing or incorrect'); + } + + async verifyNftNonce() { + const element = await this.driver.findElement(this.nftNonce); + assert.ok(element, 'NFT Nonce element is missing or incorrect'); + } + + async verifyNftDeadline() { + const element = await this.driver.findElement(this.nftDeadline); + assert.ok(element, 'NFT Deadline element is missing or incorrect'); + } +} diff --git a/test/e2e/page-objects/pages/confirmations/redesign/personal-sign-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/personal-sign-confirmation.ts new file mode 100644 index 000000000000..e2d55a46e1cc --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/personal-sign-confirmation.ts @@ -0,0 +1,34 @@ +import { strict as assert } from 'assert'; +import { DAPP_HOST_ADDRESS } from '../../../../constants'; +import { Driver } from '../../../../webdriver/driver'; +import Confirmation from './confirmation'; + +export default class PersonalSignConfirmation extends Confirmation { + constructor(driver: Driver) { + super(driver); + + this.driver = driver; + } + + private originSelector = { text: DAPP_HOST_ADDRESS }; + + private messageSelector = { text: 'Example `personal_sign` message' }; + + private siweMessage = { + text: 'I accept the MetaMask Terms of Service: https://community.metamask.io/tos', + }; + + async verifyOrigin() { + const origin = await this.driver.findElement(this.originSelector); + assert.ok(origin, 'Origin element is missing or incorrect'); + } + + async verifyMessage() { + const message = this.driver.findElement(this.messageSelector); + assert.ok(await message); + } + + async verifySiweMessage() { + this.driver.findElement(this.siweMessage); + } +} diff --git a/test/e2e/page-objects/pages/confirmations/redesign/sign-typed-data-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/sign-typed-data-confirmation.ts new file mode 100644 index 000000000000..6c747912be9d --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/sign-typed-data-confirmation.ts @@ -0,0 +1,92 @@ +import { strict as assert } from 'assert'; +import { DAPP_HOST_ADDRESS } from '../../../../constants'; +import { Driver } from '../../../../webdriver/driver'; +import Confirmation from './confirmation'; + +export default class SignTypedData extends Confirmation { + constructor(driver: Driver) { + super(driver); + + this.driver = driver; + } + + private origin = { text: DAPP_HOST_ADDRESS }; + + private signTypedDataMessage = { text: 'Hi, Alice!' }; + + private contract = { css: '.name__value', text: '0xCcCCc...ccccC' }; + + private primaryType = { text: 'Mail' }; + + private fromName = { text: 'Cow' }; + + private fromAddress = { css: '.name__value', text: '0xCD2a3...DD826' }; + + private toName = { text: 'Bob' }; + + private toAddress = { css: '.name__value', text: '0xbBbBB...bBBbB' }; + + private contents = { text: 'Hello, Bob!' }; + + private attachment = { text: '0x' }; + + private toAddressNum2 = { css: '.name__value', text: '0xB0B0b...00000' }; + + async verifyOrigin() { + const origin = await this.driver.findElement(this.origin); + assert.ok(origin, 'Origin element is missing or incorrect'); + } + + async verifySignTypedDataMessage() { + const message = this.driver.findElement(this.signTypedDataMessage); + assert.ok(await message); + } + + async verifyContractPetName() { + const contractPetName = await this.driver.findElement(this.contract); + assert.ok( + contractPetName, + 'Contract pet name element is missing or incorrect', + ); + } + + async verifyPrimaryType() { + const primaryType = await this.driver.findElement(this.primaryType); + assert.ok(primaryType, 'Primary type element is missing or incorrect'); + } + + async verifyFromName() { + const fromName = await this.driver.findElement(this.fromName); + assert.ok(fromName, 'From name element is missing or incorrect'); + } + + async verifyFromAddress() { + const fromAddress = await this.driver.findElement(this.fromAddress); + assert.ok(fromAddress, 'From address element is missing or incorrect'); + } + + async verifyToName() { + const toName = await this.driver.findElement(this.toName); + assert.ok(toName, 'To name element is missing or incorrect'); + } + + async verifyToAddress() { + const toAddress = await this.driver.findElement(this.toAddress); + assert.ok(toAddress, 'To address element is missing or incorrect'); + } + + async verifyContents() { + const contents = await this.driver.findElement(this.contents); + assert.ok(contents, 'Contents element is missing or incorrect'); + } + + async verifyAttachment() { + const attachment = await this.driver.findElement(this.attachment); + assert.ok(attachment, 'Attachment element is missing or incorrect'); + } + + async verifyToAddressNum2() { + const toAddressNum2 = await this.driver.findElement(this.toAddressNum2); + assert.ok(toAddressNum2, 'To Address num2 element is missing or incorrect'); + } +} diff --git a/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts index c7f618d3fc61..d2d294c28cd0 100644 --- a/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts +++ b/test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts @@ -1,3 +1,4 @@ +import { strict as assert } from 'assert'; import { tEn } from '../../../../../lib/i18n-helpers'; import { Driver } from '../../../../webdriver/driver'; import { RawLocator } from '../../../common'; @@ -8,6 +9,16 @@ class TransactionConfirmation extends Confirmation { private dappInitiatedHeadingTitle: RawLocator; + private advancedDetailsButton: RawLocator; + + private advancedDetailsSection: RawLocator; + + private advancedDetailsDataFunction: RawLocator; + + private advancedDetailsDataParam: RawLocator; + + private advancedDetailsHexData: RawLocator; + constructor(driver: Driver) { super(driver); @@ -21,6 +32,17 @@ class TransactionConfirmation extends Confirmation { css: 'h3', text: tEn('transferRequest') as string, }; + + this.advancedDetailsButton = `[data-testid="header-advanced-details-button"]`; + + this.advancedDetailsSection = + '[data-testid="advanced-details-data-section"]'; + this.advancedDetailsDataFunction = + '[data-testid="advanced-details-data-function"]'; + this.advancedDetailsDataParam = + '[data-testid="advanced-details-data-param-0"]'; + this.advancedDetailsHexData = + '[data-testid="advanced-details-transaction-hex"]'; } async check_walletInitiatedHeadingTitle() { @@ -30,6 +52,155 @@ class TransactionConfirmation extends Confirmation { async check_dappInitiatedHeadingTitle() { await this.driver.waitForSelector(this.dappInitiatedHeadingTitle); } + + async clickAdvancedDetailsButton() { + await this.driver.clickElement(this.advancedDetailsButton); + } + + async verifyAdvancedDetailsIsDisplayed(type: string) { + const advancedDetailsSection = await this.driver.findElement( + this.advancedDetailsSection, + ); + + await advancedDetailsSection.isDisplayed(); + await advancedDetailsSection + .findElement({ css: this.advancedDetailsDataFunction.toString() }) + .isDisplayed(); + await advancedDetailsSection + .findElement({ css: this.advancedDetailsDataParam.toString() }) + .isDisplayed(); + + const functionInfo = await this.driver.findElement( + this.advancedDetailsDataFunction, + ); + const functionText = await functionInfo.getText(); + + assert.ok( + functionText.includes('Function'), + 'Expected key "Function" to be included in the function text', + ); + assert.ok( + functionText.includes('mintNFTs'), + 'Expected "mintNFTs" to be included in the function text', + ); + + const paramsInfo = await this.driver.findElement( + this.advancedDetailsDataParam, + ); + const paramsText = await paramsInfo.getText(); + + if (type === '4Bytes') { + assert.ok( + paramsText.includes('Param #1'), + 'Expected "Param #1" to be included in the param text', + ); + } else if (type === 'Sourcify') { + assert.ok( + paramsText.includes('Number Of Tokens'), + 'Expected "Number Of Tokens" to be included in the param text', + ); + } + + assert.ok( + paramsText.includes('1'), + 'Expected "1" to be included in the param value', + ); + } + + async verifyAdvancedDetailsHexDataIsDisplayed() { + const advancedDetailsSection = await this.driver.findElement( + this.advancedDetailsSection, + ); + + await advancedDetailsSection.isDisplayed(); + await advancedDetailsSection + .findElement({ css: this.advancedDetailsHexData.toString() }) + .isDisplayed(); + + const hexDataInfo = ( + await this.driver.findElement(this.advancedDetailsHexData) + ).getText(); + + assert.ok( + (await hexDataInfo).includes( + '0x3b4b13810000000000000000000000000000000000000000000000000000000000000001', + ), + 'Expected hex data to be displayed', + ); + } + + async verifyUniswapDecodedTransactionAdvancedDetails() { + const dataSections = await this.driver.findElements( + this.advancedDetailsDataFunction, + ); + + const expectedData = [ + { + functionName: 'WRAP_ETH', + recipient: '0x00000...00002', + amountMin: '100000000000000', + }, + { + functionName: 'V3_SWAP_EXACT_IN', + recipient: '0x00000...00002', + amountIn: '100000000000000', + amountOutMin: '312344', + path0: 'WETH', + path1: '500', + path2: 'USDC', + payerIsUser: 'false', + }, + { + functionName: 'PAY_PORTION', + token: 'USDC', + recipient: '0x27213...71c47', + bips: '25', + }, + { + functionName: 'SWEEP', + token: 'USDC', + recipient: '0x00000...00001', + amountMin: '312344', + }, + ]; + + assert.strictEqual( + dataSections.length, + expectedData.length, + 'Mismatch between data sections and expected data count.', + ); + + await Promise.all( + dataSections.map(async (dataSection, sectionIndex) => { + await dataSection.isDisplayed(); + + const data = expectedData[sectionIndex]; + + const functionText = await dataSection.getText(); + assert.ok( + functionText.includes(data.functionName), + `Expected function name '${data.functionName}' in advanced details.`, + ); + + const params = `[data-testid="advanced-details-${functionText}-params"]`; + + const paramsData = await this.driver.findElement(params); + const paramText = await paramsData.getText(); + + for (const [key, expectedValue] of Object.entries(data)) { + if (key === 'functionName') { + continue; + } + assert.ok( + paramText.includes(expectedValue), + `Expected ${key} '${expectedValue}' in data section ${functionText}.`, + ); + + this.clickScrollToBottomButton(); + } + }), + ); + } } export default TransactionConfirmation; diff --git a/test/e2e/page-objects/pages/dialog/account-details-modal.ts b/test/e2e/page-objects/pages/dialog/account-details-modal.ts new file mode 100644 index 000000000000..0dbb2e0f87d7 --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/account-details-modal.ts @@ -0,0 +1,106 @@ +import { Driver } from '../../../webdriver/driver'; + +class AccountDetailsModal { + private driver: Driver; + + private readonly accountAddressText = '.qr-code__address-segments'; + + private readonly accountQrCodeAddress = '.qr-code__address-segments'; + + private readonly accountQrCodeImage = '.qr-code__wrapper'; + + private readonly closeAccountModalButton = + 'header button[aria-label="Close"]'; + + private readonly copyAddressButton = + '[data-testid="address-copy-button-text"]'; + + private readonly editableLabelButton = + '[data-testid="editable-label-button"]'; + + private readonly editableLabelInput = '[data-testid="editable-input"] input'; + + private readonly saveAccountLabelButton = + '[data-testid="save-account-label-input"]'; + + private readonly showPrivateKeyButton = { + css: 'button', + text: 'Show private key', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.editableLabelButton, + this.copyAddressButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for account details modal to be loaded', + e, + ); + throw e; + } + console.log('Account details modal is loaded'); + } + + async closeAccountDetailsModal(): Promise { + await this.driver.clickElementAndWaitToDisappear( + this.closeAccountModalButton, + ); + } + + /** + * Change the label of the account in the account details modal. + * + * @param newLabel - The new label to set for the account. + */ + async changeAccountLabel(newLabel: string): Promise { + console.log( + `Account details modal opened, changing account label to: ${newLabel}`, + ); + await this.driver.clickElement(this.editableLabelButton); + await this.driver.fill(this.editableLabelInput, newLabel); + await this.driver.clickElement(this.saveAccountLabelButton); + await this.closeAccountDetailsModal(); + } + + async getAccountAddress(): Promise { + console.log(`Get account address in account details modal`); + await this.driver.waitForSelector(this.accountAddressText); + const accountAddress = await ( + await this.driver.findElement(this.accountAddressText) + ).getText(); + await this.closeAccountDetailsModal(); + return accountAddress; + } + + /** + * Check that the correct address is displayed in the account details modal. + * + * @param expectedAddress - The expected address to check. + */ + async check_addressInAccountDetailsModal( + expectedAddress: string, + ): Promise { + console.log( + `Check that address ${expectedAddress} is displayed in account details modal`, + ); + await this.driver.waitForSelector(this.accountQrCodeImage); + await this.driver.waitForSelector({ + css: this.accountQrCodeAddress, + text: expectedAddress, + }); + } + + async check_showPrivateKeyButtonIsNotDisplayed(): Promise { + console.log('Check that show private key button is not displayed'); + await this.driver.assertElementNotPresent(this.showPrivateKeyButton); + } +} + +export default AccountDetailsModal; diff --git a/test/e2e/page-objects/pages/dialog/add-tokens.ts b/test/e2e/page-objects/pages/dialog/add-tokens.ts new file mode 100644 index 000000000000..e587113ab7f5 --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/add-tokens.ts @@ -0,0 +1,50 @@ +import { strict as assert } from 'assert'; +import { Driver } from '../../../webdriver/driver'; + +class AddTokensModal { + protected driver: Driver; + + private addTokenButton = { text: 'Add token', tag: 'button' }; + + private tokenListItem = '.confirm-add-suggested-token__token-list-item'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.tokenListItem, + this.addTokenButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Add tokens dialog to be loaded', + e, + ); + throw e; + } + console.log('Add tokens dialog was loaded'); + } + + /** + * Checks the count of suggested tokens. + * + * @param expectedTokenCount - The expected count of suggested tokens. + */ + async check_SuggestedTokensCount(expectedTokenCount: number) { + const multipleSuggestedTokens = await this.driver.findElements( + this.tokenListItem, + ); + + // Confirm the expected number of tokens are present as suggested token list + assert.equal(multipleSuggestedTokens.length, expectedTokenCount); + } + + async confirmAddTokens() { + await this.driver.clickElementAndWaitForWindowToClose(this.addTokenButton); + } +} + +export default AddTokensModal; diff --git a/test/e2e/page-objects/pages/dialog/confirm-alert.ts b/test/e2e/page-objects/pages/dialog/confirm-alert.ts new file mode 100644 index 000000000000..c7bc7b1f7a49 --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/confirm-alert.ts @@ -0,0 +1,36 @@ +import { Driver } from '../../../webdriver/driver'; + +class ConfirmAlertModal { + protected driver: Driver; + + private alertModalAcknowledgeCheckBox = + '[data-testid="alert-modal-acknowledge-checkbox"]'; + + private alertModalButton = '[data-testid="alert-modal-button"]'; + + private alertModalSubmitButton = + '[data-testid="confirm-alert-modal-submit-button"]'; + + private alertModalCancelButton = + '[data-testid="confirm-alert-modal-cancel-button"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async rejectFromAlertModal() { + this.driver.clickElement(this.alertModalCancelButton); + } + + async confirmFromAlertModal() { + this.driver.clickElement(this.alertModalAcknowledgeCheckBox); + this.driver.clickElement(this.alertModalSubmitButton); + } + + async acknowledgeAlert() { + this.driver.clickElement(this.alertModalAcknowledgeCheckBox); + this.driver.clickElement(this.alertModalButton); + } +} + +export default ConfirmAlertModal; diff --git a/test/e2e/page-objects/pages/dialog/create-contract.ts b/test/e2e/page-objects/pages/dialog/create-contract.ts new file mode 100644 index 000000000000..44c101271ffe --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/create-contract.ts @@ -0,0 +1,39 @@ +import { Driver } from '../../../webdriver/driver'; + +class CreateContractModal { + protected driver: Driver; + + private readonly confirmButtton = { text: 'Confirm', tag: 'button' }; + + private readonly cancelButton = { text: 'Cancel', tag: 'button' }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.confirmButtton, + this.cancelButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for create contract dialog to be loaded', + e, + ); + throw e; + } + console.log('Create contract dialog was loaded'); + } + + async clickConfirm() { + await this.driver.clickElementAndWaitForWindowToClose(this.confirmButtton); + } + + async clickCancel() { + await this.driver.clickElementAndWaitForWindowToClose(this.cancelButton); + } +} + +export default CreateContractModal; diff --git a/test/e2e/page-objects/pages/dialog/select-network.ts b/test/e2e/page-objects/pages/dialog/select-network.ts index bc20c42855ae..914684c6a240 100644 --- a/test/e2e/page-objects/pages/dialog/select-network.ts +++ b/test/e2e/page-objects/pages/dialog/select-network.ts @@ -86,6 +86,7 @@ class SelectNetwork { console.log(`Click ${networkName}`); const networkNameItem = `[data-testid="${networkName}"]`; await this.driver.clickElementAndWaitToDisappear(networkNameItem); + await this.driver.assertElementNotPresent('.loading-overlay'); } async selectRPC(rpcName: string): Promise { diff --git a/test/e2e/page-objects/pages/hardware-wallet/connect-hardware-wallet-page.ts b/test/e2e/page-objects/pages/hardware-wallet/connect-hardware-wallet-page.ts new file mode 100644 index 000000000000..e3b4de3fb522 --- /dev/null +++ b/test/e2e/page-objects/pages/hardware-wallet/connect-hardware-wallet-page.ts @@ -0,0 +1,54 @@ +import { Driver } from '../../../webdriver/driver'; + +/** + * Represents the page for connecting hardware wallets. + * This page allows users to initiate connections with various hardware wallet types. + */ +class ConnectHardwareWalletPage { + private driver: Driver; + + private readonly connectHardwareWalletPageTitle = { + text: 'Connect a hardware wallet', + tag: 'h3', + }; + + private readonly connectLatticeButton = '[data-testid="connect-lattice-btn"]'; + + private readonly connectTrezorButton = '[data-testid="connect-trezor-btn"]'; + + private readonly continueButton = { text: 'Continue', tag: 'button' }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.connectHardwareWalletPageTitle, + this.connectLatticeButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for connect hardware wallet page to be loaded', + e, + ); + throw e; + } + console.log('Connect hardware wallet page is loaded'); + } + + async openConnectLatticePage(): Promise { + console.log(`Open connect lattice page`); + await this.driver.clickElement(this.connectLatticeButton); + await this.driver.clickElement(this.continueButton); + } + + async openConnectTrezorPage(): Promise { + console.log(`Open connect trezor page`); + await this.driver.clickElement(this.connectTrezorButton); + await this.driver.clickElement(this.continueButton); + } +} + +export default ConnectHardwareWalletPage; diff --git a/test/e2e/page-objects/pages/hardware-wallet/select-trezor-account-page.ts b/test/e2e/page-objects/pages/hardware-wallet/select-trezor-account-page.ts new file mode 100644 index 000000000000..65a00808c0bb --- /dev/null +++ b/test/e2e/page-objects/pages/hardware-wallet/select-trezor-account-page.ts @@ -0,0 +1,94 @@ +import { Driver } from '../../../webdriver/driver'; + +/** + * Represents the select trezor hardware wallet account page. + * This page allows users to select Trezor accounts to connect. + */ +class SelectTrezorAccountPage { + private driver: Driver; + + private readonly cancelButton = { text: 'Cancel', tag: 'button' }; + + private readonly selectTrezorAccountPageTitle = { + text: 'Select an account', + tag: 'h3', + }; + + private readonly trezorAccountCheckbox = '.hw-account-list__item__checkbox'; + + private readonly unlockButton = { text: 'Unlock', tag: 'button' }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.selectTrezorAccountPageTitle, + this.cancelButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for select trezor account page to be loaded', + e, + ); + throw e; + } + console.log('Select trezor account page is loaded'); + } + + async clickUnlockButton(): Promise { + console.log(`Click unlock button on select trezor account page`); + await this.driver.clickElement(this.unlockButton); + } + + async selectTrezorAccount(accountIndex: number): Promise { + console.log(`Select trezor account ${accountIndex}`); + const accountCheckboxes = await this.driver.findElements( + this.trezorAccountCheckbox, + ); + await accountCheckboxes[accountIndex - 1].click(); + } + + async unlockAccount(accountIndex: number): Promise { + console.log(`Unlock trezor account ${accountIndex}`); + await this.selectTrezorAccount(accountIndex); + await this.clickUnlockButton(); + } + + /** + * Check that the specified address is displayed in the list of accounts. + * + * @param address - The address to check for. + */ + async check_addressIsDisplayed(address: string): Promise { + console.log( + `Check that account address ${address} is displayed on select trezor account page`, + ); + await this.driver.waitForSelector({ text: address }); + } + + /** + * This function checks if the specified number of trezor account items is displayed in the trezor account list. + * + * @param expectedNumber - The number of trezor account items expected to be displayed. Defaults to 5. + * @returns A promise that resolves if the expected number of trezor account items is displayed. + */ + async check_trezorAccountNumber(expectedNumber: number = 5): Promise { + console.log( + `Waiting for ${expectedNumber} trezor account items to be displayed`, + ); + await this.driver.wait(async () => { + const trezorAccountItems = await this.driver.findElements( + this.trezorAccountCheckbox, + ); + return trezorAccountItems.length === expectedNumber; + }, 10000); + console.log( + `Expected number of trezor account items ${expectedNumber} is displayed.`, + ); + } +} + +export default SelectTrezorAccountPage; diff --git a/test/e2e/page-objects/pages/header-navbar.ts b/test/e2e/page-objects/pages/header-navbar.ts index 100c23b851e4..5324def49aab 100644 --- a/test/e2e/page-objects/pages/header-navbar.ts +++ b/test/e2e/page-objects/pages/header-navbar.ts @@ -1,10 +1,16 @@ +import { strict as assert } from 'assert'; import { Driver } from '../../webdriver/driver'; class HeaderNavbar { - private driver: Driver; + protected driver: Driver; private readonly accountMenuButton = '[data-testid="account-menu-icon"]'; + private readonly allPermissionsButton = + '[data-testid="global-menu-connected-sites"]'; + + private readonly copyAddressButton = '[data-testid="app-header-copy-button"]'; + private readonly threeDotMenuButton = '[data-testid="account-options-menu-button"]'; @@ -15,10 +21,15 @@ class HeaderNavbar { private readonly mmiPortfolioButton = '[data-testid="global-menu-mmi-portfolio"]'; + private readonly openAccountDetailsButton = + '[data-testid="account-list-menu-details"]'; + private readonly settingsButton = '[data-testid="global-menu-settings"]'; private readonly switchNetworkDropDown = '[data-testid="network-display"]'; + private readonly networkPicker = '.mm-picker-network'; + constructor(driver: Driver) { this.driver = driver; } @@ -43,6 +54,13 @@ class HeaderNavbar { async openAccountMenu(): Promise { await this.driver.clickElement(this.accountMenuButton); + await this.driver.waitForSelector('.multichain-account-menu-popover__list'); + } + + async openAccountDetailsModal(): Promise { + console.log('Open account details modal'); + await this.openThreeDotMenu(); + await this.driver.clickElement(this.openAccountDetailsButton); } async openThreeDotMenu(): Promise { @@ -54,6 +72,12 @@ class HeaderNavbar { } } + async openPermissionsPage(): Promise { + console.log('Open permissions page in header navbar'); + await this.openThreeDotMenu(); + await this.driver.clickElement(this.allPermissionsButton); + } + async openSnapListPage(): Promise { console.log('Open account snap page'); await this.openThreeDotMenu(); @@ -78,6 +102,29 @@ class HeaderNavbar { ); } + async check_ifNetworkPickerClickable(clickable: boolean): Promise { + console.log('Check whether the network picker is clickable or not'); + assert.equal( + await (await this.driver.findElement(this.networkPicker)).isEnabled(), + clickable, + ); + } + + /** + * Verifies that the displayed account address in header matches the expected address. + * + * @param expectedAddress - The expected address of the account. + */ + async check_accountAddress(expectedAddress: string): Promise { + console.log( + `Verify the displayed account address in header is: ${expectedAddress}`, + ); + await this.driver.waitForSelector({ + css: this.copyAddressButton, + text: expectedAddress, + }); + } + /** * Verifies that the displayed account label in header matches the expected label. * diff --git a/test/e2e/page-objects/pages/home/activity-list.ts b/test/e2e/page-objects/pages/home/activity-list.ts new file mode 100644 index 000000000000..e0237cad93eb --- /dev/null +++ b/test/e2e/page-objects/pages/home/activity-list.ts @@ -0,0 +1,173 @@ +import { strict as assert } from 'assert'; +import { Driver } from '../../../webdriver/driver'; + +class ActivityListPage { + private readonly driver: Driver; + + private readonly completedTransactions = '[data-testid="activity-list-item"]'; + + private readonly confirmedTransactions = { + text: 'Confirmed', + css: '.transaction-status-label--confirmed', + }; + + private readonly failedTransactions = { + text: 'Failed', + css: '.transaction-status-label--failed', + }; + + private readonly transactionAmountsInActivity = + '[data-testid="transaction-list-item-primary-currency"]'; + + private readonly activityListAction = + '[data-testid="activity-list-item-action"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + /** + * This function checks the specified number of completed transactions are displayed in the activity list on the homepage. + * It waits up to 10 seconds for the expected number of completed transactions to be visible. + * + * @param expectedNumber - The number of completed transactions expected to be displayed in the activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of completed transactions is displayed within the timeout period. + */ + async check_completedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} completed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const completedTxs = await this.driver.findElements( + this.completedTransactions, + ); + return completedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} completed transactions found in activity list on homepage`, + ); + } + + /** + * This function checks if the specified number of confirmed transactions are displayed in the activity list on homepage. + * It waits up to 10 seconds for the expected number of confirmed transactions to be visible. + * + * @param expectedNumber - The number of confirmed transactions expected to be displayed in activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of confirmed transactions is displayed within the timeout period. + */ + async check_confirmedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} confirmed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const confirmedTxs = await this.driver.findElements( + this.confirmedTransactions, + ); + return confirmedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} confirmed transactions found in activity list on homepage`, + ); + } + + /** + * This function checks if the specified number of failed transactions are displayed in the activity list on homepage. + * It waits up to 10 seconds for the expected number of failed transactions to be visible. + * + * @param expectedNumber - The number of failed transactions expected to be displayed in activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of failed transactions is displayed within the timeout period. + */ + async check_failedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} failed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const failedTxs = await this.driver.findElements(this.failedTransactions); + return failedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} failed transactions found in activity list on homepage`, + ); + } + + /** + * This function checks if a specified transaction amount at the specified index matches the expected one. + * + * @param expectedAmount - The expected transaction amount to be displayed. Defaults to '-1 ETH'. + * @param expectedNumber - The 1-based index of the transaction in the activity list whose amount is to be checked. + * Defaults to 1, indicating the first transaction in the list. + * @returns A promise that resolves if the transaction amount at the specified index matches the expected amount. + * The promise is rejected if the amounts do not match or if an error occurs during the process. + * @example + * // To check if the third transaction in the activity list displays an amount of '2 ETH' + * await check_txAmountInActivity('2 ETH', 3); + */ + async check_txAmountInActivity( + expectedAmount: string = '-1 ETH', + expectedNumber: number = 1, + ): Promise { + const transactionAmounts = await this.driver.findElements( + this.transactionAmountsInActivity, + ); + const transactionAmountsText = await transactionAmounts[ + expectedNumber - 1 + ].getText(); + assert.equal( + transactionAmountsText, + expectedAmount, + `${transactionAmountsText} is displayed as transaction amount instead of ${expectedAmount} for transaction ${expectedNumber}`, + ); + console.log( + `Amount for transaction ${expectedNumber} is displayed as ${expectedAmount}`, + ); + } + + async check_txAction(expectedAction: string, expectedNumber: number = 1) { + const transactionActions = await this.driver.findElements( + this.activityListAction, + ); + + const transactionActionText = await transactionActions[ + expectedNumber - 1 + ].getText(); + + assert.equal( + transactionActionText, + expectedAction, + `${transactionActionText} is displayed as transaction action instead of ${expectedAction} for transaction ${expectedNumber}`, + ); + + console.log( + `Action for transaction ${expectedNumber} is displayed as ${expectedAction}`, + ); + } + + async check_noTxInActivity(): Promise { + await this.driver.assertElementNotPresent(this.completedTransactions); + } + + /** + * Verifies that a specific warning message is displayed on the activity list. + * + * @param warningText - The expected warning text to validate against. + * @returns A promise that resolves if the warning message matches the expected text. + * @throws Assertion error if the warning message does not match the expected text. + */ + async check_warningMessage(warningText: string): Promise { + console.log( + `Check warning message "${warningText}" is displayed on activity list`, + ); + await this.driver.waitForSelector({ + tag: 'div', + text: warningText, + }); + } +} + +export default ActivityListPage; diff --git a/test/e2e/page-objects/pages/home/asset-list.ts b/test/e2e/page-objects/pages/home/asset-list.ts new file mode 100644 index 000000000000..8c120b9879b7 --- /dev/null +++ b/test/e2e/page-objects/pages/home/asset-list.ts @@ -0,0 +1,393 @@ +import { Driver } from '../../../webdriver/driver'; + +class AssetListPage { + private readonly driver: Driver; + + private readonly allNetworksOption = + '[data-testid="network-filter-all__button"]'; + + private readonly allNetworksTotal = + '[data-testid="network-filter-all__total"]'; + + private readonly assetOptionsButton = '[data-testid="asset-options__button"]'; + + private readonly confirmImportTokenButton = + '[data-testid="import-tokens-modal-import-button"]'; + + private readonly confirmImportTokenMessage = { + text: 'Would you like to import this token?', + tag: 'p', + }; + + private readonly currentNetworkOption = + '[data-testid="network-filter-current__button"]'; + + private readonly currentNetworksTotal = `${this.currentNetworkOption} [data-testid="account-value-and-suffix"]`; + + private readonly customTokenModalOption = { + text: 'Custom token', + tag: 'button', + }; + + private readonly hideTokenButton = '[data-testid="asset-options__hide"]'; + + private readonly hideTokenConfirmationButton = + '[data-testid="hide-token-confirmation__hide"]'; + + private readonly hideTokenConfirmationModalTitle = { + text: 'Hide token', + css: '.hide-token-confirmation__title', + }; + + private readonly importTokenModalTitle = { text: 'Import tokens', tag: 'h4' }; + + private readonly importTokensButton = '[data-testid="importTokens"]'; + + private readonly importTokensNextButton = + '[data-testid="import-tokens-button-next"]'; + + private readonly networksToggle = '[data-testid="sort-by-networks"]'; + + private sortByAlphabetically = '[data-testid="sortByAlphabetically"]'; + + private sortByDecliningBalance = '[data-testid="sortByDecliningBalance"]'; + + private sortByPopoverToggle = '[data-testid="sort-by-popover-toggle"]'; + + private readonly tokenAddressInput = + '[data-testid="import-tokens-modal-custom-address"]'; + + private readonly tokenAmountValue = + '[data-testid="multichain-token-list-item-value"]'; + + private readonly tokenImportedSuccessMessage = { + text: 'Token imported', + tag: 'h6', + }; + + private readonly tokenListItem = + '[data-testid="multichain-token-list-button"]'; + + private readonly tokenOptionsButton = '[data-testid="import-token-button"]'; + + private tokenPercentage(address: string): string { + return `[data-testid="token-increase-decrease-percentage-${address}"]`; + } + + private readonly tokenSearchInput = 'input[placeholder="Search tokens"]'; + + private readonly tokenSymbolInput = + '[data-testid="import-tokens-modal-custom-symbol"]'; + + private readonly modalWarningBanner = 'div.mm-banner-alert--severity-warning'; + + private readonly tokenIncreaseDecreaseValue = + '[data-testid="token-increase-decrease-value"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async clickCurrentNetworkOption(): Promise { + console.log(`Clicking on the current network option`); + await this.driver.clickElement(this.currentNetworkOption); + await this.driver.waitUntil( + async () => { + const label = await this.getNetworksFilterLabel(); + return label !== 'All networks'; + }, + { timeout: 5000, interval: 100 }, + ); + } + + async clickOnAsset(assetName: string): Promise { + const buttons = await this.driver.findElements(this.tokenListItem); + for (const button of buttons) { + const text = await button.getText(); + if (text.includes(assetName)) { + await button.click(); + return; + } + } + throw new Error(`${assetName} button not found`); + } + + async getCurrentNetworksOptionTotal(): Promise { + console.log(`Retrieving the "Current network" option fiat value`); + const allNetworksValueElement = await this.driver.findElement( + this.currentNetworksTotal, + ); + const value = await allNetworksValueElement.getText(); + return value; + } + + async getNetworksFilterLabel(): Promise { + console.log(`Retrieving the network filter label`); + const toggle = await this.driver.findElement(this.networksToggle); + const text = await toggle.getText(); + return text; + } + + async getNumberOfAssets(): Promise { + console.log(`Returning the total number of asset items in the token list`); + const assets = await this.driver.findElements(this.tokenListItem); + return assets.length; + } + + async getTokenListNames(): Promise { + console.log(`Retrieving the list of token names`); + const tokenElements = await this.driver.findElements(this.tokenListItem); + const tokenNames = await Promise.all( + tokenElements.map(async (element) => { + return await element.getText(); + }), + ); + return tokenNames; + } + + async sortTokenList( + sortBy: 'alphabetically' | 'decliningBalance', + ): Promise { + console.log(`Sorting the token list by ${sortBy}`); + await this.driver.clickElement(this.sortByPopoverToggle); + if (sortBy === 'alphabetically') { + await this.driver.clickElement(this.sortByAlphabetically); + } else if (sortBy === 'decliningBalance') { + await this.driver.clickElement(this.sortByDecliningBalance); + } + } + + /** + * Hides a token by clicking on the token name, and confirming the hide modal. + * + * @param tokenName - The name of the token to hide. + */ + async hideToken(tokenName: string): Promise { + console.log(`Hide token ${tokenName} on homepage`); + await this.driver.clickElement({ text: tokenName, tag: 'p' }); + await this.driver.clickElement(this.assetOptionsButton); + await this.driver.clickElement(this.hideTokenButton); + await this.driver.waitForSelector(this.hideTokenConfirmationModalTitle); + await this.driver.clickElementAndWaitToDisappear( + this.hideTokenConfirmationButton, + ); + } + + async importCustomToken(tokenAddress: string, symbol: string): Promise { + console.log(`Creating custom token ${symbol} on homepage`); + await this.driver.clickElement(this.tokenOptionsButton); + await this.driver.clickElement(this.importTokensButton); + await this.driver.waitForSelector(this.importTokenModalTitle); + await this.driver.clickElement(this.customTokenModalOption); + await this.driver.waitForSelector(this.modalWarningBanner); + await this.driver.fill(this.tokenAddressInput, tokenAddress); + await this.driver.fill(this.tokenSymbolInput, symbol); + await this.driver.clickElement(this.importTokensNextButton); + await this.driver.clickElementAndWaitToDisappear( + this.confirmImportTokenButton, + ); + await this.driver.waitForSelector(this.tokenImportedSuccessMessage); + } + + async importTokenBySearch(tokenName: string) { + console.log(`Import token ${tokenName} on homepage by search`); + await this.driver.clickElement(this.tokenOptionsButton); + await this.driver.clickElement(this.importTokensButton); + await this.driver.waitForSelector(this.importTokenModalTitle); + await this.driver.fill(this.tokenSearchInput, tokenName); + await this.driver.clickElement({ text: tokenName, tag: 'p' }); + await this.driver.clickElement(this.importTokensNextButton); + await this.driver.waitForSelector(this.confirmImportTokenMessage); + await this.driver.clickElementAndWaitToDisappear( + this.confirmImportTokenButton, + ); + } + + async importMultipleTokensBySearch(tokenNames: string[]) { + console.log( + `Importing tokens ${tokenNames.join(', ')} on homepage by search`, + ); + await this.driver.clickElement(this.tokenOptionsButton); + await this.driver.clickElement(this.importTokensButton); + await this.driver.waitForSelector(this.importTokenModalTitle); + + for (const name of tokenNames) { + await this.driver.fill(this.tokenSearchInput, name); + await this.driver.clickElement({ text: name, tag: 'p' }); + } + await this.driver.clickElement(this.importTokensNextButton); + await this.driver.clickElementAndWaitToDisappear( + this.confirmImportTokenButton, + ); + } + + async openNetworksFilter(): Promise { + console.log(`Opening the network filter`); + await this.driver.clickElement(this.networksToggle); + await this.driver.waitUntil( + async () => { + return await this.driver.findElement(this.allNetworksOption); + }, + { + timeout: 5000, + interval: 100, + }, + ); + } + + async waitUntilFilterLabelIs(label: string): Promise { + console.log(`Waiting until the filter label is ${label}`); + await this.driver.waitUntil( + async () => { + const currentLabel = await this.getNetworksFilterLabel(); + return currentLabel === label; + }, + { timeout: 5000, interval: 100 }, + ); + } + + async check_networkFilterText(expectedText: string): Promise { + console.log( + `Verify the displayed account label in header is: ${expectedText}`, + ); + await this.driver.waitForSelector({ + css: this.networksToggle, + text: expectedText, + }); + } + + /** + * Checks if the specified token amount is displayed in the token list. + * + * @param tokenAmount - The token amount to be checked for. + */ + async check_tokenAmountIsDisplayed(tokenAmount: string): Promise { + console.log(`Waiting for token amount ${tokenAmount} to be displayed`); + await this.driver.waitForSelector({ + css: this.tokenAmountValue, + text: tokenAmount, + }); + } + + /** + * Checks if the specified token amount is displayed in the token details modal. + * + * @param tokenName - The name of the token to check for. + * @param tokenAmount - The token amount to be checked for. + */ + async check_tokenAmountInTokenDetailsModal( + tokenName: string, + tokenAmount: string, + ): Promise { + console.log( + `Check that token amount ${tokenAmount} is displayed in token details modal for token ${tokenName}`, + ); + await this.driver.clickElement({ + tag: 'span', + text: tokenName, + }); + await this.driver.waitForSelector({ + css: this.tokenAmountValue, + text: tokenAmount, + }); + } + + /** + * This function checks if the specified token is displayed in the token list by its name. + * + * @param tokenName - The name of the token to check for. + * @returns A promise that resolves if the specified token is displayed. + */ + async check_tokenIsDisplayed(tokenName: string): Promise { + console.log(`Waiting for token ${tokenName} to be displayed`); + await this.driver.waitForSelector({ + text: tokenName, + tag: 'p', + }); + console.log(`Token ${tokenName} is displayed.`); + } + + /** + * This function checks if the specified number of token items is displayed in the token list. + * + * @param expectedNumber - The number of token items expected to be displayed. Defaults to 1. + * @returns A promise that resolves if the expected number of token items is displayed. + */ + async check_tokenItemNumber(expectedNumber: number = 1): Promise { + console.log(`Waiting for ${expectedNumber} token items to be displayed`); + await this.driver.wait(async () => { + const tokenItemsNumber = await this.getNumberOfAssets(); + return tokenItemsNumber === expectedNumber; + }, 10000); + console.log( + `Expected number of token items ${expectedNumber} is displayed.`, + ); + } + + /** + * Checks if the token's general increase or decrease percentage is displayed correctly + * + * @param address - The token address to check + * @param expectedChange - The expected change percentage value (e.g. '+0.02%' or '-0.03%') + */ + async check_tokenGeneralChangePercentage( + address: string, + expectedChange: string, + ): Promise { + console.log( + `Checking token general change percentage for address ${address}`, + ); + const isPresent = await this.driver.isElementPresentAndVisible({ + css: this.tokenPercentage(address), + text: expectedChange, + }); + if (!isPresent) { + throw new Error( + `Token general change percentage ${expectedChange} not found for address ${address}`, + ); + } + } + + /** + * Checks if the token's percentage change element does not exist + * + * @param address - The token address to check + */ + async check_tokenGeneralChangePercentageNotPresent( + address: string, + ): Promise { + console.log( + `Checking token general change percentage is not present for address ${address}`, + ); + const isPresent = await this.driver.isElementPresent({ + css: this.tokenPercentage(address), + }); + if (isPresent) { + throw new Error( + `Token general change percentage element should not exist for address ${address}`, + ); + } + } + + /** + * Checks if the token's general increase or decrease value is displayed correctly + * + * @param expectedChangeValue - The expected change value (e.g. '+$50.00' or '-$30.00') + */ + async check_tokenGeneralChangeValue( + expectedChangeValue: string, + ): Promise { + console.log(`Checking token general change value ${expectedChangeValue}`); + const isPresent = await this.driver.isElementPresentAndVisible({ + css: this.tokenIncreaseDecreaseValue, + text: expectedChangeValue, + }); + if (!isPresent) { + throw new Error( + `Token general change value ${expectedChangeValue} not found`, + ); + } + } +} + +export default AssetListPage; diff --git a/test/e2e/page-objects/pages/home/bitcoin-homepage.ts b/test/e2e/page-objects/pages/home/bitcoin-homepage.ts new file mode 100644 index 000000000000..2f78328623f5 --- /dev/null +++ b/test/e2e/page-objects/pages/home/bitcoin-homepage.ts @@ -0,0 +1,110 @@ +import HomePage from './homepage'; + +class BitcoinHomepage extends HomePage { + protected readonly balance = + '[data-testid="coin-overview__primary-currency"]'; + + protected readonly bridgeButton = '[data-testid="coin-overview-bridge"]'; + + private readonly buySellButton = '[data-testid="coin-overview-buy"]'; + + private readonly receiveButton = '[data-testid="coin-overview-receive"]'; + + protected readonly sendButton = '[data-testid="coin-overview-send"]'; + + protected readonly swapButton = '[data-testid="coin-overview-swap"]'; + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.sendButton, + this.buySellButton, + this.receiveButton, + ]); + } catch (e) { + console.log('Timeout while waiting for bitcoin homepage to be loaded', e); + throw e; + } + console.log('Bitcoin homepage is loaded'); + } + + async startSendFlow(): Promise { + await this.driver.clickElement(this.sendButton); + } + + /** + * Checks if the bridge button is enabled on bitcoin account homepage. + * + */ + async check_isBridgeButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.bridgeButton, 1000); + } catch (e) { + console.log('Bridge button not enabled', e); + return false; + } + console.log('Bridge button is enabled'); + return true; + } + + /** + * Checks if the buy/sell button is enabled on bitcoin account homepage. + */ + async check_isBuySellButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.buySellButton, 1000); + } catch (e) { + console.log('Buy/Sell button not enabled', e); + return false; + } + console.log('Buy/Sell button is enabled'); + return true; + } + + /** + * Checks if the expected bitcoin balance is displayed on homepage. + * + * @param expectedBalance - The expected bitcoin balance to be displayed. Defaults to '0'. + */ + async check_isExpectedBitcoinBalanceDisplayed( + expectedBalance: number = 0, + ): Promise { + console.log( + `Check if expected bitcoin balance is displayed: ${expectedBalance} BTC`, + ); + await this.driver.waitForSelector({ + css: this.balance, + text: `${expectedBalance}BTC`, + }); + } + + /** + * Checks if the receive button is enabled on bitcoin account homepage. + */ + async check_isReceiveButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.receiveButton, 1000); + } catch (e) { + console.log('Receive button not enabled', e); + return false; + } + console.log('Receive button is enabled'); + return true; + } + + /** + * Checks if the swap button is enabled on bitcoin account homepage. + */ + async check_isSwapButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.swapButton, 1000); + } catch (e) { + console.log('Swap button not enabled', e); + return false; + } + console.log('Swap button is enabled'); + return true; + } +} + +export default BitcoinHomepage; diff --git a/test/e2e/page-objects/pages/home/homepage.ts b/test/e2e/page-objects/pages/home/homepage.ts new file mode 100644 index 000000000000..708ae5ac0277 --- /dev/null +++ b/test/e2e/page-objects/pages/home/homepage.ts @@ -0,0 +1,262 @@ +import { Driver } from '../../../webdriver/driver'; +import { Ganache } from '../../../seeder/ganache'; +import { getCleanAppState } from '../../../helpers'; +import HeaderNavbar from '../header-navbar'; + +class HomePage { + protected driver: Driver; + + public headerNavbar: HeaderNavbar; + + private readonly activityTab = { + testId: 'account-overview__activity-tab', + }; + + protected readonly balance: string = + '[data-testid="eth-overview__primary-currency"]'; + + private readonly basicFunctionalityOffWarningMessage = { + text: 'Basic functionality is off', + css: '.mm-banner-alert', + }; + + protected readonly bridgeButton: string = + '[data-testid="eth-overview-bridge"]'; + + private readonly closeUseNetworkNotificationModalButton = { + text: 'Got it', + tag: 'h6', + }; + + private readonly erc20TokenDropdown = { + testId: 'import-token-button', + }; + + private readonly nftTab = { + testId: 'account-overview__nfts-tab', + }; + + private readonly popoverBackground = '.popover-bg'; + + private readonly popoverCloseButton = { + testId: 'popover-close', + }; + + private readonly portfolioLink = '[data-testid="portfolio-link"]'; + + private readonly privacyBalanceToggle = { + testId: 'sensitive-toggle', + }; + + protected readonly sendButton: string = '[data-testid="eth-overview-send"]'; + + protected readonly swapButton: string = + '[data-testid="token-overview-button-swap"]'; + + private readonly refreshErc20Tokens = { + testId: 'refreshList', + }; + + private readonly tokensTab = { + testId: 'account-overview__asset-tab', + }; + + constructor(driver: Driver) { + this.driver = driver; + this.headerNavbar = new HeaderNavbar(driver); + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.sendButton, + this.activityTab, + this.tokensTab, + ]); + } catch (e) { + console.log('Timeout while waiting for home page to be loaded', e); + throw e; + } + console.log('Home page is loaded'); + } + + async closePopover(): Promise { + console.log('Closing popover'); + await this.driver.clickElement(this.popoverCloseButton); + } + + async closeUseNetworkNotificationModal(): Promise { + // We need to use clickElementSafe + assertElementNotPresent as sometimes the network dialog doesn't appear, as per this issue (#25788) + // TODO: change the 2 actions for clickElementAndWaitToDisappear, once the issue is fixed + await this.driver.assertElementNotPresent(this.popoverBackground); + await this.driver.clickElementSafe( + this.closeUseNetworkNotificationModalButton, + ); + await this.driver.assertElementNotPresent( + this.closeUseNetworkNotificationModalButton, + ); + } + + async goToActivityList(): Promise { + console.log(`Open activity tab on homepage`); + await this.driver.clickElement(this.activityTab); + } + + async goToNftTab(): Promise { + console.log(`Go to NFT tab on homepage`); + await this.driver.clickElement(this.nftTab); + } + + async openPortfolioPage(): Promise { + console.log(`Open portfolio page on homepage`); + await this.driver.clickElement(this.portfolioLink); + } + + async refreshErc20TokenList(): Promise { + console.log(`Refresh the ERC20 token list`); + await this.driver.clickElement(this.erc20TokenDropdown); + await this.driver.clickElement(this.refreshErc20Tokens); + } + + async startSendFlow(): Promise { + await this.driver.clickElement(this.sendButton); + } + + async togglePrivacyBalance(): Promise { + await this.driver.clickElement(this.privacyBalanceToggle); + } + + /** + * Checks if the toaster message for adding a network is displayed on the homepage. + * + * @param networkName - The name of the network that was added. + */ + async check_addNetworkMessageIsDisplayed(networkName: string): Promise { + console.log( + `Check the toaster message for adding network ${networkName} is displayed on homepage`, + ); + await this.driver.waitForSelector({ + tag: 'h6', + text: `“${networkName}” was successfully added!`, + }); + } + + async check_basicFunctionalityOffWarnigMessageIsDisplayed(): Promise { + console.log( + 'Check if basic functionality off warning message is displayed on homepage', + ); + await this.driver.waitForSelector(this.basicFunctionalityOffWarningMessage); + } + + async check_disabledButtonTooltip(tooltipText: string): Promise { + console.log(`Check if disabled button tooltip is displayed on homepage`); + await this.driver.waitForSelector( + `.icon-button--disabled [data-tooltipped][data-original-title="${tooltipText}"]`, + ); + } + + /** + * Checks if the toaster message for editing a network is displayed on the homepage. + * + * @param networkName - The name of the network that was edited. + */ + async check_editNetworkMessageIsDisplayed( + networkName: string, + ): Promise { + console.log( + `Check the toaster message for editing network ${networkName} is displayed on homepage`, + ); + await this.driver.waitForSelector({ + tag: 'h6', + text: `“${networkName}” was successfully edited!`, + }); + } + + /** + * Checks if the expected balance is displayed on homepage. + * + * @param expectedBalance - The expected balance to be displayed. Defaults to '0'. + * @param symbol - The symbol of the currency or token. Defaults to 'ETH'. + */ + async check_expectedBalanceIsDisplayed( + expectedBalance: string = '0', + symbol: string = 'ETH', + ): Promise { + try { + await this.driver.waitForSelector({ + css: this.balance, + text: expectedBalance, + }); + } catch (e) { + const balance = await this.driver.waitForSelector(this.balance); + const currentBalance = parseFloat(await balance.getText()); + const errorMessage = `Expected balance ${expectedBalance} ${symbol}, got balance ${currentBalance} ${symbol}`; + console.log(errorMessage, e); + throw e; + } + console.log( + `Expected balance ${expectedBalance} ${symbol} is displayed on homepage`, + ); + } + + /** + * This function checks if account syncing has been successfully completed at least once. + */ + async check_hasAccountSyncingSyncedAtLeastOnce(): Promise { + console.log('Check if account syncing has synced at least once'); + await this.driver.wait(async () => { + const uiState = await getCleanAppState(this.driver); + return uiState.metamask.hasAccountSyncingSyncedAtLeastOnce === true; + }, 10000); + } + + async check_ifBridgeButtonIsClickable(): Promise { + try { + await this.driver.findClickableElement(this.bridgeButton, 1000); + } catch (e) { + console.log('Bridge button not clickable', e); + return false; + } + console.log('Bridge button is clickable'); + return true; + } + + async check_ifSendButtonIsClickable(): Promise { + try { + await this.driver.findClickableElement(this.sendButton, 1000); + } catch (e) { + console.log('Send button not clickable', e); + return false; + } + console.log('Send button is clickable'); + return true; + } + + async check_ifSwapButtonIsClickable(): Promise { + try { + await this.driver.findClickableElement(this.swapButton, 1000); + } catch (e) { + console.log('Swap button not clickable', e); + return false; + } + console.log('Swap button is clickable'); + return true; + } + + async check_localBlockchainBalanceIsDisplayed( + localBlockchainServer?: Ganache, + address = null, + ): Promise { + let expectedBalance: string; + if (localBlockchainServer) { + expectedBalance = ( + await localBlockchainServer.getBalance(address) + ).toString(); + } else { + expectedBalance = '0'; + } + await this.check_expectedBalanceIsDisplayed(expectedBalance); + } +} + +export default HomePage; diff --git a/test/e2e/page-objects/pages/home/nft-list.ts b/test/e2e/page-objects/pages/home/nft-list.ts new file mode 100644 index 000000000000..ca8211417daf --- /dev/null +++ b/test/e2e/page-objects/pages/home/nft-list.ts @@ -0,0 +1,89 @@ +import { Driver } from '../../../webdriver/driver'; + +class NftListPage { + private readonly driver: Driver; + + private readonly confirmImportNftButton = + '[data-testid="import-nfts-modal-import-button"]'; + + private readonly importNftAddressInput = '#address'; + + private readonly importNftButton = '[data-testid="import-nft-button"]'; + + private readonly importNftModalTitle = { text: 'Import NFT', tag: 'header' }; + + private readonly importNftTokenIdInput = '#token-id'; + + private readonly nftIconOnActivityList = '[data-testid="nft-item"]'; + + private readonly successImportNftMessage = { + text: 'NFT was successfully added!', + tag: 'h6', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async clickNFTIconOnActivityList() { + await this.driver.clickElement(this.nftIconOnActivityList); + } + + /** + * Imports an NFT by entering the NFT contract address and token ID + * + * @param nftContractAddress - The address of the NFT contract to import + * @param id - The ID of the NFT to import + * @param expectedErrorMessage - Expected error message if the import should fail + */ + async importNft( + nftContractAddress: string, + id: string, + expectedErrorMessage?: string, + ) { + await this.driver.clickElement(this.importNftButton); + await this.driver.waitForSelector(this.importNftModalTitle); + await this.driver.fill(this.importNftAddressInput, nftContractAddress); + await this.driver.fill(this.importNftTokenIdInput, id); + if (expectedErrorMessage) { + await this.driver.clickElement(this.confirmImportNftButton); + await this.driver.waitForSelector({ + tag: 'p', + text: expectedErrorMessage, + }); + } else { + await this.driver.clickElementAndWaitToDisappear( + this.confirmImportNftButton, + ); + } + } + + async check_nftImageIsDisplayed(): Promise { + console.log('Check that NFT image is displayed in NFT tab on homepage'); + await this.driver.waitForSelector(this.nftIconOnActivityList); + } + + /** + * Checks if the NFT item with the specified name is displayed in the homepage nft tab. + * + * @param nftName - The name of the NFT to check for. + */ + async check_nftNameIsDisplayed(nftName: string): Promise { + console.log( + `Check that NFT item ${nftName} is displayed in NFT tab on homepage`, + ); + await this.driver.waitForSelector({ + tag: 'p', + text: nftName, + }); + } + + async check_successImportNftMessageIsDisplayed(): Promise { + console.log( + 'Check that success imported NFT message is displayed on homepage', + ); + await this.driver.waitForSelector(this.successImportNftMessage); + } +} + +export default NftListPage; diff --git a/test/e2e/page-objects/pages/home/non-evm-homepage.ts b/test/e2e/page-objects/pages/home/non-evm-homepage.ts new file mode 100644 index 000000000000..f0a586c02b3a --- /dev/null +++ b/test/e2e/page-objects/pages/home/non-evm-homepage.ts @@ -0,0 +1,69 @@ +import HomePage from './homepage'; + +class NonEvmHomepage extends HomePage { + protected readonly buySellButton = '[data-testid="coin-overview-buy"]'; + + protected readonly receiveButton = '[data-testid="coin-overview-receive"]'; + + protected readonly sendButton = '[data-testid="coin-overview-send"]'; + + protected readonly swapButton = '[data-testid="token-overview-button-swap"]'; + + protected readonly bridgeButton = '[data-testid="coin-overview-bridge"]'; + + /** + * Clicks the send button on the non-EVM account homepage. + */ + async clickOnSendButton(): Promise { + await this.driver.waitForControllersLoaded(); + await this.driver.clickElement(this.sendButton); + } + + /** + * Checks if the expected balance is displayed on homepage. + * + * @param balance + */ + async check_getBalance(balance: string): Promise { + console.log(`Getting Non-evm account balance`); + await this.driver.waitForSelector( + { + css: 'div', + text: balance, + }, + { timeout: 5000 }, + ); + } + + /** + * Checks if the receive button is enabled on a non-evm account homepage. + */ + async check_isReceiveButtonEnabled(): Promise { + try { + await this.driver.waitForSelector(this.receiveButton, { timeout: 5000 }); + } catch (e) { + console.log('Receive button not enabled', e); + return false; + } + console.log('Receive button is enabled'); + return true; + } + + /** + * Checks if the buy/sell button is enabled on a non-evm account homepage. + */ + async check_ifBuySellButtonIsClickable(): Promise { + try { + await this.driver.waitForSelector(this.buySellButton, { timeout: 5000 }); + const buySellButton = await this.driver.findClickableElement( + this.buySellButton, + ); + return await buySellButton.isEnabled(); + } catch (e) { + console.log('Buy/Sell button not enabled', e); + return false; + } + } +} + +export default NonEvmHomepage; diff --git a/test/e2e/page-objects/pages/homepage.ts b/test/e2e/page-objects/pages/homepage.ts deleted file mode 100644 index c5c4d5369d4e..000000000000 --- a/test/e2e/page-objects/pages/homepage.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { strict as assert } from 'assert'; -import { Driver } from '../../webdriver/driver'; -import { Ganache } from '../../seeder/ganache'; -import { getCleanAppState } from '../../helpers'; -import HeaderNavbar from './header-navbar'; - -class HomePage { - private driver: Driver; - - public headerNavbar: HeaderNavbar; - - private readonly activityTab = - '[data-testid="account-overview__activity-tab"]'; - - private readonly balance = '[data-testid="eth-overview__primary-currency"]'; - - private readonly basicFunctionalityOffWarningMessage = { - text: 'Basic functionality is off', - css: '.mm-banner-alert', - }; - - private readonly closeUseNetworkNotificationModalButton = { - text: 'Got it', - tag: 'h6', - }; - - private readonly completedTransactions = '[data-testid="activity-list-item"]'; - - private readonly confirmedTransactions = { - text: 'Confirmed', - css: '.transaction-status-label--confirmed', - }; - - private readonly failedTransactions = { - text: 'Failed', - css: '.transaction-status-label--failed', - }; - - private readonly popoverBackground = '.popover-bg'; - - private readonly sendButton = '[data-testid="eth-overview-send"]'; - - private readonly tokensTab = '[data-testid="account-overview__asset-tab"]'; - - private readonly transactionAmountsInActivity = - '[data-testid="transaction-list-item-primary-currency"]'; - - // NFT selectors - private readonly confirmImportNftButton = - '[data-testid="import-nfts-modal-import-button"]'; - - private readonly importNftAddressInput = '#address'; - - private readonly importNftButton = '[data-testid="import-nft-button"]'; - - private readonly importNftModalTitle = { text: 'Import NFT', tag: 'header' }; - - private readonly importNftTokenIdInput = '#token-id'; - - private readonly nftIconOnActivityList = '[data-testid="nft-item"]'; - - private readonly nftTab = '[data-testid="account-overview__nfts-tab"]'; - - private readonly successImportNftMessage = { - text: 'NFT was successfully added!', - tag: 'h6', - }; - - constructor(driver: Driver) { - this.driver = driver; - this.headerNavbar = new HeaderNavbar(driver); - } - - async check_pageIsLoaded(): Promise { - try { - await this.driver.waitForMultipleSelectors([ - this.sendButton, - this.activityTab, - this.tokensTab, - ]); - } catch (e) { - console.log('Timeout while waiting for home page to be loaded', e); - throw e; - } - console.log('Home page is loaded'); - } - - async closeUseNetworkNotificationModal(): Promise { - // We need to use clickElementSafe + assertElementNotPresent as sometimes the network dialog doesn't appear, as per this issue (#25788) - // TODO: change the 2 actions for clickElementAndWaitToDisappear, once the issue is fixed - await this.driver.assertElementNotPresent(this.popoverBackground); - await this.driver.clickElementSafe( - this.closeUseNetworkNotificationModalButton, - ); - await this.driver.assertElementNotPresent( - this.closeUseNetworkNotificationModalButton, - ); - } - - async goToActivityList(): Promise { - console.log(`Open activity tab on homepage`); - await this.driver.clickElement(this.activityTab); - } - - async goToNftTab(): Promise { - console.log(`Go to NFT tab on homepage`); - await this.driver.clickElement(this.nftTab); - } - - async clickNFTIconOnActivityList() { - await this.driver.clickElement(this.nftIconOnActivityList); - } - - async startSendFlow(): Promise { - await this.driver.clickElement(this.sendButton); - } - - /** - * Imports an NFT by entering the NFT contract address and token ID - * - * @param nftContractAddress - The address of the NFT contract to import - * @param id - The ID of the NFT to import - * @param expectedErrorMessage - Expected error message if the import should fail - */ - async importNft( - nftContractAddress: string, - id: string, - expectedErrorMessage?: string, - ) { - await this.driver.clickElement(this.importNftButton); - await this.driver.waitForSelector(this.importNftModalTitle); - await this.driver.fill(this.importNftAddressInput, nftContractAddress); - await this.driver.fill(this.importNftTokenIdInput, id); - if (expectedErrorMessage) { - await this.driver.clickElement(this.confirmImportNftButton); - await this.driver.waitForSelector({ - tag: 'p', - text: expectedErrorMessage, - }); - } else { - await this.driver.clickElementAndWaitToDisappear( - this.confirmImportNftButton, - ); - } - } - - /** - * Checks if the toaster message for adding a network is displayed on the homepage. - * - * @param networkName - The name of the network that was added. - */ - async check_addNetworkMessageIsDisplayed(networkName: string): Promise { - console.log( - `Check the toaster message for adding network ${networkName} is displayed on homepage`, - ); - await this.driver.waitForSelector({ - tag: 'h6', - text: `“${networkName}” was successfully added!`, - }); - } - - async check_basicFunctionalityOffWarnigMessageIsDisplayed(): Promise { - console.log( - 'Check if basic functionality off warning message is displayed on homepage', - ); - await this.driver.waitForSelector(this.basicFunctionalityOffWarningMessage); - } - - /** - * This function checks the specified number of completed transactions are displayed in the activity list on the homepage. - * It waits up to 10 seconds for the expected number of completed transactions to be visible. - * - * @param expectedNumber - The number of completed transactions expected to be displayed in the activity list. Defaults to 1. - * @returns A promise that resolves if the expected number of completed transactions is displayed within the timeout period. - */ - async check_completedTxNumberDisplayedInActivity( - expectedNumber: number = 1, - ): Promise { - console.log( - `Wait for ${expectedNumber} completed transactions to be displayed in activity list`, - ); - await this.driver.wait(async () => { - const completedTxs = await this.driver.findElements( - this.completedTransactions, - ); - return completedTxs.length === expectedNumber; - }, 10000); - console.log( - `${expectedNumber} completed transactions found in activity list on homepage`, - ); - } - - /** - * This function checks if the specified number of confirmed transactions are displayed in the activity list on homepage. - * It waits up to 10 seconds for the expected number of confirmed transactions to be visible. - * - * @param expectedNumber - The number of confirmed transactions expected to be displayed in activity list. Defaults to 1. - * @returns A promise that resolves if the expected number of confirmed transactions is displayed within the timeout period. - */ - async check_confirmedTxNumberDisplayedInActivity( - expectedNumber: number = 1, - ): Promise { - console.log( - `Wait for ${expectedNumber} confirmed transactions to be displayed in activity list`, - ); - await this.driver.wait(async () => { - const confirmedTxs = await this.driver.findElements( - this.confirmedTransactions, - ); - return confirmedTxs.length === expectedNumber; - }, 10000); - console.log( - `${expectedNumber} confirmed transactions found in activity list on homepage`, - ); - } - - async check_nftImageIsDisplayed(): Promise { - console.log('Check that NFT image is displayed in NFT tab on homepage'); - await this.driver.waitForSelector(this.nftIconOnActivityList); - } - - /** - * Checks if the toaster message for editing a network is displayed on the homepage. - * - * @param networkName - The name of the network that was edited. - */ - async check_editNetworkMessageIsDisplayed( - networkName: string, - ): Promise { - console.log( - `Check the toaster message for editing network ${networkName} is displayed on homepage`, - ); - await this.driver.waitForSelector({ - tag: 'h6', - text: `“${networkName}” was successfully edited!`, - }); - } - - /** - * Checks if the expected balance is displayed on homepage. - * - * @param expectedBalance - The expected balance to be displayed. Defaults to '0'. - */ - async check_expectedBalanceIsDisplayed( - expectedBalance: string = '0', - ): Promise { - try { - await this.driver.waitForSelector({ - css: this.balance, - text: `${expectedBalance} ETH`, - }); - } catch (e) { - const balance = await this.driver.waitForSelector(this.balance); - const currentBalance = parseFloat(await balance.getText()); - const errorMessage = `Expected balance ${expectedBalance} ETH, got balance ${currentBalance} ETH`; - console.log(errorMessage, e); - throw e; - } - console.log( - `Expected balance ${expectedBalance} ETH is displayed on homepage`, - ); - } - - /** - * This function checks if the specified number of failed transactions are displayed in the activity list on homepage. - * It waits up to 10 seconds for the expected number of failed transactions to be visible. - * - * @param expectedNumber - The number of failed transactions expected to be displayed in activity list. Defaults to 1. - * @returns A promise that resolves if the expected number of failed transactions is displayed within the timeout period. - */ - async check_failedTxNumberDisplayedInActivity( - expectedNumber: number = 1, - ): Promise { - console.log( - `Wait for ${expectedNumber} failed transactions to be displayed in activity list`, - ); - await this.driver.wait(async () => { - const failedTxs = await this.driver.findElements(this.failedTransactions); - return failedTxs.length === expectedNumber; - }, 10000); - console.log( - `${expectedNumber} failed transactions found in activity list on homepage`, - ); - } - - async check_localBlockchainBalanceIsDisplayed( - localBlockchainServer?: Ganache, - address = null, - ): Promise { - let expectedBalance: string; - if (localBlockchainServer) { - expectedBalance = ( - await localBlockchainServer.getBalance(address) - ).toString(); - } else { - expectedBalance = '0'; - } - await this.check_expectedBalanceIsDisplayed(expectedBalance); - } - - /** - * Checks if the NFT item with the specified name is displayed in the homepage nft tab. - * - * @param nftName - The name of the NFT to check for. - */ - async check_nftNameIsDisplayed(nftName: string): Promise { - console.log( - `Check that NFT item ${nftName} is displayed in NFT tab on homepage`, - ); - await this.driver.waitForSelector({ - tag: 'h5', - text: nftName, - }); - } - - async check_successImportNftMessageIsDisplayed(): Promise { - console.log( - 'Check that success imported NFT message is displayed on homepage', - ); - await this.driver.waitForSelector(this.successImportNftMessage); - } - - /** - * This function checks if a specified transaction amount at the specified index matches the expected one. - * - * @param expectedAmount - The expected transaction amount to be displayed. Defaults to '-1 ETH'. - * @param expectedNumber - The 1-based index of the transaction in the activity list whose amount is to be checked. - * Defaults to 1, indicating the first transaction in the list. - * @returns A promise that resolves if the transaction amount at the specified index matches the expected amount. - * The promise is rejected if the amounts do not match or if an error occurs during the process. - * @example - * // To check if the third transaction in the activity list displays an amount of '2 ETH' - * await check_txAmountInActivity('2 ETH', 3); - */ - async check_txAmountInActivity( - expectedAmount: string = '-1 ETH', - expectedNumber: number = 1, - ): Promise { - const transactionAmounts = await this.driver.findElements( - this.transactionAmountsInActivity, - ); - const transactionAmountsText = await transactionAmounts[ - expectedNumber - 1 - ].getText(); - assert.equal( - transactionAmountsText, - expectedAmount, - `${transactionAmountsText} is displayed as transaction amount instead of ${expectedAmount} for transaction ${expectedNumber}`, - ); - console.log( - `Amount for transaction ${expectedNumber} is displayed as ${expectedAmount}`, - ); - } - - /** - * This function checks if account syncing has been successfully completed at least once. - */ - async check_hasAccountSyncingSyncedAtLeastOnce(): Promise { - console.log('Check if account syncing has synced at least once'); - await this.driver.wait(async () => { - const uiState = await getCleanAppState(this.driver); - return uiState.metamask.hasAccountSyncingSyncedAtLeastOnce === true; - }, 10000); - } -} - -export default HomePage; diff --git a/test/e2e/page-objects/pages/permission/permission-list-page.ts b/test/e2e/page-objects/pages/permission/permission-list-page.ts new file mode 100644 index 000000000000..10f214fd8da6 --- /dev/null +++ b/test/e2e/page-objects/pages/permission/permission-list-page.ts @@ -0,0 +1,50 @@ +import { Driver } from '../../../webdriver/driver'; + +/** + * Represents the permissions list page. + * This page allows users to view permissions for connected sites. + */ +class PermissionListPage { + private driver: Driver; + + private readonly permissionsPage = '[data-testid="permissions-page"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForSelector(this.permissionsPage); + } catch (e) { + console.log( + 'Timeout while waiting for permission list page to be loaded', + e, + ); + throw e; + } + console.log('Permission list page is loaded'); + } + + /** + * Open permission page for site + * + * @param site - Site to open + */ + async openPermissionPageForSite(site: string): Promise { + console.log('Open permission page for site', site); + await this.driver.clickElement({ text: site, tag: 'p' }); + } + + /** + * Check if account is connected to site + * + * @param site - Site to check + */ + async check_connectedToSite(site: string): Promise { + console.log('Check if account is connected to site', site); + await this.driver.waitForSelector({ text: site, tag: 'p' }); + } +} + +export default PermissionListPage; diff --git a/test/e2e/page-objects/pages/permission/site-permission-page.ts b/test/e2e/page-objects/pages/permission/site-permission-page.ts new file mode 100644 index 000000000000..9f7ae5d778ff --- /dev/null +++ b/test/e2e/page-objects/pages/permission/site-permission-page.ts @@ -0,0 +1,125 @@ +import { Driver } from '../../../webdriver/driver'; + +/** + * Represents the site permission page. + * This page allows users to view and manage permissions for a connected site. + */ +class SitePermissionPage { + private driver: Driver; + + private readonly confirmEditAccountsButton = + '[data-testid="connect-more-accounts-button"]'; + + private readonly confirmEditNetworksButton = + '[data-testid="connect-more-chains-button"]'; + + private readonly connectedAccountsInfo = { + text: 'See your accounts and suggest transactions', + tag: 'p', + }; + + private readonly editAccountsModalTitle = { + text: 'Edit accounts', + tag: 'h4', + }; + + private readonly editButton = '[data-testid="edit"]'; + + private readonly editNetworksModalTitle = { + text: 'Edit networks', + tag: 'h4', + }; + + private readonly enabledNetworksInfo = { + text: 'Use your enabled networks', + tag: 'p', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + /** + * Check if site permission page is loaded + * + * @param site - Site to check + */ + async check_pageIsLoaded(site: string): Promise { + try { + await this.driver.waitForSelector(this.connectedAccountsInfo); + await this.driver.waitForSelector(this.enabledNetworksInfo); + await this.driver.waitForSelector({ text: site, tag: 'span' }); + } catch (e) { + console.log( + 'Timeout while waiting for site permission page to be loaded', + e, + ); + throw e; + } + console.log('Site permission page is loaded'); + } + + /** + * Edit permissions for accounts on site permission page + * + * @param accountLabels - Account labels to edit + */ + async editPermissionsForAccount(accountLabels: string[]): Promise { + console.log(`Edit permissions for accounts: ${accountLabels}`); + const editButtons = await this.driver.findElements(this.editButton); + await editButtons[0].click(); + await this.driver.waitForSelector(this.editAccountsModalTitle); + for (const accountLabel of accountLabels) { + await this.driver.clickElement({ text: accountLabel, tag: 'button' }); + } + await this.driver.clickElementAndWaitToDisappear( + this.confirmEditAccountsButton, + ); + } + + /** + * Edit permissions for networks on site permission page + * + * @param networkNames - Network names to edit + */ + async editPermissionsForNetwork(networkNames: string[]): Promise { + console.log(`Edit permissions for networks: ${networkNames}`); + const editButtons = await this.driver.findElements(this.editButton); + await editButtons[1].click(); + await this.driver.waitForSelector(this.editNetworksModalTitle); + for (const networkName of networkNames) { + await this.driver.clickElement({ text: networkName, tag: 'p' }); + } + await this.driver.clickElementAndWaitToDisappear( + this.confirmEditNetworksButton, + ); + } + + /** + * Check if the number of connected accounts is correct + * + * @param number - Expected number of connected accounts + */ + async check_connectedAccountsNumber(number: number): Promise { + console.log(`Check that the number of connected accounts is: ${number}`); + await this.driver.waitForSelector({ + text: `${number} accounts connected`, + tag: 'span', + }); + } + + /** + * Check if the number of connected networks is correct + * + * @param number - Expected number of connected networks + */ + async check_connectedNetworksNumber(number: number): Promise { + console.log(`Check that the number of connected networks is: ${number}`); + await this.driver.waitForSelector({ + text: `${number} networks connected`, + tag: 'span', + }); + } +} + +export default SitePermissionPage; diff --git a/test/e2e/page-objects/pages/send/bitcoin-review-tx-page.ts b/test/e2e/page-objects/pages/send/bitcoin-review-tx-page.ts new file mode 100644 index 000000000000..0ce42992c45f --- /dev/null +++ b/test/e2e/page-objects/pages/send/bitcoin-review-tx-page.ts @@ -0,0 +1,42 @@ +import { Driver } from '../../../webdriver/driver'; + +class BitcoinReviewTxPage { + private driver: Driver; + + private readonly reviewPageTitle = { + text: 'Review', + tag: 'h4', + }; + + private readonly sendButton = { + text: 'Send', + tag: 'span', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.reviewPageTitle, + this.sendButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for bitcoin review tx page to be loaded', + e, + ); + throw e; + } + console.log('Bitcoin review tx page is loaded'); + } + + async clickSendButton() { + console.log('Click send button on bitcoin review tx page'); + await this.driver.clickElementAndWaitToDisappear(this.sendButton); + } +} + +export default BitcoinReviewTxPage; diff --git a/test/e2e/page-objects/pages/send/bitcoin-send-page.ts b/test/e2e/page-objects/pages/send/bitcoin-send-page.ts new file mode 100644 index 000000000000..226f0926f626 --- /dev/null +++ b/test/e2e/page-objects/pages/send/bitcoin-send-page.ts @@ -0,0 +1,82 @@ +import { Driver } from '../../../webdriver/driver'; + +class BitcoinSendPage { + private driver: Driver; + + private readonly amountInputField = `input[placeholder="Enter amount to send"]`; + + private readonly maxAmountButton = { + text: 'Max', + tag: 'button', + }; + + private readonly recipientInputField = `input[placeholder="Enter receiving address"]`; + + private readonly reviewButton = { + text: 'Review', + tag: 'span', + }; + + private readonly totalFee = { + text: 'Total', + tag: 'p', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.recipientInputField, + this.amountInputField, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for bitcoin send page to be loaded', + e, + ); + throw e; + } + console.log('Bitcoin send page is loaded'); + } + + async clickReviewButton() { + console.log('Click review button on send bitcoin screen'); + await this.driver.waitForSelector(this.totalFee); + await this.driver.clickElement(this.reviewButton); + } + + async fillAmount(amount: string): Promise { + console.log(`Fill amount input with ${amount} on send bitcoin screen`); + await this.driver.pasteIntoField(this.amountInputField, amount); + } + + async fillRecipientAddress(recipient: string) { + console.log( + `Fill recipient address with ${recipient} on send bitcoin screen`, + ); + await this.driver.pasteIntoField(this.recipientInputField, recipient); + } + + async selectMaxAmount() { + console.log('Select max amount on send bitcoin screen'); + await this.driver.clickElement(this.maxAmountButton); + } + + /** + * Verifies that a specific amount is displayed on the send bitcoin screen. + * + * @param amount - The expected amount to validate. + */ + async check_amountIsDisplayed(amount: string) { + console.log(`Check amount ${amount} is displayed on send bitcoin screen`); + await this.driver.waitForSelector({ + text: amount, + tag: 'p', + }); + } +} + +export default BitcoinSendPage; diff --git a/test/e2e/page-objects/pages/send/send-token-page.ts b/test/e2e/page-objects/pages/send/send-token-page.ts index 29222ed3d48c..6b3dd2a78d78 100644 --- a/test/e2e/page-objects/pages/send/send-token-page.ts +++ b/test/e2e/page-objects/pages/send/send-token-page.ts @@ -1,4 +1,5 @@ import { strict as assert } from 'assert'; +import { WebElement } from 'selenium-webdriver'; import { Driver } from '../../../webdriver/driver'; class SendTokenPage { @@ -11,11 +12,18 @@ class SendTokenPage { tag: 'button', }; + private readonly cancelButton = { + text: 'Cancel', + tag: 'button', + }; + private readonly ensAddressAsRecipient = '[data-testid="ens-input-selected"]'; private readonly ensResolvedName = '[data-testid="multichain-send-page__recipient__item__title"]'; + private readonly assetValue = '[data-testid="account-value-and-suffix"]'; + private readonly inputAmount = '[data-testid="currency-input"]'; private readonly inputNFTAmount = '[data-testid="nft-input"]'; @@ -30,10 +38,20 @@ class SendTokenPage { private readonly tokenListButton = '[data-testid="multichain-token-list-button"]'; + private readonly toastText = '.toast-text'; + + private readonly warning = + '[data-testid="send-warning"] .mm-box--min-width-0 span'; + constructor(driver: Driver) { this.driver = driver; } + async getAssetPickerItems(): Promise { + console.log('Retrieving asset picker items'); + return this.driver.findElements(this.tokenListButton); + } + async check_pageIsLoaded(): Promise { try { await this.driver.waitForMultipleSelectors([ @@ -59,6 +77,22 @@ class SendTokenPage { await elements[1].click(); } + async checkAccountValueAndSuffix(value: string): Promise { + console.log(`Checking if account value and suffix is ${value}`); + const element = await this.driver.waitForSelector(this.assetValue); + const text = await element.getText(); + assert.equal( + text, + value, + `Expected account value and suffix to be ${value}, got ${text}`, + ); + console.log(`Account value and suffix is ${value}`); + } + + async clickCancelButton(): Promise { + await this.driver.clickElement(this.cancelButton); + } + async fillAmount(amount: string): Promise { console.log(`Fill amount input with ${amount} on send token screen`); const inputAmount = await this.driver.waitForSelector(this.inputAmount); @@ -74,6 +108,16 @@ class SendTokenPage { ); } + async check_networkChange(networkName: string): Promise { + const toastTextElement = await this.driver.findElement(this.toastText); + const toastText = await toastTextElement.getText(); + assert.equal( + toastText, + `You're now using ${networkName}`, + 'Toast text is correct', + ); + } + async fillNFTAmount(amount: string) { await this.driver.pasteIntoField(this.inputNFTAmount, amount); } @@ -155,6 +199,22 @@ class SendTokenPage { text: address, }); } + + /** + * Verifies that a specific warning message is displayed on the send token screen. + * + * @param warningText - The expected warning text to validate against. + * @returns A promise that resolves if the warning message matches the expected text. + * @throws Assertion error if the warning message does not match the expected text. + */ + async check_warningMessage(warningText: string): Promise { + console.log(`Checking if warning message "${warningText}" is displayed`); + await this.driver.waitForSelector({ + css: this.warning, + text: warningText, + }); + console.log('Warning message validation successful'); + } } export default SendTokenPage; diff --git a/test/e2e/page-objects/pages/send/solana-confirm-tx-page.ts b/test/e2e/page-objects/pages/send/solana-confirm-tx-page.ts new file mode 100644 index 000000000000..85086cd36659 --- /dev/null +++ b/test/e2e/page-objects/pages/send/solana-confirm-tx-page.ts @@ -0,0 +1,90 @@ +import { Driver } from '../../../webdriver/driver'; + +class ConfirmSolanaTxPage { + private driver: Driver; + + private readonly toAddressInput = '#send-to'; + + private readonly sendButton = { + text: 'Send', + tag: 'span', + }; + + private readonly cancelButton = { + text: 'Cancel', + tag: 'span', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async checkAmountDisplayed( + amount: string, + currency: string = 'SOL', + ): Promise { + try { + await this.driver.waitForSelector({ + text: `Sending ${amount} ${currency}`, + tag: 'h2', + }); + return true; + } catch (err) { + console.log('Amount summary text incorrect'); + return false; + } + } + + async isTransactionDetailDisplayed(text: string): Promise { + const detail = await this.driver.findElement( + { + text, + tag: 'p', + }, + 200, + ); + return await detail.isDisplayed(); + } + + async setToAddress(toAddress: string): Promise { + await this.driver.pasteIntoField(this.toAddressInput, toAddress); + } + + /** + * Clicks the send button on the Solana transaction confirmation page + */ + async clickOnSend(): Promise { + const sendButton = await this.driver.findElement(this.sendButton); + await sendButton.click(); + } + + async isSendButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.sendButton, 1000); + } catch (e) { + console.log('Send button not enabled', e); + return false; + } + console.log('Send button is enabled'); + return true; + } + + async isInsufficientBalanceDisplayed(): Promise { + try { + await this.driver.findClickableElement( + { + text: 'Insufficient balance', + tag: 'p', + }, + 1000, + ); + } catch (e) { + console.log('Insufficient balance message not displayed', e); + return false; + } + console.log('Insufficient balance message displayed'); + return true; + } +} + +export default ConfirmSolanaTxPage; diff --git a/test/e2e/page-objects/pages/send/solana-send-page.ts b/test/e2e/page-objects/pages/send/solana-send-page.ts new file mode 100644 index 000000000000..e048bd5c63df --- /dev/null +++ b/test/e2e/page-objects/pages/send/solana-send-page.ts @@ -0,0 +1,98 @@ +import { Driver } from '../../../webdriver/driver'; + +class SendSolanaPage { + private driver: Driver; + + private readonly sendAmountInput = '#send-amount-input'; + + private readonly toAddressInput = '#send-to'; + + private readonly continueButton = { + text: 'Continue', + tag: 'button', + }; + + private readonly swapCurrencyButton = '#send-swap-currency'; + + private readonly cancelButton = { + text: 'Cancel', + tag: 'button', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async clickOnSwapCurrencyButton(): Promise { + await this.driver.waitForControllersLoaded(); + const swapCurrencyButton = await this.driver.waitForSelector( + this.swapCurrencyButton, + { timeout: 10000 }, + ); + await swapCurrencyButton.click(); + } + + async check_validationErrorAppears( + validationErrorText: string, + ): Promise { + try { + await this.driver.waitForSelector( + { + text: validationErrorText, + tag: 'p', + }, + { timeout: 5000 }, + ); + return true; + } catch (e) { + console.log(`${validationErrorText} is not displayed`); + return false; + } + } + + async setAmount(amount: string): Promise { + await this.driver.waitForControllersLoaded(); + await this.driver.waitForSelector(this.sendAmountInput, { timeout: 10000 }); + await this.driver.pasteIntoField(this.sendAmountInput, amount); + } + + async setToAddress(toAddress: string): Promise { + await this.driver.waitForControllersLoaded(); + await this.driver.waitForSelector(this.toAddressInput, { timeout: 10000 }); + await this.driver.pasteIntoField(this.toAddressInput, toAddress); + } + + async clickOnContinue(): Promise { + const continueButton = await this.driver.waitForSelector( + { + text: 'Continue', + tag: 'span', + }, + { timeout: 5000 }, + ); // Since the buttons takes a bit to get enabled, this avoid test flakiness + const clickableButton = await this.driver.findElement( + '.confirmation-page button:nth-of-type(2)', + ); + await this.driver.wait(() => clickableButton.isEnabled()); + await continueButton.click(); + } + + async isContinueButtonEnabled(): Promise { + try { + const continueButton = await this.driver.findClickableElement( + this.continueButton, + 2000, + ); + await this.driver.wait( + async () => await continueButton.isEnabled(), + 5000, + ); + return await continueButton.isEnabled(); + } catch (e) { + console.log('Continue button not enabled', e); + return false; + } + } +} + +export default SendSolanaPage; diff --git a/test/e2e/page-objects/pages/send/solana-tx-result-page.ts b/test/e2e/page-objects/pages/send/solana-tx-result-page.ts new file mode 100644 index 000000000000..41d029df2cd6 --- /dev/null +++ b/test/e2e/page-objects/pages/send/solana-tx-result-page.ts @@ -0,0 +1,82 @@ +import { Driver } from '../../../webdriver/driver'; + +class SolanaTxresultPage { + private driver: Driver; + + private readonly closeButton = { + text: 'Close', + tag: 'span', + }; + + private readonly viewTransactionLink = { + text: 'View transaction', + tag: 'span', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_isViewTransactionLinkDisplayed() { + try { + await this.driver.findClickableElement(this.viewTransactionLink); + return true; + } catch (err) { + console.log('View transaction link not displayed'); + return false; + } + } + + async check_TransactionStatus(sent: boolean): Promise { + const statusText = sent ? 'Sent' : 'Transaction failed'; + try { + await this.driver.findElement({ + text: statusText, + tag: 'h2', + }); + return true; + } catch (err) { + console.log('Transaction status incorrect'); + return false; + } + } + + async check_TransactionStatusText( + amount: string, + sent: boolean, + ): Promise { + const displayedText = sent + ? `${amount} SOL was successfully sent` + : `Unable to send ${amount} SOL`; + const txStatusText = { + text: displayedText, + tag: 'p', + }; + try { + await this.driver.waitForSelector( + txStatusText, + { timeout: 5000 }, // even the tx is being mock, there is an spinner that sometimes is slow to disappear + ); + return true; + } catch (err) { + console.log( + `Transaction status text incorrect, expected ${displayedText} did not match`, + ); + return false; + } + } + + async isTransactionDetailDisplayed(text: string): Promise { + const detail = await this.driver.waitForSelector({ + text, + tag: 'p', + }); + return await detail.isDisplayed(); + } + + async clickOnClose(): Promise { + await this.driver.clickElement(this.closeButton); + } +} + +export default SolanaTxresultPage; diff --git a/test/e2e/page-objects/pages/settings/advanced-settings.ts b/test/e2e/page-objects/pages/settings/advanced-settings.ts new file mode 100644 index 000000000000..b93f29b58736 --- /dev/null +++ b/test/e2e/page-objects/pages/settings/advanced-settings.ts @@ -0,0 +1,32 @@ +import { Driver } from '../../../webdriver/driver'; + +class AdvancedSettings { + private readonly driver: Driver; + + private readonly downloadDataButton = '[data-testid="export-data-button"]'; + + private readonly downloadStateLogsButton = + '[data-testid="advanced-setting-state-logs"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.downloadStateLogsButton, + this.downloadDataButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Advanced Settings page to be loaded', + e, + ); + throw e; + } + console.log('Advanced Settings page is loaded'); + } +} + +export default AdvancedSettings; diff --git a/test/e2e/page-objects/pages/settings/experimental-settings.ts b/test/e2e/page-objects/pages/settings/experimental-settings.ts index 3af5c8a538e4..19de2da25c47 100644 --- a/test/e2e/page-objects/pages/settings/experimental-settings.ts +++ b/test/e2e/page-objects/pages/settings/experimental-settings.ts @@ -1,4 +1,5 @@ import { Driver } from '../../../webdriver/driver'; +import messages from '../../../../../app/_locales/en/messages.json'; class ExperimentalSettings { private readonly driver: Driver; @@ -7,13 +8,19 @@ class ExperimentalSettings { private readonly addAccountSnapToggle = '[data-testid="add-account-snap-toggle-div"]'; + private readonly addBitcoinAccountToggle = + '[data-testid="bitcoin-support-toggle-div"]'; + private readonly experimentalPageTitle = { text: 'Experimental', tag: 'h4', }; - private readonly redesignedSignatureToggle = - '[data-testid="toggle-redesigned-confirmations-container"]'; + private readonly watchAccountToggleState = + '[data-testid="watch-account-toggle"]'; + + private readonly watchAccountToggle = + '[data-testid="watch-account-toggle-div"]'; constructor(driver: Driver) { this.driver = driver; @@ -32,14 +39,32 @@ class ExperimentalSettings { console.log('Experimental Settings page is loaded'); } + // Get the state of the Watch Account Toggle, returns true if the toggle is selected + async getWatchAccountToggleState(): Promise { + console.log('Get Watch Account Toggle State'); + const toggleInput = await this.driver.findElement( + this.watchAccountToggleState, + ); + return toggleInput.isSelected(); + } + + async toggleBitcoinAccount(): Promise { + console.log('Toggle Add new Bitcoin account on experimental setting page'); + await this.driver.waitForSelector({ + text: messages.bitcoinSupportToggleTitle.message, + tag: 'span', + }); + await this.driver.clickElement(this.addBitcoinAccountToggle); + } + async toggleAddAccountSnap(): Promise { console.log('Toggle Add Account Snap on experimental setting page'); await this.driver.clickElement(this.addAccountSnapToggle); } - async toggleRedesignedSignature(): Promise { - console.log('Toggle Redesigned Signature on experimental setting page'); - await this.driver.clickElement(this.redesignedSignatureToggle); + async toggleWatchAccount(): Promise { + console.log('Toggle Watch Account on experimental setting page'); + await this.driver.clickElement(this.watchAccountToggle); } } diff --git a/test/e2e/page-objects/pages/settings/general-settings.ts b/test/e2e/page-objects/pages/settings/general-settings.ts new file mode 100644 index 000000000000..0db61a03a23f --- /dev/null +++ b/test/e2e/page-objects/pages/settings/general-settings.ts @@ -0,0 +1,61 @@ +import { Driver } from '../../../webdriver/driver'; + +class GeneralSettings { + private readonly driver: Driver; + + private readonly generalSettingsPageTitle = { + text: 'General', + tag: 'h4', + }; + + private readonly loadingOverlaySpinner = '.loading-overlay__spinner'; + + private readonly selectLanguageField = '[data-testid="locale-select"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.check_noLoadingOverlaySpinner(); + await this.driver.waitForMultipleSelectors([ + this.generalSettingsPageTitle, + this.selectLanguageField, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for General Settings page to be loaded', + e, + ); + throw e; + } + console.log('General Settings page is loaded'); + } + + /** + * Change the language of MM on General Settings page + * + * @param languageToSelect - The language to select + */ + async changeLanguage(languageToSelect: string): Promise { + console.log( + 'Changing language to ', + languageToSelect, + 'on general settings page', + ); + await this.check_noLoadingOverlaySpinner(); + await this.driver.clickElement(this.selectLanguageField); + await this.driver.clickElement({ + text: languageToSelect, + tag: 'option', + }); + await this.check_noLoadingOverlaySpinner(); + } + + async check_noLoadingOverlaySpinner(): Promise { + await this.driver.assertElementNotPresent(this.loadingOverlaySpinner); + } +} + +export default GeneralSettings; diff --git a/test/e2e/page-objects/pages/settings/settings-page.ts b/test/e2e/page-objects/pages/settings/settings-page.ts index d103aca83f97..4444556a1b74 100644 --- a/test/e2e/page-objects/pages/settings/settings-page.ts +++ b/test/e2e/page-objects/pages/settings/settings-page.ts @@ -40,6 +40,35 @@ class SettingsPage { console.log('Settings page is loaded'); } + async clickAdvancedTab(): Promise { + console.log('Clicking on Advanced tab'); + await this.driver.clickElement({ + css: '.tab-bar__tab__content__title', + text: 'Advanced', + }); + } + + async toggleShowFiatOnTestnets(): Promise { + console.log('Toggling Show Fiat on Testnets setting'); + await this.driver.clickElement( + '.toggle-button.show-fiat-on-testnets-toggle', + ); + } + + async toggleBalanceSetting(): Promise { + console.log('Toggling balance setting'); + await this.driver.clickElement( + '.toggle-button.show-native-token-as-main-balance', + ); + } + + async exitSettings(): Promise { + console.log('Exiting settings page'); + await this.driver.clickElement( + '.settings-page__header__title-container__close-button', + ); + } + async closeSettingsPage(): Promise { console.log('Closing Settings page'); await this.driver.clickElement(this.closeSettingsPageButton); diff --git a/test/e2e/page-objects/pages/snap-simple-keyring-page.ts b/test/e2e/page-objects/pages/snap-simple-keyring-page.ts index c75adb06da3a..5d84b38c1c10 100644 --- a/test/e2e/page-objects/pages/snap-simple-keyring-page.ts +++ b/test/e2e/page-objects/pages/snap-simple-keyring-page.ts @@ -1,5 +1,5 @@ import { Driver } from '../../webdriver/driver'; -import { WINDOW_TITLES } from '../../helpers'; +import { regularDelayMs, WINDOW_TITLES } from '../../helpers'; class SnapSimpleKeyringPage { private readonly driver: Driver; @@ -170,6 +170,9 @@ class SnapSimpleKeyringPage { console.log( 'Approve/Reject snap account transaction on Snap Simple Keyring page', ); + + await this.driver.delay(regularDelayMs); + if (isSignatureRequest) { await this.driver.clickElementAndWaitForWindowToClose( this.confirmationSubmitButton, @@ -306,7 +309,10 @@ class SnapSimpleKeyringPage { await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await this.driver.clickElement(this.confirmConnectionButton); - await this.driver.waitForSelector(this.addtoMetamaskMessage); + // set a bigger timeout to wait for element as a temporary fix to reduce flakiness + await this.driver.waitForSelector(this.addtoMetamaskMessage, { + timeout: 15000, + }); await this.driver.clickElementSafe(this.snapInstallScrollButton, 200); await this.driver.waitForSelector(this.confirmAddtoMetamask); await this.driver.clickElement(this.confirmAddtoMetamask); diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index afff2f37e57e..3f684a3d9fb6 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -1,3 +1,4 @@ +import { strict as assert } from 'assert'; import { WINDOW_TITLES } from '../../helpers'; import { Driver } from '../../webdriver/driver'; @@ -7,6 +8,11 @@ const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`; class TestDapp { private driver: Driver; + private readonly addTokensToWalletButton = { + text: 'Add Token(s) to Wallet', + tag: 'button', + }; + private readonly confirmDepositButton = '[data-testid="confirm-footer-button"]'; @@ -15,8 +21,11 @@ class TestDapp { private readonly confirmDialogScrollButton = '[data-testid="signature-request-scroll-button"]'; - private readonly confirmSignatureButton = - '[data-testid="page-container-footer-next"]'; + private readonly confirmScrollToBottomButtonRedesign = + '.confirm-scroll-to-bottom__button'; + + private readonly confirmSignatureButtonRedesign = + '[data-testid="confirm-footer-button"]'; private readonly connectAccountButton = '#connectButton'; @@ -29,13 +38,10 @@ class TestDapp { private readonly depositPiggyBankContractButton = '#depositButton'; - private readonly editConnectButton = { - text: 'Edit', - tag: 'button', - }; - private readonly simpleSendButton = '#sendButton'; + private readonly erc20TokenAddresses = '#erc20TokenAddresses'; + private readonly erc721MintButton = '#mintButton'; private readonly erc721TransferFromButton = '#transferFromButton'; @@ -60,11 +66,6 @@ class TestDapp { private readonly erc721SetApprovalForAllButton = '#setApprovalForAllButton'; - private readonly localhostCheckbox = { - text: 'Localhost 8545', - tag: 'p', - }; - private readonly localhostNetworkMessage = { css: '#chainId', text: '0x539', @@ -76,37 +77,31 @@ class TestDapp { private readonly personalSignResult = '#personalSignVerifyECRecoverResult'; - private readonly personalSignSignatureRequestMessage = { - text: 'personal_sign', - tag: 'div', - }; - private readonly personalSignVerifyButton = '#personalSignVerify'; + private personalSignSigUtilResultSelector = + '#personalSignVerifySigUtilResult'; + private readonly revokePermissionButton = '#revokeAccountsPermission'; private readonly signPermitButton = '#signPermit'; private readonly signPermitResult = '#signPermitResult'; - private readonly signPermitSignatureRequestMessage = { - text: 'Permit', - tag: 'p', - }; - private readonly signPermitVerifyButton = '#signPermitVerify'; private readonly signPermitVerifyResult = '#signPermitVerifyResult'; + private readonly signPermitResultR = '#signPermitResultR'; + + private readonly signPermitResultS = '#signPermitResultS'; + + private readonly signPermitResultV = '#signPermitResultV'; + private readonly signTypedDataButton = '#signTypedData'; private readonly signTypedDataResult = '#signTypedDataResult'; - private readonly signTypedDataSignatureRequestMessage = { - text: 'Hi, Alice!', - tag: 'div', - }; - private readonly signTypedDataV3Button = '#signTypedDataV3'; private readonly signTypedDataV3Result = '#signTypedDataV3Result'; @@ -116,6 +111,11 @@ class TestDapp { tag: 'div', }; + private readonly signTypedDataV3V4SignatureRequestMessageRedesign = { + text: 'Hello, Bob!', + tag: 'p', + }; + private readonly signTypedDataV3VerifyButton = '#signTypedDataV3Verify'; private readonly signTypedDataV3VerifyResult = '#signTypedDataV3VerifyResult'; @@ -132,18 +132,45 @@ class TestDapp { private readonly signTypedDataVerifyResult = '#signTypedDataVerifyResult'; + private readonly signSiweButton = '#siwe'; + + private readonly signSiweVerifyResult = '#siweResult'; + + private readonly signSiweBadDomainButton = '#siweBadDomain'; + + private readonly sign721PermitButton = '#sign721Permit'; + + private sign721PermitVerifyButton = '#sign721PermitVerify'; + + private sign721PermitVerifyResult = '#sign721PermitVerifyResult'; + + private sign721PermitResult = '#sign721PermitResult'; + + private sign721PermitResultR = '#sign721PermitResultR'; + + private sign721PermitResultS = '#sign721PermitResultS'; + + private sign721PermitResultV = '#sign721PermitResultV'; + + private readonly eip747ContractAddressInput = '#eip747ContractAddress'; + private readonly transactionRequestMessage = { text: 'Transaction request', tag: 'h2', }; - private readonly updateNetworkButton = { - text: 'Update', - tag: 'button', + private readonly userRejectedRequestMessage = { + tag: 'span', + text: 'Error: User rejected the request.', }; private erc20TokenTransferButton = '#transferTokens'; + private createTokenButton = { + text: 'Create Token', + tag: 'button', + }; + constructor(driver: Driver) { this.driver = driver; } @@ -188,11 +215,21 @@ class TestDapp { }); } + public async clickAddTokenToWallet() { + await this.driver.clickElement(this.addTokensToWalletButton); + } + async clickSimpleSendButton() { + await this.driver.waitForSelector(this.simpleSendButton, { + state: 'enabled', + }); await this.driver.clickElement(this.simpleSendButton); } async clickERC721MintButton() { + await this.driver.waitForSelector(this.erc721MintButton, { + state: 'enabled', + }); await this.driver.clickElement(this.erc721MintButton); } @@ -240,26 +277,46 @@ class TestDapp { await this.driver.clickElement(this.erc20TokenTransferButton); } - /** - * Connect account to test dapp. - * - * @param publicAddress - The public address to connect to test dapp. - */ - async connectAccount(publicAddress: string) { - console.log('Connect account to test dapp'); - await this.driver.clickElement(this.connectAccountButton); + async confirmConnectAccountModal() { + console.log('Confirm connect account modal in notification window'); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await this.driver.waitForSelector(this.connectMetaMaskMessage); - await this.driver.clickElementAndWaitForWindowToClose( this.confirmDialogButton, ); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.waitForSelector({ - css: this.connectedAccount, - text: publicAddress.toLowerCase(), - }); - await this.driver.waitForSelector(this.localhostNetworkMessage); + } + + /** + * Connect account to test dapp. + * + * @param options - Options for connecting account to test dapp. + * @param [options.connectAccountButtonEnabled] - Indicates if the connect account button should be enabled. + * @param options.publicAddress - The public address to connect to test dapp. + */ + async connectAccount({ + connectAccountButtonEnabled = true, + publicAddress, + }: { + connectAccountButtonEnabled?: boolean; + publicAddress?: string; + }) { + console.log('Connect account to test dapp'); + await this.driver.clickElement(this.connectAccountButton); + if (connectAccountButtonEnabled) { + await this.confirmConnectAccountModal(); + } else { + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await this.driver.waitForSelector(this.connectMetaMaskMessage); + const confirmConnectDialogButton = await this.driver.findElement( + this.confirmDialogButton, + ); + assert.equal(await confirmConnectDialogButton.isEnabled(), false); + } + if (publicAddress) { + await this.check_connectedAccounts(publicAddress); + await this.driver.waitForSelector(this.localhostNetworkMessage); + } } async createDepositTransaction() { @@ -282,10 +339,43 @@ class TestDapp { await this.driver.clickElement(this.revokePermissionButton); await this.driver.refresh(); await this.check_pageIsLoaded(); - await this.driver.assertElementNotPresent({ - css: this.connectedAccount, - text: publicAddress.toLowerCase(), - }); + await this.check_connectedAccounts(publicAddress, false); + } + + /** + * Scrolls to the create token button and clicks it. + */ + public async findAndClickCreateToken() { + const createTokenElement = await this.driver.findElement( + this.createTokenButton, + ); + await this.driver.scrollToElement(createTokenElement); + await this.driver.clickElement(this.createTokenButton); + } + + /** + * Verifies the accounts connected to the test dapp. + * + * @param connectedAccounts - Account addresses to check if connected to test dapp, separated by a comma. + * @param shouldBeConnected - Whether the accounts should be connected to test dapp. Defaults to true. + */ + async check_connectedAccounts( + connectedAccounts: string, + shouldBeConnected: boolean = true, + ) { + if (shouldBeConnected) { + console.log('Verify connected accounts:', connectedAccounts); + await this.driver.waitForSelector({ + css: this.connectedAccount, + text: connectedAccounts.toLowerCase(), + }); + } else { + console.log('Verify accounts not connected:', connectedAccounts); + await this.driver.assertElementNotPresent({ + css: this.connectedAccount, + text: connectedAccounts.toLowerCase(), + }); + } } /** @@ -373,6 +463,17 @@ class TestDapp { }); } + async verifyPersonalSignSigUtilResult(publicKey: string) { + const sigUtilResult = await this.driver.waitForSelector({ + css: this.personalSignSigUtilResultSelector, + text: publicKey, + }); + assert.ok( + sigUtilResult, + `Sig Util result did not match address ${publicKey}`, + ); + } + /** * Verify the successful signPermit signature. * @@ -388,6 +489,72 @@ class TestDapp { }); } + async verifySignPermitResult(expectedSignature: string) { + await this.driver.waitForSelector({ + css: this.signPermitResult, + text: expectedSignature, + }); + } + + async verifySignPermitResultR(expectedR: string) { + await this.driver.waitForSelector({ + css: this.signPermitResultR, + text: `r: ${expectedR}`, + }); + } + + async verifySignPermitResultS(expectedS: string) { + await this.driver.waitForSelector({ + css: this.signPermitResultS, + text: `s: ${expectedS}`, + }); + } + + async verifySignPermitResultV(expectedV: string) { + await this.driver.waitForSelector({ + css: this.signPermitResultV, + text: `v: ${expectedV}`, + }); + } + + async check_successSign721Permit(publicKey: string) { + console.log('Verify successful signPermit signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.sign721PermitVerifyButton); + await this.driver.waitForSelector({ + css: this.sign721PermitVerifyResult, + text: publicKey.toLowerCase(), + }); + } + + async verifySign721PermitResult(expectedSignature: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResult, + text: expectedSignature, + }); + } + + async verifySign721PermitResultR(expectedR: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResultR, + text: `r: ${expectedR}`, + }); + } + + async verifySign721PermitResultS(expectedS: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResultS, + text: `s: ${expectedS}`, + }); + } + + async verifySign721PermitResultV(expectedV: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResultV, + text: `v: ${expectedV}`, + }); + } + /** * Verify the successful signTypedData signature. * @@ -403,6 +570,13 @@ class TestDapp { }); } + async verify_successSignTypedDataResult(result: string) { + await this.driver.waitForSelector({ + css: this.signTypedDataResult, + text: result.toLowerCase(), + }); + } + /** * Verify the successful signTypedDataV3 signature. * @@ -418,6 +592,13 @@ class TestDapp { }); } + async verify_successSignTypedDataV3Result(result: string) { + await this.driver.waitForSelector({ + css: this.signTypedDataV3Result, + text: result.toLowerCase(), + }); + } + /** * Verify the successful signTypedDataV4 signature. * @@ -433,16 +614,80 @@ class TestDapp { }); } + /** + * Checks the count of token addresses. + * + * @param expectedCount - The expected count of token addresses. + */ + async check_TokenAddressesCount(expectedCount: number) { + console.log(`checking token addresses count: ${expectedCount}`); + await this.driver.wait(async () => { + const tokenAddressesElement = await this.driver.findElement( + this.erc20TokenAddresses, + ); + const tokenAddresses = await tokenAddressesElement.getText(); + const addresses = tokenAddresses.split(',').filter(Boolean); + + return addresses.length === expectedCount; + }, 10000); + } + + async verify_successSignTypedDataV4Result(result: string) { + await this.driver.waitForSelector({ + css: this.signTypedDataV4Result, + text: result.toLowerCase(), + }); + } + + async check_successSiwe(result: string) { + console.log('Verify successful SIWE signature'); + await this.driver.waitForSelector({ + css: this.signSiweVerifyResult, + text: result.toLowerCase(), + }); + } + + async clickPersonalSign() { + await this.driver.clickElement(this.personalSignButton); + } + + async clickSignTypedData() { + await this.driver.clickElement(this.signTypedDataButton); + } + + async clickSignTypedDatav3() { + await this.driver.clickElement(this.signTypedDataV3Button); + } + + async clickSignTypedDatav4() { + await this.driver.clickElement(this.signTypedDataV4Button); + } + + async clickPermit() { + await this.driver.clickElement(this.signPermitButton); + } + + async clickSiwe() { + await this.driver.clickElement(this.signSiweButton); + } + + async clickSwieBadDomain() { + await this.driver.clickElement(this.signSiweBadDomainButton); + } + + async clickERC721Permit() { + await this.driver.clickElement(this.sign721PermitButton); + } + /** * Sign a message with the personal sign method. */ async personalSign() { console.log('Sign message with personal sign'); - await this.driver.clickElement(this.personalSignButton); + await this.clickPersonalSign(); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await this.driver.waitForSelector(this.personalSignSignatureRequestMessage); await this.driver.clickElementAndWaitForWindowToClose( - this.confirmSignatureButton, + this.confirmSignatureButtonRedesign, ); } @@ -451,11 +696,10 @@ class TestDapp { */ async signPermit() { console.log('Sign message with signPermit'); - await this.driver.clickElement(this.signPermitButton); + await this.clickPermit(); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await this.driver.waitForSelector(this.signPermitSignatureRequestMessage); await this.driver.clickElementAndWaitForWindowToClose( - this.confirmSignatureButton, + this.confirmSignatureButtonRedesign, ); } @@ -464,13 +708,10 @@ class TestDapp { */ async signTypedData() { console.log('Sign message with signTypedData'); - await this.driver.clickElement(this.signTypedDataButton); + await this.clickSignTypedData(); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await this.driver.waitForSelector( - this.signTypedDataSignatureRequestMessage, - ); await this.driver.clickElementAndWaitForWindowToClose( - this.confirmSignatureButton, + this.confirmSignatureButtonRedesign, ); } @@ -479,31 +720,69 @@ class TestDapp { */ async signTypedDataV3() { console.log('Sign message with signTypedDataV3'); - await this.driver.clickElement(this.signTypedDataV3Button); + await this.clickSignTypedDatav3(); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await this.driver.waitForSelector( this.signTypedDataV3V4SignatureRequestMessage, ); await this.driver.clickElementSafe(this.confirmDialogScrollButton, 200); await this.driver.clickElementAndWaitForWindowToClose( - this.confirmSignatureButton, + this.confirmSignatureButtonRedesign, + ); + } + + async signTypedDataV3Redesign() { + await this.clickSignTypedDatav3(); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await this.driver.waitForSelector( + this.signTypedDataV3V4SignatureRequestMessageRedesign, + ); + await this.driver.clickElementSafe( + this.confirmScrollToBottomButtonRedesign, + 200, + ); + await this.driver.clickElementAndWaitForWindowToClose( + this.confirmSignatureButtonRedesign, ); } /** * Sign a message with the signTypedDataV4 method. + * */ async signTypedDataV4() { console.log('Sign message with signTypedDataV4'); - await this.driver.clickElement(this.signTypedDataV4Button); + await this.clickSignTypedDatav4(); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await this.driver.waitForSelector( - this.signTypedDataV3V4SignatureRequestMessage, + this.signTypedDataV3V4SignatureRequestMessageRedesign, + ); + await this.driver.clickElementSafe( + this.confirmScrollToBottomButtonRedesign, + 200, ); - await this.driver.clickElementSafe(this.confirmDialogScrollButton, 200); await this.driver.clickElementAndWaitForWindowToClose( - this.confirmSignatureButton, + this.confirmSignatureButtonRedesign, + ); + } + + async pasteIntoEip747ContractAddressInput() { + await this.driver.findElement(this.eip747ContractAddressInput); + await this.driver.pasteFromClipboardIntoField( + this.eip747ContractAddressInput, + ); + } + + async assertEip747ContractAddressInputValue(expectedValue: string) { + const formFieldEl = await this.driver.findElement( + this.eip747ContractAddressInput, ); + assert.equal(await formFieldEl.getAttribute('value'), expectedValue); + } + + async assertUserRejectedRequest() { + await this.driver.waitForSelector(this.userRejectedRequestMessage); } } export default TestDapp; diff --git a/test/e2e/page-objects/pages/test-snaps.ts b/test/e2e/page-objects/pages/test-snaps.ts new file mode 100644 index 000000000000..8bb285321ef5 --- /dev/null +++ b/test/e2e/page-objects/pages/test-snaps.ts @@ -0,0 +1,56 @@ +import { Driver } from '../../webdriver/driver'; +import { TEST_SNAPS_WEBSITE_URL } from '../../snaps/enums'; +import { largeDelayMs, WINDOW_TITLES } from '../../helpers'; + +export class TestSnaps { + driver: Driver; + + private readonly installedSnapsHeader = '[data-testid="InstalledSnaps"]'; + + private readonly connectDialogsSnapButton = + '[data-testid="dialogs"] [data-testid="connect-button"]'; + + private readonly dialogsSnapConfirmationButton = '#sendConfirmationButton'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async openPage() { + await this.driver.openNewPage(TEST_SNAPS_WEBSITE_URL); + await this.driver.waitForSelector(this.installedSnapsHeader); + } + + async clickConnectDialogsSnapButton() { + await this.driver.scrollToElement( + this.driver.findClickableElement(this.connectDialogsSnapButton), + ); + await this.driver.delay(largeDelayMs); + await this.driver.clickElement(this.connectDialogsSnapButton); + } + + async clickDialogsSnapConfirmationButton() { + await this.driver.clickElement(this.dialogsSnapConfirmationButton); + } + + async completeSnapInstallConfirmation() { + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await this.driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await this.driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + await this.driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + } +} diff --git a/test/e2e/page-objects/pages/token-overview-page.ts b/test/e2e/page-objects/pages/token-overview-page.ts new file mode 100644 index 000000000000..46e93ed490c7 --- /dev/null +++ b/test/e2e/page-objects/pages/token-overview-page.ts @@ -0,0 +1,54 @@ +import { Driver } from '../../webdriver/driver'; + +class TokenOverviewPage { + private driver: Driver; + + private readonly receiveButton = { + text: 'Receive', + css: '.icon-button', + }; + + private readonly sendButton = { + text: 'Send', + css: '.icon-button', + }; + + private readonly swapButton = { + text: 'Swap', + css: '.icon-button', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.sendButton, + this.swapButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Token overview page to be loaded', + e, + ); + throw e; + } + console.log('Token overview page is loaded'); + } + + async clickReceive(): Promise { + await this.driver.clickElement(this.receiveButton); + } + + async clickSend(): Promise { + await this.driver.clickElement(this.sendButton); + } + + async clickSwap(): Promise { + await this.driver.clickElement(this.swapButton); + } +} + +export default TokenOverviewPage; diff --git a/test/e2e/playwright/global/specs/protect-intrinsics.spec.ts b/test/e2e/playwright/global/specs/protect-intrinsics.spec.ts index 7f67cd587f2b..63091e9fe5cc 100644 --- a/test/e2e/playwright/global/specs/protect-intrinsics.spec.ts +++ b/test/e2e/playwright/global/specs/protect-intrinsics.spec.ts @@ -1,5 +1,4 @@ import { test, expect } from '@playwright/test'; -// @ts-expect-error lint fails otherwise import 'ses'; import '../../../../../app/scripts/lockdown-run'; import '../../../../../app/scripts/lockdown-more'; diff --git a/test/e2e/playwright/shared/pageObjects/network-controller-page.ts b/test/e2e/playwright/shared/pageObjects/network-controller-page.ts index 1986de15dd88..e2116bcac676 100644 --- a/test/e2e/playwright/shared/pageObjects/network-controller-page.ts +++ b/test/e2e/playwright/shared/pageObjects/network-controller-page.ts @@ -24,8 +24,6 @@ export class NetworkController { readonly dismissBtn: Locator; - readonly networkList: Locator; - readonly networkListEdit: Locator; readonly rpcName: Locator; @@ -39,9 +37,6 @@ export class NetworkController { constructor(page: Page) { this.page = page; this.networkDisplay = this.page.getByTestId('network-display'); - this.networkList = this.page.getByTestId( - 'network-list-item-options-button-0x1', - ); this.networkListEdit = this.page.getByTestId( 'network-list-item-options-edit', ); @@ -69,9 +64,14 @@ export class NetworkController { }) { let rpcName = options.name; await this.networkDisplay.click(); - if (options.name === Tenderly.Mainnet.name) { + if ( + options.name === Tenderly.Mainnet.name || + options.name === Tenderly.Linea.name + ) { rpcName = options.rpcName; - await this.networkList.click(); + await this.page + .getByTestId(`network-list-item-options-button-${options.chainID}`) + .click(); await this.networkListEdit.click(); } else { await this.addNetworkButton.click(); @@ -82,12 +82,18 @@ export class NetworkController { await this.networkRpc.fill(options.url); await this.rpcName.fill(rpcName); await this.addURLBtn.click(); - if (options.name !== Tenderly.Mainnet.name) { + if ( + options.name !== Tenderly.Mainnet.name && + options.name !== Tenderly.Linea.name + ) { await this.networkChainId.fill(options.chainID); } await this.networkTicker.fill(options.symbol); await this.saveBtn.waitFor({ state: 'visible' }); await this.saveBtn.click({ timeout: 60000 }); + await this.page.waitForSelector(`text=/was successfully edited/`, { + timeout: 30000, + }); } async addPopularNetwork(options: { networkName: string }) { diff --git a/test/e2e/playwright/shared/pageObjects/signup-page.ts b/test/e2e/playwright/shared/pageObjects/signup-page.ts index 28909f23ba20..692ca2ea70a4 100644 --- a/test/e2e/playwright/shared/pageObjects/signup-page.ts +++ b/test/e2e/playwright/shared/pageObjects/signup-page.ts @@ -49,6 +49,8 @@ export class SignUpPage { readonly skipSrpBackupBtn: Locator; + readonly popOverBtn: Locator; + constructor(page: Page) { this.page = page; this.getStartedBtn = page.locator('button:has-text("Get started")'); @@ -77,6 +79,7 @@ export class SignUpPage { this.nextBtn = page.getByTestId('pin-extension-next'); this.agreeBtn = page.locator('button:has-text("I agree")'); this.enableBtn = page.locator('button:has-text("Enable")'); + this.popOverBtn = page.getByTestId('popover-close'); } async importWallet() { @@ -114,5 +117,6 @@ export class SignUpPage { await this.gotItBtn.click(); await this.nextBtn.click(); await this.doneBtn.click(); + await this.popOverBtn.click(); } } diff --git a/test/e2e/playwright/shared/pageObjects/wallet-page.ts b/test/e2e/playwright/shared/pageObjects/wallet-page.ts index 4e31441451bf..10b4be84aef8 100644 --- a/test/e2e/playwright/shared/pageObjects/wallet-page.ts +++ b/test/e2e/playwright/shared/pageObjects/wallet-page.ts @@ -1,7 +1,7 @@ import { type Locator, type Page } from '@playwright/test'; export class WalletPage { - private page: Page; + readonly page: Page; readonly importTokensButton: Locator; @@ -21,6 +21,8 @@ export class WalletPage { readonly importAccountConfirmBtn: Locator; + readonly tokenBalance: Locator; + constructor(page: Page) { this.page = page; this.swapButton = this.page.getByTestId('token-overview-button-swap'); @@ -29,6 +31,9 @@ export class WalletPage { this.importAccountButton = this.page.getByText('Import account'); this.importButton = this.page.getByText('Import ('); this.tokenTab = this.page.getByTestId('account-overview__asset-tab'); + this.tokenBalance = this.page.getByTestId( + 'multichain-token-list-item-value', + ); this.addAccountButton = this.page.getByTestId( 'multichain-account-menu-popover-action-button', ); @@ -68,4 +73,12 @@ export class WalletPage { async selectActivityList() { await this.activityListTab.click(); } + + async getTokenBalance() { + return await this.tokenBalance.first().textContent(); + } + + async waitforTokenBalance(balance: string) { + await this.page.waitForSelector(`text=/${balance}/`, { timeout: 60000 }); + } } diff --git a/test/e2e/playwright/swap/pageObjects/swap-page.ts b/test/e2e/playwright/swap/pageObjects/swap-page.ts index c10536d37f05..1daed72cbdf1 100644 --- a/test/e2e/playwright/swap/pageObjects/swap-page.ts +++ b/test/e2e/playwright/swap/pageObjects/swap-page.ts @@ -63,7 +63,12 @@ export class SwapPage { ); } - async enterQuote(options: { from?: string; to: string; qty: string }) { + async enterQuote(options: { + from?: string; + to: string; + qty: string; + checkBalance: boolean; + }) { // Enter source token const native = await this.page.$(`text=/${options.from}/`); if (!native && options.from) { @@ -75,7 +80,7 @@ export class SwapPage { .locator('[class*="balance"]') .first() .textContent(); - if (balanceString) { + if (balanceString && options.checkBalance) { if (parseFloat(balanceString.split(' ')[1]) <= parseFloat(options.qty)) { await this.goBack(); // not enough balance so cancel out @@ -84,6 +89,8 @@ export class SwapPage { } // Enter Swap Quantity + await this.page.waitForTimeout(1000); + await this.tokenQty.waitFor({ state: 'visible' }); await this.tokenQty.fill(options.qty); this.swapQty = options.qty; diff --git a/test/e2e/playwright/swap/specs/swap.spec.ts b/test/e2e/playwright/swap/specs/swap.spec.ts index 73a85620eabf..1a553293a5de 100644 --- a/test/e2e/playwright/swap/specs/swap.spec.ts +++ b/test/e2e/playwright/swap/specs/swap.spec.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { test } from '@playwright/test'; +import { test, expect } from '@playwright/test'; import log from 'loglevel'; import { ChromeExtensionPage } from '../../shared/pageObjects/extension-page'; @@ -14,6 +14,7 @@ let swapPage: SwapPage; let networkController: NetworkController; let walletPage: WalletPage; let activityListPage: ActivityListPage; +let wallet: ethers.Wallet; const testSet = [ { @@ -23,6 +24,22 @@ const testSet = [ destination: 'DAI', network: Tenderly.Mainnet, }, + /* TODO: Skipping these tests as it returning no quote available + { + quantity: '.5', + source: 'ETH', + type: 'native', + destination: 'DAI', + network: Tenderly.Linea, + }, + { + quantity: '10', + source: 'DAI', + type: 'unapproved', + destination: 'USDC', + network: Tenderly.Linea, + }, + */ { quantity: '50', source: 'DAI', @@ -61,9 +78,6 @@ test.beforeAll( const page = await extension.initExtension(); page.setDefaultTimeout(15000); - const wallet = ethers.Wallet.createRandom(); - await addFundsToAccount(Tenderly.Mainnet.url, wallet.address); - const signUp = new SignUpPage(page); await signUp.createWallet(); @@ -71,21 +85,59 @@ test.beforeAll( swapPage = new SwapPage(page); activityListPage = new ActivityListPage(page); walletPage = new WalletPage(page); - - await networkController.addCustomNetwork(Tenderly.Mainnet); - await walletPage.importAccount(wallet.privateKey); }, ); +// TODO: Skipping test as it's failing in the pipeline for unknown reasons +test.skip(`Get quote on Mainnet Network`, async () => { + await walletPage.selectSwapAction(); + await walletPage.page.waitForTimeout(3000); + await swapPage.enterQuote({ + from: 'ETH', + to: 'USDC', + qty: '.01', + checkBalance: false, + }); + await walletPage.page.waitForTimeout(3000); + const quoteFound = await swapPage.waitForQuote(); + expect(quoteFound).toBeTruthy(); + await swapPage.goBack(); +}); + +test(`Add Custom Networks and import test account`, async () => { + let response; + wallet = ethers.Wallet.createRandom(); + + response = await addFundsToAccount(Tenderly.Mainnet.url, wallet.address); + expect(response.error).toBeUndefined(); + + response = await addFundsToAccount(Tenderly.Linea.url, wallet.address); + expect(response.error).toBeUndefined(); + + await networkController.addCustomNetwork(Tenderly.Linea); + await networkController.addCustomNetwork(Tenderly.Mainnet); + + await walletPage.importAccount(wallet.privateKey); + expect(walletPage.accountMenu).toHaveText('Account 2', { timeout: 30000 }); +}); + testSet.forEach((options) => { test(`should swap ${options.type} token ${options.source} to ${options.destination} on ${options.network.name}'`, async () => { await walletPage.selectTokenWallet(); await networkController.selectNetwork(options.network); + const balance = await walletPage.getTokenBalance(); + if (balance === '0 ETH') { + test.skip(); + } + await walletPage.selectSwapAction(); + // Allow balance label to populate + await walletPage.page.waitForTimeout(3000); const quoteEntered = await swapPage.enterQuote({ from: options.source, to: options.destination, qty: options.quantity, + checkBalance: true, }); if (quoteEntered) { diff --git a/test/e2e/playwright/swap/tenderly-network.ts b/test/e2e/playwright/swap/tenderly-network.ts index 996dee47a81a..26afb57cbab4 100644 --- a/test/e2e/playwright/swap/tenderly-network.ts +++ b/test/e2e/playwright/swap/tenderly-network.ts @@ -1,12 +1,11 @@ import axios from 'axios'; -import log from 'loglevel'; export const Tenderly = { Mainnet: { name: 'Ethereum Mainnet', rpcName: 'Tenderly - Mainnet', - url: 'https://virtual.mainnet.rpc.tenderly.co/03bb8912-7505-4856-839f-52819a26d0cd', - chainID: '1', + url: 'https://virtual.mainnet.rpc.tenderly.co/6a1cf1d8-3625-4ba0-b07e-c620d326ecb9', + chainID: '0x1', symbol: 'ETH', }, Optimism: { @@ -23,6 +22,13 @@ export const Tenderly = { chainID: '137', symbol: 'ETH', }, + Linea: { + name: 'Linea', + rpcName: '', + url: 'https://virtual.linea.rpc.tenderly.co/76ec2678-5c4e-4cd8-baa0-8d3dea738645', + chainID: '0xe708', + symbol: 'ETH', + }, }; export async function addFundsToAccount( @@ -42,9 +48,5 @@ export async function addFundsToAccount( }, }); - if (response.data.error) { - log.error( - `\tERROR: RROR: Failed to add funds to Tenderly VirtualTestNet\n${response.data.error}`, - ); - } + return response.data; } diff --git a/test/e2e/restore/MetaMaskUserData.json b/test/e2e/restore/MetaMaskUserData.json index 7a687ec254c0..6f08305db357 100644 --- a/test/e2e/restore/MetaMaskUserData.json +++ b/test/e2e/restore/MetaMaskUserData.json @@ -40,7 +40,6 @@ }, "theme": "light", "useBlockie": false, - "useRequestQueue": true, "useNftDetection": false, "useNonceField": false, "usePhishDetect": true, diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index b1b75d8864f6..3ef50edf66b0 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -79,7 +79,7 @@ function runningOnCircleCI(testPaths) { // 1. split the test files into chunks based on how long they take to run // 2. support "Rerun failed tests" on CircleCI const result = execSync( - 'circleci tests run --command=">test/test-results/myTestList.txt xargs echo" --split-by=timings --timings-type=filename --time-default=30s < test/test-results/fullTestList.txt', + 'circleci tests run --command=">test/test-results/myTestList.txt xargs echo" --split-by=timings --timings-type=filename --time-default=50s < test/test-results/fullTestList.txt', ).toString('utf8'); // Report if no tests found, exit gracefully diff --git a/test/e2e/seeder/ganache.ts b/test/e2e/seeder/ganache.ts index 7fa3d1ab0238..d262924ab61e 100644 --- a/test/e2e/seeder/ganache.ts +++ b/test/e2e/seeder/ganache.ts @@ -76,6 +76,13 @@ export class Ganache { }); } + async mineBlock() { + return await this.getProvider()?.request({ + method: 'evm_mine', + params: [], + }); + } + async quit() { if (!this.#server) { throw new Error('Server not running yet'); diff --git a/test/e2e/set-manifest-flags.ts b/test/e2e/set-manifest-flags.ts index 75339250506f..8a7e45050f14 100644 --- a/test/e2e/set-manifest-flags.ts +++ b/test/e2e/set-manifest-flags.ts @@ -1,7 +1,7 @@ -import { execSync } from 'child_process'; import fs from 'fs'; import { merge } from 'lodash'; import { ManifestFlags } from '../../app/scripts/lib/manifestFlags'; +import { fetchManifestFlagsFromPRAndGit } from '../../development/lib/get-manifest-flag'; export const folder = `dist/${process.env.SELENIUM_BROWSER}`; @@ -12,86 +12,8 @@ function parseIntOrUndefined(value: string | undefined): number | undefined { return value ? parseInt(value, 10) : undefined; } -/** - * Search a string for `flags = {...}` and return ManifestFlags if it exists - * - * @param str - The string to search - * @param errorType - The type of error to log if parsing fails - * @returns The ManifestFlags object if valid, otherwise undefined - */ -function regexSearchForFlags( - str: string, - errorType: string, -): ManifestFlags | undefined { - // Search str for `flags = {...}` - const flagsMatch = str.match(/flags\s*=\s*(\{.*\})/u); - - if (flagsMatch) { - try { - // Get 1st capturing group from regex - return JSON.parse(flagsMatch[1]); - } catch (error) { - console.error( - `Error parsing flags from ${errorType}, ignoring flags\n`, - error, - ); - } - } - - return undefined; -} - -/** - * Add flags from the GitHub PR body if they are set - * - * To use this feature, add a line to your PR body like: - * `flags = {"sentry": {"tracesSampleRate": 0.1}}` - * (must be valid JSON) - * - * @param flags - The flags object to add to - */ -function addFlagsFromPrBody(flags: ManifestFlags) { - let body; - - try { - body = fs.readFileSync('changed-files/pr-body.txt', 'utf8'); - } catch (error) { - console.debug('No pr-body.txt, ignoring flags'); - return; - } - - const newFlags = regexSearchForFlags(body, 'PR body'); - - if (newFlags) { - // Use lodash merge to do a deep merge (spread operator is shallow) - merge(flags, newFlags); - } -} - -/** - * Add flags from the Git message if they are set - * - * To use this feature, add a line to your commit message like: - * `flags = {"sentry": {"tracesSampleRate": 0.1}}` - * (must be valid JSON) - * - * @param flags - The flags object to add to - */ -function addFlagsFromGitMessage(flags: ManifestFlags) { - const gitMessage = execSync( - `git show --format='%B' --no-patch "HEAD"`, - ).toString(); - - const newFlags = regexSearchForFlags(gitMessage, 'git message'); - - if (newFlags) { - // Use lodash merge to do a deep merge (spread operator is shallow) - merge(flags, newFlags); - } -} - // Alter the manifest with CircleCI environment variables and custom flags -export function setManifestFlags(flags: ManifestFlags = {}) { +export async function setManifestFlags(flags: ManifestFlags = {}) { if (process.env.CIRCLECI) { flags.circleci = { enabled: true, @@ -104,8 +26,8 @@ export function setManifestFlags(flags: ManifestFlags = {}) { ), }; - addFlagsFromPrBody(flags); - addFlagsFromGitMessage(flags); + const additionalManifestFlags = await fetchManifestFlagsFromPRAndGit(); + merge(flags, additionalManifestFlags); // Set `flags.sentry.forceEnable` to true by default if (flags.sentry === undefined) { diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index 30037479fca8..8ac5dacd1f73 100644 --- a/test/e2e/snaps/enums.js +++ b/test/e2e/snaps/enums.js @@ -1,3 +1,3 @@ module.exports = { - TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.15.3', + TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.18.0', }; diff --git a/test/e2e/snaps/test-snap-cronjob.spec.js b/test/e2e/snaps/test-snap-cronjob.spec.js index 5fbc22d7ad71..73362b9df3ca 100644 --- a/test/e2e/snaps/test-snap-cronjob.spec.js +++ b/test/e2e/snaps/test-snap-cronjob.spec.js @@ -52,6 +52,8 @@ describe('Test Snap Cronjob', function () { tag: 'button', }); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); + // wait for and click confirm await driver.waitForSelector({ text: 'Confirm' }); await driver.clickElement({ diff --git a/test/e2e/snaps/test-snap-ethprovider.spec.js b/test/e2e/snaps/test-snap-ethprovider.spec.js index 0e5ea68a09eb..bb2cf2b67e25 100644 --- a/test/e2e/snaps/test-snap-ethprovider.spec.js +++ b/test/e2e/snaps/test-snap-ethprovider.spec.js @@ -116,11 +116,43 @@ describe('Test Snap ethereum_provider', function () { // switch to test snap page await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - // check the results of the message signature using waitForSelector + // check the results of the account selection using waitForSelector await driver.waitForSelector({ css: '#ethproviderResult', text: '"0x5cfe73b6021e818b776b421b1c4db2474086a7e1"', }); + + // Test personal_sign + await driver.pasteIntoField('#personalSignMessage', 'foo'); + await driver.clickElement('#signPersonalSignMessage'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + await driver.waitForSelector({ + css: '#personalSignResult', + text: '"0xf63c587cd42e7775e2e815a579f9744ea62944f263b3e69fad48535ba98a5ea107bc878088a99942733a59a89ef1d590eafdb467d59cf76564158d7e78351b751b"', + }); + + // Test eth_signTypedData + await driver.pasteIntoField('#signTypedData', 'bar'); + await driver.clickElement('#signTypedDataButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + await driver.waitForSelector({ + css: '#signTypedDataResult', + text: '"0x18d05f8139ad66d581fb658aca4d41950c6f38a8daeb3adfdb18614e645bf73508a4c24d4fc4026b5d447d223fcf026a32947846205f663c536df8a7b4d841fe1c"', + }); }, ); }); diff --git a/test/e2e/snaps/test-snap-managestate.spec.js b/test/e2e/snaps/test-snap-managestate.spec.js index 4d6a58faa678..0ecdb1291da8 100644 --- a/test/e2e/snaps/test-snap-managestate.spec.js +++ b/test/e2e/snaps/test-snap-managestate.spec.js @@ -8,7 +8,152 @@ const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); describe('Test Snap manageState', function () { - it('can pop up manageState snap and do update get and clear', async function () { + it('can use the new state API', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // navigate to test snaps page, then fill in the snapId + await driver.driver.get(TEST_SNAPS_WEBSITE_URL); + + // wait for page to load + await driver.waitForSelector({ + text: 'Installed Snaps', + tag: 'h2', + }); + + // scroll to manage-state snap + const snapButton1 = await driver.findElement('#connectstate'); + await driver.scrollToElement(snapButton1); + + // added delay for firefox (deflake) + await driver.delayFirefox(1000); + + // wait for and click connect + await driver.waitForSelector('#connectstate'); + await driver.clickElement('#connectstate'); + + // switch to metamask extension + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click connect + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // wait for and click confirm + await driver.waitForSelector({ text: 'Confirm' }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // wait for and click ok and wait for window to close + await driver.waitForSelector({ text: 'OK' }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'OK', + tag: 'button', + }); + + // fill and click send inputs on test snap page + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // wait for npm installation success + await driver.waitForSelector({ + css: '#connectstate', + text: 'Reconnect to State Snap', + }); + + // Enter data and click set + const sendEncrypted = await driver.findElement('#setStateKey'); + await driver.scrollToElement(sendEncrypted); + await driver.delayFirefox(1000); + await driver.pasteIntoField('#setStateKey', 'foo'); + await driver.pasteIntoField('#dataState', '"bar"'); + await driver.clickElement('#sendState'); + + // Check that the entire state blob was updated + await driver.waitForSelector({ + css: '#encryptedStateResult', + text: JSON.stringify({ foo: 'bar' }, null, 2), + }); + + // Check that we can retrieve one state key + const getKeyField = await driver.findElement('#getState'); + await driver.scrollToElement(getKeyField); + await driver.delayFirefox(1000); + await driver.pasteIntoField('#getState', 'foo'); + await driver.clickElement('#sendGetState'); + + await driver.waitForSelector({ + css: '#getStateResult', + text: '"bar"', + }); + + // click clear results + await driver.clickElement('#clearState'); + + // check result array is empty + await driver.waitForSelector({ + css: '#encryptedStateResult', + text: 'null', + }); + + // repeat the same above steps to check unencrypted state management + + // Enter data and click set + const sendUnencrypted = await driver.findElement( + '#setStateKeyUnencrypted', + ); + await driver.scrollToElement(sendUnencrypted); + await driver.delayFirefox(1000); + await driver.pasteIntoField('#setStateKeyUnencrypted', 'foo'); + await driver.pasteIntoField('#dataUnencryptedState', '"bar"'); + await driver.clickElement('#sendUnencryptedState'); + + // Check that the entire state blob was updated + await driver.waitForSelector({ + css: '#unencryptedStateResult', + text: JSON.stringify({ foo: 'bar' }, null, 2), + }); + + // Check that we can retrive one state key + const getUnencryptedKeyField = await driver.findElement( + '#getUnencryptedState', + ); + await driver.scrollToElement(getUnencryptedKeyField); + await driver.delayFirefox(1000); + await driver.pasteIntoField('#getUnencryptedState', 'foo'); + await driver.clickElement('#sendGetUnencryptedState'); + + await driver.waitForSelector({ + css: '#getStateUnencryptedResult', + text: '"bar"', + }); + + // click clear results + await driver.clickElement('#clearStateUnencrypted'); + + // check result array is empty + await driver.waitForSelector({ + css: '#unencryptedStateResult', + text: 'null', + }); + }, + ); + }); + + it('can use the legacy state API', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), @@ -71,7 +216,7 @@ describe('Test Snap manageState', function () { // wait for npm installation success await driver.waitForSelector({ css: '#connectmanage-state', - text: 'Reconnect to Manage State Snap', + text: 'Reconnect to Legacy State Snap', }); // enter data and click send managestate diff --git a/test/e2e/snaps/test-snap-metrics.spec.js b/test/e2e/snaps/test-snap-metrics.spec.js index 6c8ac7b9530f..934bda718d43 100644 --- a/test/e2e/snaps/test-snap-metrics.spec.js +++ b/test/e2e/snaps/test-snap-metrics.spec.js @@ -245,7 +245,7 @@ describe('Test Snap Metrics', function () { assert.deepStrictEqual(events[1].properties, { snap_id: 'npm:@metamask/notification-example-snap', origin: 'https://metamask.github.io', - version: '2.1.4', + version: '2.3.0', category: 'Snaps', locale: 'en', chain_id: '0x539', @@ -545,7 +545,7 @@ describe('Test Snap Metrics', function () { const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { snap_id: 'npm:@metamask/notification-example-snap', - version: '2.1.4', + version: '2.3.0', category: 'Snaps', locale: 'en', chain_id: '0x539', diff --git a/test/e2e/snaps/test-snap-revoke-perm.spec.js b/test/e2e/snaps/test-snap-revoke-perm.spec.js index e61c1d831862..9f5867cba7fa 100644 --- a/test/e2e/snaps/test-snap-revoke-perm.spec.js +++ b/test/e2e/snaps/test-snap-revoke-perm.spec.js @@ -150,7 +150,7 @@ describe('Test Snap revoke permission', function () { }); // try to click on options menu - await driver.clickElement('[data-testid="eth_accounts"]'); + await driver.clickElement('[data-testid="endowment:caip25"]'); // try to click on revoke permission await driver.clickElement({ diff --git a/test/e2e/snaps/test-snap-siginsights.spec.js b/test/e2e/snaps/test-snap-siginsights.spec.js index 94237b99c7f9..cd30a307e6df 100644 --- a/test/e2e/snaps/test-snap-siginsights.spec.js +++ b/test/e2e/snaps/test-snap-siginsights.spec.js @@ -1,10 +1,9 @@ const { withFixtures, - clickSignOnSignatureConfirmation, + clickSignOnRedesignedSignatureConfirmation, defaultGanacheOptions, openDapp, unlockWallet, - tempToggleSettingRedesignedConfirmations, WINDOW_TITLES, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -269,7 +268,6 @@ describe('Test Snap Signature Insights', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); // navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -332,12 +330,6 @@ describe('Test Snap Signature Insights', function () { // switch back to MetaMask window and switch to tx insights pane await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // wait for and click sign - await clickSignOnSignatureConfirmation({ - driver, - snapSigInsights: true, - }); - // look for returned signature insights data await driver.waitForSelector({ text: '0x4578616d706c652060706572736f6e616c5f7369676e60206d657373616765', @@ -347,14 +339,14 @@ describe('Test Snap Signature Insights', function () { // wait for host to render and click checkbox to authorize signing await driver.waitForSelector({ text: '127.0.0.1:8080', - tag: 'span', + tag: 'p', }); - await driver.clickElement('.mm-checkbox__input-wrapper'); - // click sign button and wait for window to close - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="snapInsightsButtonConfirm"]', - ); + // wait for and click sign + await clickSignOnRedesignedSignatureConfirmation({ + driver, + snapSigInsights: true, + }); // switch back to test-dapp window await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); @@ -374,12 +366,6 @@ describe('Test Snap Signature Insights', function () { // switch back to MetaMask window and switch to tx insights pane await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // wait for and click sign - await clickSignOnSignatureConfirmation({ - driver, - snapSigInsights: true, - }); - // look for returned signature insights data await driver.waitForSelector({ text: '1', @@ -389,14 +375,14 @@ describe('Test Snap Signature Insights', function () { // wait for host to render and click checkbox to authorize signing await driver.waitForSelector({ text: '127.0.0.1:8080', - tag: 'span', + tag: 'p', }); - await driver.clickElement('.mm-checkbox__input-wrapper'); - // click sign button and wait for window to close - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="snapInsightsButtonConfirm"]', - ); + // wait for and click sign + await clickSignOnRedesignedSignatureConfirmation({ + driver, + snapSigInsights: true, + }); // switch back to test-dapp window await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); @@ -416,16 +402,6 @@ describe('Test Snap Signature Insights', function () { // switch back to MetaMask window and switch to tx insights pane await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // click down arrow - await driver.waitForSelector('.fa-arrow-down'); - await driver.clickElement('.fa-arrow-down'); - - // wait for and click sign - await clickSignOnSignatureConfirmation({ - driver, - snapSigInsights: true, - }); - // look for returned signature insights data await driver.waitForSelector({ text: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC has been identified as a malicious verifying contract.', @@ -435,14 +411,14 @@ describe('Test Snap Signature Insights', function () { // wait for host to render and click checkbox to authorize signing await driver.waitForSelector({ text: '127.0.0.1:8080', - tag: 'span', + tag: 'p', }); - await driver.clickElement('.mm-checkbox__input-wrapper'); - // click sign button and wait for window to close - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="snapInsightsButtonConfirm"]', - ); + // wait for and click sign + await clickSignOnRedesignedSignatureConfirmation({ + driver, + snapSigInsights: true, + }); // switch back to test-dapp window await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); @@ -462,16 +438,6 @@ describe('Test Snap Signature Insights', function () { // switch back to MetaMask window and switch to tx insights pane await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // click down arrow - await driver.waitForSelector('.fa-arrow-down'); - await driver.clickElement('.fa-arrow-down'); - - // wait for and click sign - await clickSignOnSignatureConfirmation({ - driver, - snapSigInsights: true, - }); - // look for returned signature insights data await driver.waitForSelector({ text: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC has been identified as a malicious verifying contract.', @@ -481,14 +447,14 @@ describe('Test Snap Signature Insights', function () { // wait for host to render and click checkbox to authorize signing await driver.waitForSelector({ text: '127.0.0.1:8080', - tag: 'span', + tag: 'p', }); - await driver.clickElement('.mm-checkbox__input-wrapper'); - // click sign button and wait for window to close - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="snapInsightsButtonConfirm"]', - ); + // wait for and click sign + await clickSignOnRedesignedSignatureConfirmation({ + driver, + snapSigInsights: true, + }); // switch back to test-dapp window await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); diff --git a/test/e2e/snaps/test-snap-txinsights-v2.spec.js b/test/e2e/snaps/test-snap-txinsights-v2.spec.js deleted file mode 100644 index ba2d468a870a..000000000000 --- a/test/e2e/snaps/test-snap-txinsights-v2.spec.js +++ /dev/null @@ -1,174 +0,0 @@ -const { - defaultGanacheOptions, - withFixtures, - unlockWallet, - WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); - -describe('Test Snap TxInsights-v2', function () { - describe('Old confirmation screens', function () { - it('tests tx insights v2 functionality', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // navigate to test snaps page and connect - await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the transaction-insights test snap - const snapButton1 = await driver.findElement( - '#connecttransaction-insights', - ); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(1000); - - // wait for and click connect - await driver.waitForSelector('#connecttransaction-insights'); - await driver.clickElement('#connecttransaction-insights'); - - // switch to metamask extension - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click connect - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // wait for and click connect - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click ok and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch to test-snaps page - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // wait for and click get accounts - await driver.waitForSelector('#getAccounts'); - await driver.clickElement('#getAccounts'); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click confirm and wait for window to close - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // switch to test-snaps page and send tx - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - await driver.clickElement('#sendInsights'); - - // delay added for rendering (deflake) - await driver.delay(2000); - - // switch back to MetaMask window and switch to tx insights pane - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // find confirm button - await driver.findClickableElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click insights snap tab - await driver.waitForSelector({ - text: 'Insights Example Snap', - tag: 'button', - }); - await driver.clickElement({ - text: 'Insights Example Snap', - tag: 'button', - }); - - // check that txinsightstest tab contains the right info - await driver.waitForSelector({ - css: '.snap-ui-renderer__content', - text: 'ERC-20', - }); - - // click confirm to continue - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // check for warning from txinsights - await driver.waitForSelector({ - css: '.snap-delineator__header__text', - text: 'Warning from Insights Example Snap', - }); - - // check info in warning - await driver.waitForSelector({ - css: '.snap-ui-renderer__text', - text: 'ERC-20', - }); - - // click the warning confirm checkbox - await driver.clickElement('.mm-checkbox__input'); - - // click confirm button to send transaction - await driver.clickElement({ - css: '.mm-box--color-error-inverse', - text: 'Confirm', - tag: 'button', - }); - - // switch back to MetaMask tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // switch to activity pane - await driver.clickElement({ - tag: 'button', - text: 'Activity', - }); - - // wait for transaction confirmation - await driver.waitForSelector({ - css: '.transaction-status-label', - text: 'Confirmed', - }); - }, - ); - }); - }); -}); diff --git a/test/e2e/snaps/test-snap-txinsights.spec.js b/test/e2e/snaps/test-snap-txinsights.spec.js index 0171759587b5..c599bb124c52 100644 --- a/test/e2e/snaps/test-snap-txinsights.spec.js +++ b/test/e2e/snaps/test-snap-txinsights.spec.js @@ -3,229 +3,113 @@ const { withFixtures, unlockWallet, WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); describe('Test Snap TxInsights', function () { - describe('Old confirmation screens', function () { - it('tests tx insights functionality', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // navigate to test snaps page and connect - await driver.driver.get(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the transaction-insights test snap - const snapButton1 = await driver.findElement( - '#connecttransaction-insights', - ); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(1000); - - // wait for and click connect - await driver.waitForSelector('#connecttransaction-insights'); - await driver.clickElement('#connecttransaction-insights'); - - // switch to metamask extension - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click connect - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // wait for and click confirm - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click ok and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch to test-snaps page and get accounts - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // click get accounts - await driver.clickElement('#getAccounts'); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click next and wait for window to close - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // switch to test-snaps page - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // click send tx - await driver.clickElement('#sendInsights'); - - // delay added for rendering (deflake) - await driver.delay(2000); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and switch to insight snap pane - await driver.waitForSelector({ - text: 'Insights Example Snap', - tag: 'button', - }); - await driver.clickElement({ - text: 'Insights Example Snap', - tag: 'button', - }); - - // check that txinsightstest tab contains the right info - await driver.waitForSelector({ - css: '.snap-ui-renderer__content', - text: 'ERC-20', - }); - }, - ); - }); - }); - - describe('Redesigned confirmation screens', function () { - it('tests tx insights functionality', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // navigate to test snaps page and connect - await driver.driver.get(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the transaction-insights test snap - const snapButton1 = await driver.findElement( - '#connecttransaction-insights', - ); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(1000); - - // wait for and click connect - await driver.waitForSelector('#connecttransaction-insights'); - await driver.clickElement('#connecttransaction-insights'); - - // switch to metamask extension - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click connect - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // wait for and click confirm - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click ok and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch to test-snaps page and get accounts - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // click get accounts - await driver.clickElement('#getAccounts'); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click next and wait for window to close - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // switch to test-snaps page - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // click send tx - await driver.clickElement('#sendInsights'); - - // delay added for rendering (deflake) - await driver.delay(2000); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and switch to insight snap pane - await driver.waitForSelector({ - text: 'Insights Example Snap', - tag: 'span', - }); - - // check that txinsightstest tab contains the right info - await driver.waitForSelector({ - css: 'p', - text: 'ERC-20', - }); - }, - ); - }); + it('tests tx insights functionality', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // navigate to test snaps page and connect + await driver.driver.get(TEST_SNAPS_WEBSITE_URL); + + // wait for page to load + await driver.waitForSelector({ + text: 'Installed Snaps', + tag: 'h2', + }); + + // find and scroll to the transaction-insights test snap + const snapButton1 = await driver.findElement( + '#connecttransaction-insights', + ); + await driver.scrollToElement(snapButton1); + + // added delay for firefox (deflake) + await driver.delayFirefox(1000); + + // wait for and click connect + await driver.waitForSelector('#connecttransaction-insights'); + await driver.clickElement('#connecttransaction-insights'); + + // switch to metamask extension + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click connect + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // wait for and click confirm + await driver.waitForSelector({ text: 'Confirm' }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // wait for and click ok and wait for window to close + await driver.waitForSelector({ text: 'OK' }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'OK', + tag: 'button', + }); + + // switch to test-snaps page and get accounts + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // click get accounts + await driver.clickElement('#getAccounts'); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click next and wait for window to close + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // switch to test-snaps page + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // click send tx + await driver.clickElement('#sendInsights'); + + // delay added for rendering (deflake) + await driver.delay(2000); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and switch to insight snap pane + await driver.waitForSelector({ + text: 'Insights Example Snap', + tag: 'span', + }); + + // check that txinsightstest tab contains the right info + await driver.waitForSelector({ + css: 'p', + text: 'ERC-20', + }); + }, + ); }); }); diff --git a/test/e2e/tests/account/account-custom-name.spec.ts b/test/e2e/tests/account/account-custom-name.spec.ts index 4c0ecbe196f9..92302e032224 100644 --- a/test/e2e/tests/account/account-custom-name.spec.ts +++ b/test/e2e/tests/account/account-custom-name.spec.ts @@ -1,8 +1,10 @@ import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import { withFixtures } from '../../helpers'; +import { ACCOUNT_TYPE } from '../../constants'; import FixtureBuilder from '../../fixture-builder'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import AccountDetailsModal from '../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; @@ -25,14 +27,20 @@ describe('Account Custom Name Persistence', function (this: Suite) { // Change account label for existing account and verify edited account label const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.openAccountOptionsMenu(); - await accountListPage.changeAccountLabel(newAccountLabel); + await accountListPage.openAccountDetailsModal('Account 1'); + + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.changeAccountLabel(newAccountLabel); await headerNavbar.check_accountLabel(newAccountLabel); // Add new account with custom label and verify new added account label await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(anotherAccountLabel); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: anotherAccountLabel, + }); await headerNavbar.check_accountLabel(anotherAccountLabel); // Switch back to the first account and verify first custom account persists diff --git a/test/e2e/tests/account/add-account.spec.ts b/test/e2e/tests/account/add-account.spec.ts index 906e01f50b4d..ea0a52ed5f83 100644 --- a/test/e2e/tests/account/add-account.spec.ts +++ b/test/e2e/tests/account/add-account.spec.ts @@ -1,17 +1,18 @@ import { E2E_SRP } from '../../default-fixture'; import FixtureBuilder from '../../fixture-builder'; +import { ACCOUNT_TYPE } from '../../constants'; import { WALLET_PASSWORD, defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, withFixtures, } from '../../helpers'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; import { completeImportSRPOnboardingFlow } from '../../page-objects/flows/onboarding.flow'; -import { sendTransactionToAccount } from '../../page-objects/flows/send-transaction.flow'; +import { sendRedesignedTransactionToAccount } from '../../page-objects/flows/send-transaction.flow'; import AccountListPage from '../../page-objects/pages/account-list-page'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import LoginPage from '../../page-objects/pages/login-page'; import ResetPasswordPage from '../../page-objects/pages/reset-password-page'; @@ -26,8 +27,6 @@ describe('Add account', function () { async ({ driver, ganacheServer }) => { await completeImportSRPOnboardingFlow({ driver }); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); @@ -38,7 +37,9 @@ describe('Add account', function () { const newAccountName = 'Account 2'; const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel(newAccountName); await homePage.check_expectedBalanceIsDisplayed(); @@ -49,16 +50,17 @@ describe('Add account', function () { await accountListPage.switchToAccount('Account 1'); await headerNavbar.check_accountLabel('Account 1'); await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); - await sendTransactionToAccount({ + + await sendRedesignedTransactionToAccount({ driver, recipientAccount: newAccountName, amount: '2.8', - gasFee: '0.000042', - totalFee: '2.800042', }); + await homePage.check_pageIsLoaded(); - await homePage.check_confirmedTxNumberDisplayedInActivity(); - await homePage.check_txAmountInActivity('-2.8 ETH'); + const activityList = new ActivityListPage(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(); + await activityList.check_txAmountInActivity('-2.8 ETH'); // Lock wallet and recover via SRP in "forget password" option await headerNavbar.lockMetaMask(); @@ -100,7 +102,9 @@ describe('Add account', function () { // Create new account with default name Account 2 const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel('Account 2'); await homePage.check_expectedBalanceIsDisplayed(); diff --git a/test/e2e/tests/account/forgot-password.spec.ts b/test/e2e/tests/account/forgot-password.spec.ts index 698efcbe0819..62d382cd69dc 100644 --- a/test/e2e/tests/account/forgot-password.spec.ts +++ b/test/e2e/tests/account/forgot-password.spec.ts @@ -3,7 +3,7 @@ import FixtureBuilder from '../../fixture-builder'; import { Driver } from '../../webdriver/driver'; import { E2E_SRP } from '../../default-fixture'; import { Ganache } from '../../seeder/ganache'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import LoginPage from '../../page-objects/pages/login-page'; import ResetPasswordPage from '../../page-objects/pages/reset-password-page'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; diff --git a/test/e2e/tests/account/import-flow.spec.ts b/test/e2e/tests/account/import-flow.spec.ts index 8dc989db4d41..c8ffcc334e49 100644 --- a/test/e2e/tests/account/import-flow.spec.ts +++ b/test/e2e/tests/account/import-flow.spec.ts @@ -1,15 +1,16 @@ import path from 'path'; +import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; import { withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import AccountListPage from '../../page-objects/pages/account-list-page'; +import AccountDetailsModal from '../../page-objects/pages/dialog/account-details-modal'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; import { completeImportSRPOnboardingFlow } from '../../page-objects/flows/onboarding.flow'; describe('Import flow @no-mmi', function () { it('Import wallet using Secret Recovery Phrase with pasting word by word', async function () { - const testAddress = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), @@ -32,8 +33,10 @@ describe('Import flow @no-mmi', function () { const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); await accountListPage.openAccountDetailsModal('Account 1'); - await accountListPage.check_addressInAccountDetailsModal( - testAddress.toLowerCase(), + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.check_addressInAccountDetailsModal( + DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), ); }, ); diff --git a/test/e2e/tests/account/incremental-security.spec.js b/test/e2e/tests/account/incremental-security.spec.js index f16fb5350b91..e8295de765ec 100644 --- a/test/e2e/tests/account/incremental-security.spec.js +++ b/test/e2e/tests/account/incremental-security.spec.js @@ -82,7 +82,7 @@ describe('Incremental Security', function () { await driver.findVisibleElement( '[data-testid="account-details-modal"]', ); - await driver.clickElement('button[aria-label="Close"]'); + await driver.clickElement('header button[aria-label="Close"]'); // wait for account modal to be removed from DOM await driver.assertElementNotPresent( diff --git a/test/e2e/tests/account/lock-account.spec.ts b/test/e2e/tests/account/lock-account.spec.ts index b77170c23676..06e1523889e3 100644 --- a/test/e2e/tests/account/lock-account.spec.ts +++ b/test/e2e/tests/account/lock-account.spec.ts @@ -1,7 +1,7 @@ import { Suite } from 'mocha'; import { defaultGanacheOptions, withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; diff --git a/test/e2e/tests/account/migrate-old-vault.spec.ts b/test/e2e/tests/account/migrate-old-vault.spec.ts index 702729bd6fe7..372c94351465 100644 --- a/test/e2e/tests/account/migrate-old-vault.spec.ts +++ b/test/e2e/tests/account/migrate-old-vault.spec.ts @@ -3,7 +3,7 @@ import { defaultGanacheOptions, withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; describe('Migrate vault with old encryption', function (this: Suite) { diff --git a/test/e2e/tests/account/snap-account-contract-interaction.spec.ts b/test/e2e/tests/account/snap-account-contract-interaction.spec.ts index b5cfd4d2707c..0aee1c716714 100644 --- a/test/e2e/tests/account/snap-account-contract-interaction.spec.ts +++ b/test/e2e/tests/account/snap-account-contract-interaction.spec.ts @@ -10,8 +10,9 @@ import { WINDOW_TITLES, } from '../../helpers'; import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import SnapSimpleKeyringPage from '../../page-objects/pages/snap-simple-keyring-page'; import TestDapp from '../../page-objects/pages/test-dapp'; import { installSnapSimpleKeyring } from '../../page-objects/flows/snap-simple-keyring.flow'; @@ -27,7 +28,6 @@ describe('Snap Account Contract interaction @no-mmi', function (this: Suite) { .withPermissionControllerSnapAccountConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -75,8 +75,9 @@ describe('Snap Account Contract interaction @no-mmi', function (this: Suite) { const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); await homePage.goToActivityList(); - await homePage.check_confirmedTxNumberDisplayedInActivity(); - await homePage.check_txAmountInActivity('-4 ETH'); + const activityList = new ActivityListPage(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(); + await activityList.check_txAmountInActivity('-4 ETH'); }, ); }); diff --git a/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts b/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts index 7e355302212a..10b5890053c6 100644 --- a/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts +++ b/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts @@ -2,9 +2,7 @@ import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import { WINDOW_TITLES, withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; -import ExperimentalSettings from '../../page-objects/pages/settings/experimental-settings'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import SettingsPage from '../../page-objects/pages/settings/settings-page'; import SnapSimpleKeyringPage from '../../page-objects/pages/snap-simple-keyring-page'; import TestDapp from '../../page-objects/pages/test-dapp'; import { installSnapSimpleKeyring } from '../../page-objects/flows/snap-simple-keyring.flow'; @@ -19,11 +17,7 @@ describe('Snap Account Signatures and Disconnects @no-mmi', function (this: Suit await withFixtures( { dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp({ - restrictReturnedAccounts: false, - }) - .build(), + fixtures: new FixtureBuilder().build(), title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { @@ -39,24 +33,19 @@ describe('Snap Account Signatures and Disconnects @no-mmi', function (this: Suit const headerNavbar = new HeaderNavbar(driver); await headerNavbar.check_accountLabel('SSK Account'); - // Navigate to experimental settings and disable redesigned signature. - await headerNavbar.openSettingsPage(); - const settingsPage = new SettingsPage(driver); - await settingsPage.check_pageIsLoaded(); - await settingsPage.goToExperimentalSettings(); - - const experimentalSettings = new ExperimentalSettings(driver); - await experimentalSettings.check_pageIsLoaded(); - await experimentalSettings.toggleRedesignedSignature(); - - // Open the Test Dapp and signTypedDataV3 + // Open the Test Dapp and connect const testDapp = new TestDapp(driver); await testDapp.openTestDappPage(); + await testDapp.connectAccount({ publicAddress: newPublicKey }); + + // SignedTypedDataV3 with Test Dapp await signTypedDataV3WithSnapAccount(driver, newPublicKey, false, true); // Disconnect from Test Dapp and reconnect to Test Dapp await testDapp.disconnectAccount(newPublicKey); - await testDapp.connectAccount(newPublicKey); + await testDapp.connectAccount({ + publicAddress: newPublicKey, + }); // SignTypedDataV4 with Test Dapp await signTypedDataV4WithSnapAccount(driver, newPublicKey, false, true); diff --git a/test/e2e/tests/account/snap-account-signatures.spec.ts b/test/e2e/tests/account/snap-account-signatures.spec.ts index e04987561fc5..ca705183abd4 100644 --- a/test/e2e/tests/account/snap-account-signatures.spec.ts +++ b/test/e2e/tests/account/snap-account-signatures.spec.ts @@ -30,11 +30,7 @@ describe('Snap Account Signatures @no-mmi', function (this: Suite) { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp({ - restrictReturnedAccounts: false, - }) - .build(), + fixtures: new FixtureBuilder().build(), title, }, async ({ driver }: { driver: Driver }) => { @@ -60,10 +56,13 @@ describe('Snap Account Signatures @no-mmi', function (this: Suite) { const experimentalSettings = new ExperimentalSettings(driver); await experimentalSettings.check_pageIsLoaded(); - await experimentalSettings.toggleRedesignedSignature(); + + // Connect the SSK account + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.connectAccount({ publicAddress: newPublicKey }); // Run all 5 signature types - await new TestDapp(driver).openTestDappPage(); await personalSignWithSnapAccount( driver, newPublicKey, diff --git a/test/e2e/tests/account/snap-account-transfers.spec.ts b/test/e2e/tests/account/snap-account-transfers.spec.ts index af2a61a62a39..2a0aa78e9825 100644 --- a/test/e2e/tests/account/snap-account-transfers.spec.ts +++ b/test/e2e/tests/account/snap-account-transfers.spec.ts @@ -2,7 +2,6 @@ import { Suite } from 'mocha'; import { multipleGanacheOptions, PRIVATE_KEY_TWO, - tempToggleSettingRedesignedTransactionConfirmations, WINDOW_TITLES, withFixtures, } from '../../helpers'; @@ -10,317 +9,156 @@ import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; import AccountListPage from '../../page-objects/pages/account-list-page'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; import FixtureBuilder from '../../fixture-builder'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import SnapSimpleKeyringPage from '../../page-objects/pages/snap-simple-keyring-page'; import { installSnapSimpleKeyring } from '../../page-objects/flows/snap-simple-keyring.flow'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; -import { - sendRedesignedTransactionWithSnapAccount, - sendTransactionWithSnapAccount, -} from '../../page-objects/flows/send-transaction.flow'; +import { sendRedesignedTransactionWithSnapAccount } from '../../page-objects/flows/send-transaction.flow'; describe('Snap Account Transfers @no-mmi', function (this: Suite) { - // TODO: Remove the old confirmations screen tests once migration has been complete. - // See: https://github.com/MetaMask/MetaMask-planning/issues/3030 - describe('Old confirmation screens', function () { - it('can import a private key and transfer 1 ETH (sync flow)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ + it('can import a private key and transfer 1 ETH (sync flow)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await installSnapSimpleKeyring(driver); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 + await sendRedesignedTransactionWithSnapAccount({ driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await installSnapSimpleKeyring(driver); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 - await sendTransactionWithSnapAccount({ - driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - }); - await headerNavbar.check_pageIsLoaded(); - await headerNavbar.openAccountMenu(); - const accountList = new AccountListPage(driver); - await accountList.check_pageIsLoaded(); - - // check the balance of the 2 accounts are updated - await accountList.check_accountBalanceDisplayed('26'); - await accountList.check_accountBalanceDisplayed('24'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow approve)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ - driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await installSnapSimpleKeyring(driver, false); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 and approve the transaction - await sendTransactionWithSnapAccount({ - driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - isSyncFlow: false, - }); - await headerNavbar.check_pageIsLoaded(); - await headerNavbar.openAccountMenu(); - const accountList = new AccountListPage(driver); - await accountList.check_pageIsLoaded(); - - // check the balance of the 2 accounts are updated - await accountList.check_accountBalanceDisplayed('26'); - await accountList.check_accountBalanceDisplayed('24'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow reject)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - ignoredConsoleErrors: ['Request rejected by user or snap.'], - }, - async ({ - driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await installSnapSimpleKeyring(driver, false); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // Import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 and reject the transaction - await sendTransactionWithSnapAccount({ - driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - isSyncFlow: false, - approveTransaction: false, - }); - - // check the transaction is failed in MetaMask activity list - const homepage = new HomePage(driver); - await homepage.check_pageIsLoaded(); - await homepage.check_failedTxNumberDisplayedInActivity(); - }, - ); - }); + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + }); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + + // check the balance of the 2 accounts are updated + await accountList.check_accountBalanceDisplayed('26'); + await accountList.check_accountBalanceDisplayed('24'); + }, + ); }); - describe('Redesigned confirmation screens', function () { - it('can import a private key and transfer 1 ETH (sync flow)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ + it('can import a private key and transfer 1 ETH (async flow approve)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await installSnapSimpleKeyring(driver, false); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 and approve the transaction + await sendRedesignedTransactionWithSnapAccount({ driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await installSnapSimpleKeyring(driver); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 - await sendRedesignedTransactionWithSnapAccount({ - driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - }); - await headerNavbar.check_pageIsLoaded(); - await headerNavbar.openAccountMenu(); - const accountList = new AccountListPage(driver); - await accountList.check_pageIsLoaded(); - - // check the balance of the 2 accounts are updated - await accountList.check_accountBalanceDisplayed('26'); - await accountList.check_accountBalanceDisplayed('24'); - }, - ); - }); + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + isSyncFlow: false, + }); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + + // check the balance of the 2 accounts are updated + await accountList.check_accountBalanceDisplayed('26'); + await accountList.check_accountBalanceDisplayed('24'); + }, + ); + }); - it('can import a private key and transfer 1 ETH (async flow approve)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ + it('can import a private key and transfer 1 ETH (async flow reject)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + ignoredConsoleErrors: ['Request rejected by user or snap.'], + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await installSnapSimpleKeyring(driver, false); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // Import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 and reject the transaction + await sendRedesignedTransactionWithSnapAccount({ driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await installSnapSimpleKeyring(driver, false); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 and approve the transaction - await sendRedesignedTransactionWithSnapAccount({ - driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - isSyncFlow: false, - }); - await headerNavbar.check_pageIsLoaded(); - await headerNavbar.openAccountMenu(); - const accountList = new AccountListPage(driver); - await accountList.check_pageIsLoaded(); - - // check the balance of the 2 accounts are updated - await accountList.check_accountBalanceDisplayed('26'); - await accountList.check_accountBalanceDisplayed('24'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow reject)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - ignoredConsoleErrors: ['Request rejected by user or snap.'], - }, - async ({ + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + isSyncFlow: false, + approveTransaction: false, + }); + + // check the transaction is failed in MetaMask activity list + await new HomePage(driver).check_pageIsLoaded(); + await new ActivityListPage( driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await installSnapSimpleKeyring(driver, false); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // Import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 and reject the transaction - await sendRedesignedTransactionWithSnapAccount({ - driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - isSyncFlow: false, - approveTransaction: false, - }); - - // check the transaction is failed in MetaMask activity list - const homepage = new HomePage(driver); - await homepage.check_pageIsLoaded(); - await homepage.check_failedTxNumberDisplayedInActivity(); - }, - ); - }); + ).check_failedTxNumberDisplayedInActivity(); + }, + ); }); }); diff --git a/test/e2e/tests/bridge/bridge-button-routing.spec.ts b/test/e2e/tests/bridge/bridge-button-routing.spec.ts index a3ecb70a2967..90cd03bd6dff 100644 --- a/test/e2e/tests/bridge/bridge-button-routing.spec.ts +++ b/test/e2e/tests/bridge/bridge-button-routing.spec.ts @@ -1,11 +1,17 @@ import { Suite } from 'mocha'; import { logInWithBalanceValidation, withFixtures } from '../../helpers'; import { BridgePage, getBridgeFixtures } from './bridge-test-utils'; +import { DEFAULT_FEATURE_FLAGS_RESPONSE } from './constants'; describe('Click bridge button @no-mmi', function (this: Suite) { it('loads placeholder swap route from wallet overview when flag is turned on', async function () { await withFixtures( - getBridgeFixtures(this.test?.fullTitle(), { 'extension-support': true }), + getBridgeFixtures(this.test?.fullTitle(), { + 'extension-config': { + ...DEFAULT_FEATURE_FLAGS_RESPONSE['extension-config'], + support: true, + }, + }), async ({ driver, ganacheServer }) => { const bridgePage = new BridgePage(driver); await logInWithBalanceValidation(driver, ganacheServer); diff --git a/test/e2e/tests/bridge/bridge-test-utils.ts b/test/e2e/tests/bridge/bridge-test-utils.ts index 930f0196673e..842ff31fff39 100644 --- a/test/e2e/tests/bridge/bridge-test-utils.ts +++ b/test/e2e/tests/bridge/bridge-test-utils.ts @@ -12,7 +12,7 @@ import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { Driver } from '../../webdriver/driver'; import { isManifestV3 } from '../../../../shared/modules/mv3.utils'; -import { FeatureFlagResponse } from '../../../../ui/pages/bridge/types'; +import type { FeatureFlagResponse } from '../../../../shared/types/bridge'; import { DEFAULT_FEATURE_FLAGS_RESPONSE, ETH_CONVERSION_RATE_USD, @@ -112,6 +112,10 @@ const mockServer = json: { ...DEFAULT_FEATURE_FLAGS_RESPONSE, ...featureFlagOverrides, + 'extension-config': { + ...DEFAULT_FEATURE_FLAGS_RESPONSE['extension-config'], + ...featureFlagOverrides['extension-config'], + }, }, }; }), diff --git a/test/e2e/tests/bridge/constants.ts b/test/e2e/tests/bridge/constants.ts index ae7fc37a62c6..844cec673509 100644 --- a/test/e2e/tests/bridge/constants.ts +++ b/test/e2e/tests/bridge/constants.ts @@ -1,13 +1,16 @@ -import { FeatureFlagResponse } from '../../../../ui/pages/bridge/types'; +import type { FeatureFlagResponse } from '../../../../shared/types/bridge'; export const DEFAULT_FEATURE_FLAGS_RESPONSE: FeatureFlagResponse = { 'extension-config': { refreshRate: 30, maxRefreshCount: 5, + support: false, + chains: { + '1': { isActiveSrc: true, isActiveDest: true }, + '42161': { isActiveSrc: true, isActiveDest: true }, + '59144': { isActiveSrc: true, isActiveDest: true }, + }, }, - 'extension-support': false, - 'src-network-allowlist': [1, 42161, 59144], - 'dest-network-allowlist': [1, 42161, 59144], }; export const LOCATOR = { diff --git a/test/e2e/tests/carousel/carousel.spec.ts b/test/e2e/tests/carousel/carousel.spec.ts new file mode 100644 index 000000000000..833fbd5ed00c --- /dev/null +++ b/test/e2e/tests/carousel/carousel.spec.ts @@ -0,0 +1,115 @@ +import { strict as assert } from 'assert'; +import { withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Carousel component e2e tests', () => { + it('should display correct slides with expected content', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + + await driver.waitForSelector( + '[data-testid="eth-overview__primary-currency"]', + ); + + await driver.waitForSelector('.mm-carousel'); + await driver.waitForSelector('.mm-carousel-slide'); + + const slides = await driver.findElements('.mm-carousel-slide'); + assert.ok(slides.length > 0, 'Carousel should have slides'); + + const slideIds = ['bridge', 'card', 'fund', 'cash']; + + const firstSlideSelector = `[data-testid="slide-${slideIds[0]}"]`; + await driver.waitForSelector(firstSlideSelector); + + for (let i = 0; i < slideIds.length; i++) { + if (i > 0) { + const dots = await driver.findElements('.dot'); + await dots[i].click(); + await driver.waitForSelector( + `[data-testid="slide-${slideIds[i]}"]`, + ); + } + + const slideSelector = `[data-testid="slide-${slideIds[i]}"]`; + const currentSlide = await driver.waitForSelector(slideSelector); + assert.ok( + currentSlide, + `Slide with data-testid="slide-${slideIds[i]}" should exist`, + ); + + const hasTitle = await driver.isElementPresent( + `${slideSelector} .mm-text--body-sm-medium`, + ); + const hasDescription = await driver.isElementPresent( + `${slideSelector} .mm-text--body-xs`, + ); + + assert.ok(hasTitle, `Slide ${slideIds[i]} should have a title`); + assert.ok( + hasDescription, + `Slide ${slideIds[i]} should have a description`, + ); + } + }, + ); + }); + + it('should handle slide dismissal', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + await driver.waitForSelector('.mm-carousel'); + await driver.waitForSelector('.mm-carousel-slide'); + + const initialSlides = await driver.findElements('.mm-carousel-slide'); + assert.equal(initialSlides.length, 4); + + for (let i = 0; i < 4; i++) { + const currentSlides = await driver.findElements('.mm-carousel-slide'); + assert.equal( + currentSlides.length, + 4 - i, + `Expected ${4 - i} slides remaining`, + ); + + const dismissButton = await driver.findElement( + '.mm-carousel-slide:first-child .mm-carousel-slide__close-button', + ); + await dismissButton.click(); + + if (i < 3) { + await driver.wait(async () => { + const remainingSlides = await driver.findElements( + '.mm-carousel-slide', + ); + return remainingSlides.length === 3 - i; + }, 5e3); + } + } + + await driver.wait(async () => { + const carouselExists = await driver.isElementPresent('.mm-carousel'); + return !carouselExists; + }, 5e3); + + const carouselExists = await driver.isElementPresent('.mm-carousel'); + assert.equal( + carouselExists, + false, + 'Carousel should no longer be visible', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts b/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts index 3aa8ecc88ebc..c5f9c46c7789 100644 --- a/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts +++ b/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts @@ -30,7 +30,6 @@ describe('Alert for insufficient funds @no-mmi', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) diff --git a/test/e2e/tests/confirmations/alerts/queued-confirmations.spec.ts b/test/e2e/tests/confirmations/alerts/queued-confirmations.spec.ts index 8ecd7e908a30..622e18978caf 100644 --- a/test/e2e/tests/confirmations/alerts/queued-confirmations.spec.ts +++ b/test/e2e/tests/confirmations/alerts/queued-confirmations.spec.ts @@ -41,7 +41,7 @@ describe('Queued Confirmations', function () { dapp: true, fixtures: new FixtureBuilder() .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() .build(), dappOptions: { numberOfDapps: 2 }, @@ -86,7 +86,7 @@ describe('Queued Confirmations', function () { dapp: true, fixtures: new FixtureBuilder() .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() .build(), dappOptions: { numberOfDapps: 2 }, @@ -139,10 +139,6 @@ describe('Queued Confirmations', function () { fixtures: new FixtureBuilder() .withNetworkControllerTripleGanache() .withPermissionControllerConnectedToTestDapp() - .withPreferencesController({ - preferences: { redesignedConfirmationsEnabled: true }, - useRequestQueue: true, - }) .withSelectedNetworkControllerPerDomain() .build(), dappOptions: { numberOfDapps: 2 }, @@ -193,7 +189,7 @@ describe('Queued Confirmations', function () { dapp: true, fixtures: new FixtureBuilder() .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() .withMetaMetricsController({ metaMetricsId: 'fake-metrics-id', @@ -279,10 +275,6 @@ describe('Queued Confirmations', function () { fixtures: new FixtureBuilder() .withNetworkControllerTripleGanache() .withPermissionControllerConnectedToTestDapp() - .withPreferencesController({ - preferences: { redesignedConfirmationsEnabled: true }, - useRequestQueue: true, - }) .withSelectedNetworkControllerPerDomain() .withMetaMetricsController({ metaMetricsId: 'fake-metrics-id', diff --git a/test/e2e/tests/confirmations/helpers.ts b/test/e2e/tests/confirmations/helpers.ts index 2b1078549c5b..3e8bc10cc217 100644 --- a/test/e2e/tests/confirmations/helpers.ts +++ b/test/e2e/tests/confirmations/helpers.ts @@ -8,10 +8,15 @@ import { import { MockedEndpoint, Mockttp } from '../../mock-e2e'; import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; import { Driver } from '../../webdriver/driver'; +import Confirmation from '../../page-objects/pages/confirmations/redesign/confirmation'; + +export const DECODING_E2E_API_URL = + 'https://signature-insights.api.cx.metamask.io/v1'; export async function scrollAndConfirmAndAssertConfirm(driver: Driver) { - await driver.clickElementSafe('.confirm-scroll-to-bottom__button'); - await driver.clickElement('[data-testid="confirm-footer-button"]'); + const confirmation = new Confirmation(driver); + await confirmation.clickScrollToBottomButton(); + await confirmation.clickFooterConfirmButton(); } export function withTransactionEnvelopeTypeFixtures( @@ -59,6 +64,33 @@ async function createMockSegmentEvent(mockServer: Mockttp, eventName: string) { })); } +async function createMockSignatureDecodingEvent(mockServer: Mockttp) { + return await mockServer + .forPost(`${DECODING_E2E_API_URL}/signature`) + .thenCallback(() => ({ + statusCode: 200, + json: { + stateChanges: [ + { + assetType: 'NATIVE', + changeType: 'RECEIVE', + address: '', + amount: '900000000000000000', + contractAddress: '', + }, + { + assetType: 'ERC721', + changeType: 'LISTING', + address: '', + amount: '', + contractAddress: '0xafd4896984CA60d2feF66136e57f958dCe9482d5', + tokenID: '2101', + }, + ], + }, + })); +} + export async function mockSignatureApproved( mockServer: Mockttp, withAnonEvents = false, @@ -78,6 +110,16 @@ export async function mockSignatureApproved( ]; } +export async function mockSignatureApprovedWithDecoding( + mockServer: Mockttp, + withAnonEvents = false, +) { + return [ + ...(await mockSignatureApproved(mockServer, withAnonEvents)), + await createMockSignatureDecodingEvent(mockServer), + ]; +} + export async function mockSignatureRejected( mockServer: Mockttp, withAnonEvents = false, @@ -95,3 +137,17 @@ export async function mockSignatureRejected( ...anonEvents, ]; } + +export async function mockSignatureRejectedWithDecoding( + mockServer: Mockttp, + withAnonEvents = false, +) { + return [ + ...(await mockSignatureRejected(mockServer, withAnonEvents)), + await createMockSignatureDecodingEvent(mockServer), + ]; +} + +export async function mockPermitDecoding(mockServer: Mockttp) { + return [await createMockSignatureDecodingEvent(mockServer)]; +} diff --git a/test/e2e/tests/confirmations/navigation.spec.ts b/test/e2e/tests/confirmations/navigation.spec.ts index 97985381b08b..9f26fa697068 100644 --- a/test/e2e/tests/confirmations/navigation.spec.ts +++ b/test/e2e/tests/confirmations/navigation.spec.ts @@ -8,9 +8,14 @@ import { WINDOW_TITLES, } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { createDappTransaction } from '../../page-objects/flows/transaction'; +import { TestSnaps } from '../../page-objects/pages/test-snaps'; +import Confirmation from '../../page-objects/pages/confirmations/redesign/confirmation'; import { withTransactionEnvelopeTypeFixtures } from './helpers'; -describe('Navigation Signature - Different signature types', function (this: Suite) { +describe('Confirmation Navigation', function (this: Suite) { it('initiates and queues multiple signatures and confirms', async function () { await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), @@ -120,6 +125,49 @@ describe('Navigation Signature - Different signature types', function (this: Sui }, ); }); + + it('navigates between transactions, signatures, and snap dialogs', async function () { + await withTransactionEnvelopeTypeFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.feeMarket, + async ({ driver }: { driver: Driver }) => { + await loginWithoutBalanceValidation(driver); + + const testSnaps = new TestSnaps(driver); + await testSnaps.openPage(); + await testSnaps.clickConnectDialogsSnapButton(); + await testSnaps.completeSnapInstallConfirmation(); + await testSnaps.clickDialogsSnapConfirmationButton(); + + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.clickSignTypedDatav4(); + + await createDappTransaction(driver); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const confirmation = new Confirmation(driver); + await confirmation.check_pageNumbers(1, 3); + await driver.waitForSelector({ text: 'Confirmation Dialog' }); + + await confirmation.clickNextPage(); + await confirmation.check_pageNumbers(2, 3); + await driver.waitForSelector({ text: 'Signature request' }); + + await confirmation.clickNextPage(); + await confirmation.check_pageNumbers(3, 3); + await driver.waitForSelector({ text: 'Transfer request' }); + + await confirmation.clickPreviousPage(); + await confirmation.check_pageNumbers(2, 3); + await driver.waitForSelector({ text: 'Signature request' }); + + await confirmation.clickPreviousPage(); + await confirmation.check_pageNumbers(1, 3); + await driver.waitForSelector({ text: 'Confirmation Dialog' }); + }, + ); + }); }); async function verifySignTypedData(driver: Driver) { diff --git a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts index 5876a4d5e17f..fb01aa7267c8 100644 --- a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts +++ b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts @@ -1,21 +1,24 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; import { WINDOW_TITLES } from '../../../helpers'; -import { Driver } from '../../../webdriver/driver'; import { mockSignatureRejected, scrollAndConfirmAndAssertConfirm, withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import Confirmation from '../../../page-objects/pages/confirmations/redesign/confirmation'; +import ConfirmAlertModal from '../../../page-objects/pages/dialog/confirm-alert'; import { BlockaidReason, BlockaidResultType, } from '../../../../../shared/constants/security-provider'; import { + assertRejectedSignature, assertSignatureRejectedMetrics, + assertVerifiedSiweMessage, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -26,17 +29,22 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: TestSuiteArguments) => { + await initializePages(driver); + const confirmation = new Confirmation(driver); + const alertModal = new ConfirmAlertModal(driver); + await openDappAndTriggerSignature(driver, SignatureType.SIWE_BadDomain); - await verifyAlertIsDisplayed(driver); + await confirmation.clickScrollToBottomButton(); + await confirmation.clickInlineAlert(); - await acknowledgeAlert(driver); + await alertModal.acknowledgeAlert(); await scrollAndConfirmAndAssertConfirm(driver); - await confirmFromAlertModal(driver); + await alertModal.confirmFromAlertModal(); - await assertVerifiedMessage( + await assertVerifiedSiweMessage( driver, '0x24e559452c37827008633f9ae50c68cdb28e33f547f795af687839b520b022e4093c38bf1dfebda875ded715f2754d458ed62a19248e5a9bd2205bd1cb66f9b51b', ); @@ -52,19 +60,16 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { - await openDappAndTriggerSignature(driver, SignatureType.SIWE_BadDomain); + await initializePages(driver); + const confirmation = new Confirmation(driver); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await openDappAndTriggerSignature(driver, SignatureType.SIWE_BadDomain); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const rejectionResult = await driver.waitForSelector({ - css: '#siweResult', - text: 'Error: User rejected the request.', - }); - assert.ok(rejectionResult); + await assertRejectedSignature(); + await assertSignatureRejectedMetrics({ driver, mockedEndpoints: mockedEndpoints as MockedEndpoint[], @@ -100,23 +105,21 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); + const alertModal = new ConfirmAlertModal(driver); + await openDappAndTriggerSignature(driver, SignatureType.SIWE_BadDomain); await scrollAndConfirmAndAssertConfirm(driver); - await acknowledgeAlert(driver); + await alertModal.acknowledgeAlert(); + + await alertModal.rejectFromAlertModal(); - await driver.clickElement( - '[data-testid="confirm-alert-modal-cancel-button"]', - ); await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const rejectionResult = await driver.waitForSelector({ - css: '#siweResult', - text: 'Error: User rejected the request.', - }); - assert.ok(rejectionResult); + await assertRejectedSignature(); await assertSignatureRejectedMetrics({ driver, mockedEndpoints: mockedEndpoints as MockedEndpoint[], @@ -144,34 +147,3 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this ); }); }); - -async function confirmFromAlertModal(driver: Driver) { - await driver.clickElement('[data-testid="alert-modal-acknowledge-checkbox"]'); - await driver.clickElement( - '[data-testid="confirm-alert-modal-submit-button"]', - ); -} - -async function acknowledgeAlert(driver: Driver) { - await driver.clickElement('[data-testid="alert-modal-acknowledge-checkbox"]'); - await driver.clickElement('[data-testid="alert-modal-button"]'); -} - -async function verifyAlertIsDisplayed(driver: Driver) { - await driver.clickElementSafe('.confirm-scroll-to-bottom__button'); - await driver.waitForSelector({ - css: '[data-testid="inline-alert"]', - text: 'Alert', - }); - await driver.clickElement('[data-testid="inline-alert"]'); -} - -async function assertVerifiedMessage(driver: Driver, message: string) { - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await driver.waitForSelector({ - css: '#siweResult', - text: message, - }); -} diff --git a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts index de70d25b359b..cd485080208b 100644 --- a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts @@ -1,24 +1,27 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { DAPP_HOST_ADDRESS, WINDOW_TITLES } from '../../../helpers'; +import { WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { - mockSignatureApproved, - mockSignatureRejected, + mockSignatureApprovedWithDecoding, + mockSignatureRejectedWithDecoding, scrollAndConfirmAndAssertConfirm, withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import PermitConfirmation from '../../../page-objects/pages/confirmations/redesign/permit-confirmation'; +import TestDapp from '../../../page-objects/pages/test-dapp'; import { assertAccountDetailsMetrics, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerDeploy, SignatureType, triggerSignature, @@ -36,20 +39,21 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { }: TestSuiteArguments) => { const addresses = await (ganacheServer as Ganache).getAccounts(); const publicAddress = addresses?.[0] as string; + await initializePages(driver); await openDappAndTriggerDeploy(driver); await driver.delay(1000); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement('[data-testid="confirm-footer-button"]'); + await scrollAndConfirmAndAssertConfirm(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await driver.delay(1000); - await triggerSignature(driver, SignatureType.NFTPermit); + await triggerSignature(SignatureType.NFTPermit); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await clickHeaderInfoBtn(driver); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await assertInfoValues(driver); @@ -68,11 +72,14 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { signatureType: 'eth_signTypedData_v4', primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], + decodingChangeTypes: ['LISTING', 'RECEIVE'], + decodingResponse: 'CHANGE', + decodingDescription: null, }); await assertVerifiedResults(driver, publicAddress); }, - mockSignatureApproved, + mockSignatureApprovedWithDecoding, ); }); @@ -84,6 +91,8 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); + const confirmation = new PermitConfirmation(driver); await openDappAndTriggerDeploy(driver); await driver.delay(1000); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); @@ -91,18 +100,14 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await driver.delay(1000); - await triggerSignature(driver, SignatureType.NFTPermit); + await triggerSignature(SignatureType.NFTPermit); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - tag: 'span', - text: 'Error: User rejected the request.', - }); + await assertRejectedSignature(); await assertSignatureRejectedMetrics({ driver, @@ -111,77 +116,45 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], location: 'confirmation', + decodingChangeTypes: ['LISTING', 'RECEIVE'], + decodingResponse: 'CHANGE', + decodingDescription: null, }); }, - mockSignatureRejected, + mockSignatureRejectedWithDecoding, ); }); }); async function assertInfoValues(driver: Driver) { - await driver.clickElement('[data-testid="sectionCollapseButton"]'); - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const contractPetName = driver.findElement({ - css: '.name__value', - text: '0x581c3...45947', - }); - - const title = driver.findElement({ text: 'Withdrawal request' }); - const description = driver.findElement({ - text: 'This site wants permission to withdraw your NFTs', - }); - const primaryType = driver.findElement({ text: 'Permit' }); - const spender = driver.findElement({ - css: '.name__value', - text: '0x581c3...45947', - }); - const tokenId = driver.findElement({ text: '3606393' }); - const nonce = driver.findElement({ text: '0' }); - const deadline = driver.findElement({ text: '23 December 2024, 23:03' }); - - assert.ok(await origin, 'origin'); - assert.ok(await contractPetName, 'contractPetName'); - assert.ok(await title, 'title'); - assert.ok(await description, 'description'); - assert.ok(await primaryType, 'primaryType'); - assert.ok(await spender, 'spender'); - assert.ok(await tokenId, 'tokenId'); - assert.ok(await nonce, 'nonce'); - assert.ok(await deadline, 'deadline'); + const confirmation = new PermitConfirmation(driver); + await confirmation.clickCollapseSectionButton(); + await confirmation.verifyOrigin(); + await confirmation.verifyNftContractPetName(); + await confirmation.verifyNftTitle(); + await confirmation.verifyNftDescription(); + await confirmation.verifyNftPrimaryType(); + await confirmation.verifyNftSpender(); + await confirmation.verifyNftTokenId(); + await confirmation.verifyNftNonce(); + await confirmation.verifyNftDeadline(); } async function assertVerifiedResults(driver: Driver, publicAddress: string) { + const testDapp = new TestDapp(driver); await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#sign721PermitVerify'); - - await driver.waitForSelector({ - css: '#sign721PermitVerifyResult', - text: publicAddress, - }); - - await driver.waitForSelector({ - css: '#sign721PermitResult', - text: '0x572bc6300f6aa669e85e0a7792bc0b0803fb70c3c492226b30007ff7030b03600e390ef295a5a525d19f444943ae82697f0e5b5b0d77cc382cb2ea9486ec27801c', - }); - await driver.waitForSelector({ - css: '#sign721PermitResultR', - text: 'r: 0x572bc6300f6aa669e85e0a7792bc0b0803fb70c3c492226b30007ff7030b0360', - }); - - await driver.waitForSelector({ - css: '#sign721PermitResultS', - text: 's: 0x0e390ef295a5a525d19f444943ae82697f0e5b5b0d77cc382cb2ea9486ec2780', - }); - - await driver.waitForSelector({ - css: '#sign721PermitResultV', - text: 'v: 28', - }); - - await driver.waitForSelector({ - css: '#sign721PermitVerifyResult', - text: publicAddress, - }); + await testDapp.check_successSign721Permit(publicAddress); + await testDapp.verifySign721PermitResult( + '0x572bc6300f6aa669e85e0a7792bc0b0803fb70c3c492226b30007ff7030b03600e390ef295a5a525d19f444943ae82697f0e5b5b0d77cc382cb2ea9486ec27801c', + ); + await testDapp.verifySign721PermitResultR( + '0x572bc6300f6aa669e85e0a7792bc0b0803fb70c3c492226b30007ff7030b0360', + ); + await testDapp.verifySign721PermitResultS( + '0x0e390ef295a5a525d19f444943ae82697f0e5b5b0d77cc382cb2ea9486ec2780', + ); + await testDapp.verifySign721PermitResultV('28'); + await driver.clickElement('#sign721PermitVerify'); } diff --git a/test/e2e/tests/confirmations/signatures/permit.spec.ts b/test/e2e/tests/confirmations/signatures/permit.spec.ts index f6c8fc972b5f..d54ba056242d 100644 --- a/test/e2e/tests/confirmations/signatures/permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/permit.spec.ts @@ -2,29 +2,30 @@ import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { - DAPP_HOST_ADDRESS, - openDapp, - unlockWallet, - WINDOW_TITLES, -} from '../../../helpers'; +import { openDapp, unlockWallet, WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { - mockSignatureApproved, - mockSignatureRejected, + mockPermitDecoding, + mockSignatureApprovedWithDecoding, + mockSignatureRejectedWithDecoding, scrollAndConfirmAndAssertConfirm, withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import TestDapp from '../../../page-objects/pages/test-dapp'; +import Confirmation from '../../../page-objects/pages/confirmations/redesign/confirmation'; +import PermitConfirmation from '../../../page-objects/pages/confirmations/redesign/permit-confirmation'; import { assertAccountDetailsMetrics, assertHeaderInfoBalance, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -41,14 +42,15 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { }: TestSuiteArguments) => { const addresses = await (ganacheServer as Ganache).getAccounts(); const publicAddress = addresses?.[0] as string; + await initializePages(driver); await openDappAndTriggerSignature(driver, SignatureType.Permit); await clickHeaderInfoBtn(driver); - await assertHeaderInfoBalance(driver); + await assertHeaderInfoBalance(); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await assertInfoValues(driver); @@ -67,11 +69,14 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { signatureType: 'eth_signTypedData_v4', primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], + decodingChangeTypes: ['LISTING', 'RECEIVE'], + decodingResponse: 'CHANGE', + decodingDescription: null, }); await assertVerifiedResults(driver, publicAddress); }, - mockSignatureApproved, + mockSignatureApprovedWithDecoding, ); }); @@ -83,21 +88,18 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + const testDapp = new TestDapp(driver); + const confirmation = new Confirmation(driver); await unlockWallet(driver); await openDapp(driver); - await driver.clickElement('#signPermit'); + await testDapp.clickPermit(); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - tag: 'span', - text: 'Error: User rejected the request.', - }); + await assertRejectedSignature(); await assertSignatureRejectedMetrics({ driver, @@ -106,72 +108,71 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], location: 'confirmation', + decodingChangeTypes: ['LISTING', 'RECEIVE'], + decodingResponse: 'CHANGE', + decodingDescription: null, }); }, - mockSignatureRejected, + mockSignatureRejectedWithDecoding, ); }); -}); -async function assertInfoValues(driver: Driver) { - await driver.clickElement('[data-testid="sectionCollapseButton"]'); - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const contractPetName = driver.findElement({ - css: '.name__value', - text: '0xCcCCc...ccccC', - }); + it('display decoding information if available', async function () { + await withTransactionEnvelopeTypeFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.legacy, + async ({ driver }: TestSuiteArguments) => { + await initializePages(driver); + await openDappAndTriggerSignature(driver, SignatureType.Permit); - const primaryType = driver.findElement({ text: 'Permit' }); - const owner = driver.findElement({ css: '.name__name', text: 'Account 1' }); - const spender = driver.findElement({ - css: '.name__value', - text: '0x5B38D...eddC4', - }); - const value = driver.findElement({ text: '3,000' }); - const nonce = driver.findElement({ text: '0' }); - const deadline = driver.findElement({ text: '09 June 3554, 16:53' }); - - assert.ok(await origin, 'origin'); - assert.ok(await contractPetName, 'contractPetName'); - assert.ok(await primaryType, 'primaryType'); - assert.ok(await owner, 'owner'); - assert.ok(await spender, 'spender'); - assert.ok(await value, 'value'); - assert.ok(await nonce, 'nonce'); - assert.ok(await deadline, 'deadline'); -} + const simulationSection = driver.findElement({ + text: 'Estimated changes', + }); + const receiveChange = driver.findElement({ text: 'Listing price' }); + const listChange = driver.findElement({ text: 'You list' }); + const listChangeValue = driver.findElement({ text: '#2101' }); -async function assertVerifiedResults(driver: Driver, publicAddress: string) { - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#signPermitVerify'); + assert.ok(await simulationSection, 'Estimated changes'); + assert.ok(await receiveChange, 'Listing price'); + assert.ok(await listChange, 'You list'); + assert.ok(await listChangeValue, '#2101'); - await driver.waitForSelector({ - css: '#signPermitVerifyResult', - text: publicAddress, + await driver.delay(10000); + }, + mockPermitDecoding, + ); }); +}); - await driver.waitForSelector({ - css: '#signPermitResult', - text: '0xf6555e4cc39bdec3397c357af876f87de00667c942f22dec555c28d290ed7d730103fe85c9d7c66d808a0a972f69ae00741a11df449475280772e7d9a232ea491b', - }); +async function assertInfoValues(driver: Driver) { + const permitConfirmation = new PermitConfirmation(driver); + await permitConfirmation.clickCollapseSectionButton(); + await permitConfirmation.verifyOrigin(); + await permitConfirmation.verifyContractPetName(); + await permitConfirmation.verifyPrimaryType(); + await permitConfirmation.verifyOwner(); + await permitConfirmation.verifySpender(); + await permitConfirmation.verifyValue(); + await permitConfirmation.verifyNonce(); + await permitConfirmation.verifyDeadline(); +} - await driver.waitForSelector({ - css: '#signPermitResultR', - text: 'r: 0xf6555e4cc39bdec3397c357af876f87de00667c942f22dec555c28d290ed7d73', - }); +async function assertVerifiedResults(driver: Driver, publicAddress: string) { + const testDapp = new TestDapp(driver); + const expectedSignature = + '0xf6555e4cc39bdec3397c357af876f87de00667c942f22dec555c28d290ed7d730103fe85c9d7c66d808a0a972f69ae00741a11df449475280772e7d9a232ea491b'; + const expectedR = + '0xf6555e4cc39bdec3397c357af876f87de00667c942f22dec555c28d290ed7d73'; + const expectedS = + '0x0103fe85c9d7c66d808a0a972f69ae00741a11df449475280772e7d9a232ea49'; + const expectedV = '27'; - await driver.waitForSelector({ - css: '#signPermitResultS', - text: 's: 0x0103fe85c9d7c66d808a0a972f69ae00741a11df449475280772e7d9a232ea49', - }); + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#signPermitResultV', - text: 'v: 27', - }); - await driver.waitForSelector({ - css: '#signPermitVerifyResult', - text: publicAddress, - }); + await testDapp.check_successSignPermit(publicAddress); + await testDapp.verifySignPermitResult(expectedSignature); + await testDapp.verifySignPermitResultR(expectedR); + await testDapp.verifySignPermitResultS(expectedS); + await testDapp.verifySignPermitResultV(expectedV); } diff --git a/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts b/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts index 5f87c2d6b6e8..2bb67736df12 100644 --- a/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts +++ b/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts @@ -1,24 +1,28 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { DAPP_HOST_ADDRESS, WINDOW_TITLES } from '../../../helpers'; +import { WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { mockSignatureApproved, mockSignatureRejected, + scrollAndConfirmAndAssertConfirm, withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import TestDapp from '../../../page-objects/pages/test-dapp'; +import PersonalSignConfirmation from '../../../page-objects/pages/confirmations/redesign/personal-sign-confirmation'; import { assertAccountDetailsMetrics, assertHeaderInfoBalance, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -35,18 +39,19 @@ describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite }: TestSuiteArguments) => { const addresses = await (ganacheServer as Ganache).getAccounts(); const publicAddress = addresses?.[0] as string; + await initializePages(driver); await openDappAndTriggerSignature(driver, SignatureType.PersonalSign); await clickHeaderInfoBtn(driver); - await assertHeaderInfoBalance(driver); + await assertHeaderInfoBalance(); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await assertInfoValues(driver); - await driver.clickElement('[data-testid="confirm-footer-button"]'); + await scrollAndConfirmAndAssertConfirm(driver); await assertVerifiedPersonalMessage(driver, publicAddress); @@ -73,19 +78,17 @@ describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + const confirmation = new PersonalSignConfirmation(driver); + + await initializePages(driver); await openDappAndTriggerSignature(driver, SignatureType.PersonalSign); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const rejectionResult = await driver.waitForSelector({ - css: '#personalSign', - text: 'Error: User rejected the request.', - }); - assert.ok(rejectionResult); + await assertRejectedSignature(); + await assertSignatureRejectedMetrics({ driver, mockedEndpoints: mockedEndpoints as MockedEndpoint[], @@ -99,35 +102,19 @@ describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite }); async function assertInfoValues(driver: Driver) { - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const message = driver.findElement({ - text: 'Example `personal_sign` message', - }); - - assert.ok(await origin); - assert.ok(await message); + const personalSignConfirmation = new PersonalSignConfirmation(driver); + personalSignConfirmation.verifyOrigin(); + personalSignConfirmation.verifyMessage(); } async function assertVerifiedPersonalMessage( driver: Driver, publicAddress: string, ) { + const testDapp = new TestDapp(driver); await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#personalSignVerify'); - await driver.waitForSelector({ - css: '#personalSignVerifyECRecoverResult', - text: publicAddress, - }); - - await driver.waitForSelector({ - css: '#personalSignVerifySigUtilResult', - text: publicAddress, - }); - - await driver.waitForSelector({ - css: '#personalSignVerifyECRecoverResult', - text: publicAddress, - }); + await testDapp.check_successPersonalSign(publicAddress); + await testDapp.verifyPersonalSignSigUtilResult(publicAddress); } diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts index 7ea7f0879279..949d79dd97a3 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts @@ -1,8 +1,7 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { DAPP_HOST_ADDRESS, WINDOW_TITLES } from '../../../helpers'; +import { WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { @@ -12,14 +11,18 @@ import { withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import SignTypedData from '../../../page-objects/pages/confirmations/redesign/sign-typed-data-confirmation'; +import TestDapp from '../../../page-objects/pages/test-dapp'; import { assertAccountDetailsMetrics, assertHeaderInfoBalance, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -36,6 +39,7 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: }: TestSuiteArguments) => { const addresses = await (ganacheServer as Ganache).getAccounts(); const publicAddress = addresses?.[0] as string; + await initializePages(driver); await openDappAndTriggerSignature( driver, @@ -43,12 +47,12 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: ); await clickHeaderInfoBtn(driver); - await assertHeaderInfoBalance(driver); + await assertHeaderInfoBalance(); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.delay(1000); await assertInfoValues(driver); await scrollAndConfirmAndAssertConfirm(driver); @@ -77,14 +81,15 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); + const confirmation = new SignTypedData(driver); + await openDappAndTriggerSignature( driver, SignatureType.SignTypedDataV3, ); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await assertSignatureRejectedMetrics({ driver, @@ -93,13 +98,9 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: location: 'confirmation', }); - await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#signTypedDataV3Result', - text: 'Error: User rejected the request.', - }); + await assertRejectedSignature(); }, mockSignatureRejected, ); @@ -107,47 +108,23 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: }); async function assertInfoValues(driver: Driver) { + const signTypedData = new SignTypedData(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const contractPetName = driver.findElement({ - css: '.name__value', - text: '0xCcCCc...ccccC', - }); - - const primaryType = driver.findElement({ text: 'Mail' }); - const fromName = driver.findElement({ text: 'Cow' }); - const fromAddress = driver.findElement({ - css: '.name__value', - text: '0xCD2a3...DD826', - }); - const toName = driver.findElement({ text: 'Bob' }); - const toAddress = driver.findElement({ - css: '.name__value', - text: '0xbBbBB...bBBbB', - }); - const contents = driver.findElement({ text: 'Hello, Bob!' }); - - assert.ok(await origin, 'origin'); - assert.ok(await contractPetName, 'contractPetName'); - assert.ok(await primaryType, 'primaryType'); - assert.ok(await fromName, 'fromName'); - assert.ok(await fromAddress, 'fromAddress'); - assert.ok(await toName, 'toName'); - assert.ok(await toAddress, 'toAddress'); - assert.ok(await contents, 'contents'); + await signTypedData.verifyOrigin(); + await signTypedData.verifyContractPetName(); + await signTypedData.verifyPrimaryType(); + await signTypedData.verifyFromName(); + await signTypedData.verifyFromAddress(); + await signTypedData.verifyToName(); + await signTypedData.verifyToAddress(); + await signTypedData.verifyContents(); } async function assertVerifiedResults(driver: Driver, publicAddress: string) { + const testDapp = new TestDapp(driver); await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle('E2E Test Dapp'); - await driver.clickElement('#signTypedDataV3Verify'); - await driver.waitForSelector({ - css: '#signTypedDataV3Result', - text: '0x0a22f7796a2a70c8dc918e7e6eb8452c8f2999d1a1eb5ad714473d36270a40d6724472e5609948c778a07216bd082b60b6f6853d6354c731fd8ccdd3a2f4af261b', - }); - - await driver.waitForSelector({ - css: '#signTypedDataV3VerifyResult', - text: publicAddress, - }); + await testDapp.check_successSignTypedDataV3(publicAddress); + await testDapp.verify_successSignTypedDataV3Result( + '0x0a22f7796a2a70c8dc918e7e6eb8452c8f2999d1a1eb5ad714473d36270a40d6724472e5609948c778a07216bd082b60b6f6853d6354c731fd8ccdd3a2f4af261b', + ); } diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts index 4dfe9f04972f..ccb30ef3ad3e 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts @@ -1,8 +1,7 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { DAPP_HOST_ADDRESS, WINDOW_TITLES } from '../../../helpers'; +import { WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { @@ -12,14 +11,18 @@ import { withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import SignTypedData from '../../../page-objects/pages/confirmations/redesign/sign-typed-data-confirmation'; +import TestDapp from '../../../page-objects/pages/test-dapp'; import { assertAccountDetailsMetrics, assertHeaderInfoBalance, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -36,6 +39,7 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: }: TestSuiteArguments) => { const addresses = await (ganacheServer as Ganache).getAccounts(); const publicAddress = addresses?.[0] as string; + await initializePages(driver); await openDappAndTriggerSignature( driver, @@ -43,10 +47,10 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: ); await clickHeaderInfoBtn(driver); - await assertHeaderInfoBalance(driver); + await assertHeaderInfoBalance(); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); await assertInfoValues(driver); await scrollAndConfirmAndAssertConfirm(driver); @@ -81,14 +85,15 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); + const confirmation = new SignTypedData(driver); + await openDappAndTriggerSignature( driver, SignatureType.SignTypedDataV4, ); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await assertSignatureRejectedMetrics({ driver, @@ -98,14 +103,9 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: location: 'confirmation', }); - await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const rejectionResult = await driver.waitForSelector({ - css: '#signTypedDataV4Result', - text: 'Error: User rejected the request.', - }); - assert.ok(rejectionResult); + await assertRejectedSignature(); }, async (mockServer) => { return await mockSignatureRejected(mockServer, true); @@ -115,50 +115,25 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: }); async function assertInfoValues(driver: Driver) { + const signTypedData = new SignTypedData(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const contractPetName = driver.findElement({ - css: '.name__value', - text: '0xCcCCc...ccccC', - }); - - const primaryType = driver.findElement({ text: 'Mail' }); - const contents = driver.findElement({ text: 'Hello, Bob!' }); - const fromName = driver.findElement({ text: 'Cow' }); - const fromAddressNum0 = driver.findElement({ - css: '.name__value', - text: '0xCD2a3...DD826', - }); - const toName = driver.findElement({ text: 'Bob' }); - const toAddressNum2 = driver.findElement({ - css: '.name__value', - text: '0xB0B0b...00000', - }); - const attachment = driver.findElement({ text: '0x' }); - - assert.ok(await origin, 'origin'); - assert.ok(await contractPetName, 'contractPetName'); - assert.ok(await primaryType, 'primaryType'); - assert.ok(await contents, 'contents'); - assert.ok(await fromName, 'fromName'); - assert.ok(await fromAddressNum0, 'fromAddressNum0'); - assert.ok(await toName, 'toName'); - assert.ok(await toAddressNum2, 'toAddressNum2'); - assert.ok(await attachment, 'attachment'); + await signTypedData.verifyOrigin(); + await signTypedData.verifyContractPetName(); + await signTypedData.verifyPrimaryType(); + await signTypedData.verifyFromName(); + await signTypedData.verifyFromAddress(); + await signTypedData.verifyToName(); + await signTypedData.verifyToAddress(); + await signTypedData.verifyContents(); + await signTypedData.verifyAttachment(); + await signTypedData.verifyToAddressNum2(); } async function assertVerifiedResults(driver: Driver, publicAddress: string) { + const testDapp = new TestDapp(driver); await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#signTypedDataV4Verify'); - - await driver.waitForSelector({ - css: '#signTypedDataV4Result', - text: '0xcd2f9c55840f5e1bcf61812e93c1932485b524ca673b36355482a4fbdf52f692684f92b4f4ab6f6c8572dacce46bd107da154be1c06939b855ecce57a1616ba71b', - }); - - await driver.waitForSelector({ - css: '#signTypedDataV4VerifyResult', - text: publicAddress, - }); + await testDapp.check_successSignTypedDataV4(publicAddress); + await testDapp.verify_successSignTypedDataV4Result( + '0xcd2f9c55840f5e1bcf61812e93c1932485b524ca673b36355482a4fbdf52f692684f92b4f4ab6f6c8572dacce46bd107da154be1c06939b855ecce57a1616ba71b', + ); } diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts index e7f8e1446f5c..3c1e9abc24bc 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts @@ -1,8 +1,7 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { DAPP_HOST_ADDRESS, WINDOW_TITLES } from '../../../helpers'; +import { WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { @@ -11,14 +10,18 @@ import { withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import SignTypedData from '../../../page-objects/pages/confirmations/redesign/sign-typed-data-confirmation'; +import TestDapp from '../../../page-objects/pages/test-dapp'; import { assertAccountDetailsMetrics, assertHeaderInfoBalance, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -35,14 +38,16 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui }: TestSuiteArguments) => { const addresses = await (ganacheServer as Ganache).getAccounts(); const publicAddress = addresses?.[0] as string; + await initializePages(driver); await openDappAndTriggerSignature(driver, SignatureType.SignTypedData); await clickHeaderInfoBtn(driver); - await assertHeaderInfoBalance(driver); + await assertHeaderInfoBalance(); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); + await assertInfoValues(driver); await driver.clickElement('[data-testid="confirm-footer-button"]'); @@ -53,6 +58,7 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui mockedEndpoints as MockedEndpoint[], 'eth_signTypedData', ); + await assertSignatureConfirmedMetrics({ driver, mockedEndpoints: mockedEndpoints as MockedEndpoint[], @@ -73,11 +79,12 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); + const confirmation = new SignTypedData(driver); + await openDappAndTriggerSignature(driver, SignatureType.SignTypedData); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await assertSignatureRejectedMetrics({ driver, @@ -89,11 +96,7 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const rejectionResult = await driver.waitForSelector({ - css: '#signTypedDataResult', - text: 'Error: User rejected the request.', - }); - assert.ok(rejectionResult); + await assertRejectedSignature(); }, mockSignatureRejected, ); @@ -101,26 +104,17 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui }); async function assertInfoValues(driver: Driver) { + const signTypedData = new SignTypedData(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const message = driver.findElement({ text: 'Hi, Alice!' }); - - assert.ok(await origin); - assert.ok(await message); + signTypedData.verifySignTypedDataMessage(); + signTypedData.verifyOrigin(); } async function assertVerifiedResults(driver: Driver, publicAddress: string) { await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#signTypedDataVerify'); - - await driver.waitForSelector({ - css: '#signTypedDataResult', - text: '0x32791e3c41d40dd5bbfb42e66cf80ca354b0869ae503ad61cd19ba68e11d4f0d2e42a5835b0bfd633596b6a7834ef7d36033633a2479dacfdb96bda360d51f451b', - }); - - await driver.waitForSelector({ - css: '#signTypedDataVerifyResult', - text: publicAddress, - }); + const testDapp = new TestDapp(driver); + testDapp.check_successSignTypedData(publicAddress); + testDapp.verify_successSignTypedDataResult( + '0x32791e3c41d40dd5bbfb42e66cf80ca354b0869ae503ad61cd19ba68e11d4f0d2e42a5835b0bfd633596b6a7834ef7d36033633a2479dacfdb96bda360d51f451b', + ); } diff --git a/test/e2e/tests/confirmations/signatures/signature-helpers.ts b/test/e2e/tests/confirmations/signatures/signature-helpers.ts index a4c5e9309249..a4c9dd31b2ca 100644 --- a/test/e2e/tests/confirmations/signatures/signature-helpers.ts +++ b/test/e2e/tests/confirmations/signatures/signature-helpers.ts @@ -1,13 +1,15 @@ import { strict as assert } from 'assert'; import { MockedEndpoint } from 'mockttp'; -import { Key } from 'selenium-webdriver/lib/input'; import { WINDOW_TITLES, getEventPayloads, - openDapp, unlockWallet, } from '../../../helpers'; import { Driver } from '../../../webdriver/driver'; +import TestDapp from '../../../page-objects/pages/test-dapp'; +import { DAPP_URL } from '../../../constants'; +import Confirmation from '../../../page-objects/pages/confirmations/redesign/confirmation'; +import AccountDetailsModal from '../../../page-objects/pages/confirmations/redesign/accountDetailsModal'; import { BlockaidReason, BlockaidResultType, @@ -37,6 +39,9 @@ type AssertSignatureMetricsOptions = { withAnonEvents?: boolean; securityAlertReason?: string; securityAlertResponse?: string; + decodingChangeTypes?: string[]; + decodingResponse?: string; + decodingDescription?: string | null; }; type SignatureEventProperty = { @@ -49,6 +54,9 @@ type SignatureEventProperty = { security_alert_response: string; signature_type: string; eip712_primary_type?: string; + decoding_change_types?: string[]; + decoding_response?: string; + decoding_description?: string | null; ui_customizations?: string[]; location?: string; }; @@ -59,6 +67,14 @@ const signatureAnonProperties = { eip712_domain_name: 'Ether Mail', }; +let testDapp: TestDapp; +let accountDetailsModal: AccountDetailsModal; + +export async function initializePages(driver: Driver) { + testDapp = new TestDapp(driver); + accountDetailsModal = new AccountDetailsModal(driver); +} + /** * Generates expected signature metric properties * @@ -67,6 +83,9 @@ const signatureAnonProperties = { * @param uiCustomizations * @param securityAlertReason * @param securityAlertResponse + * @param decodingChangeTypes + * @param decodingResponse + * @param decodingDescription */ function getSignatureEventProperty( signatureType: string, @@ -74,6 +93,9 @@ function getSignatureEventProperty( uiCustomizations: string[], securityAlertReason: string = BlockaidReason.checkingChain, securityAlertResponse: string = BlockaidResultType.Loading, + decodingChangeTypes?: string[], + decodingResponse?: string, + decodingDescription?: string | null, ): SignatureEventProperty { const signatureEventProperty: SignatureEventProperty = { account_type: 'MetaMask', @@ -91,6 +113,11 @@ function getSignatureEventProperty( signatureEventProperty.eip712_primary_type = primaryType; } + if (decodingResponse) { + signatureEventProperty.decoding_change_types = decodingChangeTypes; + signatureEventProperty.decoding_response = decodingResponse; + signatureEventProperty.decoding_description = decodingDescription; + } return signatureEventProperty; } @@ -123,6 +150,9 @@ export async function assertSignatureConfirmedMetrics({ withAnonEvents = false, securityAlertReason, securityAlertResponse, + decodingChangeTypes, + decodingResponse, + decodingDescription, }: AssertSignatureMetricsOptions) { const events = await getEventPayloads(driver, mockedEndpoints); const signatureEventProperty = getSignatureEventProperty( @@ -131,6 +161,9 @@ export async function assertSignatureConfirmedMetrics({ uiCustomizations, securityAlertReason, securityAlertResponse, + decodingChangeTypes, + decodingResponse, + decodingDescription, ); assertSignatureRequestedMetrics( @@ -164,6 +197,9 @@ export async function assertSignatureRejectedMetrics({ withAnonEvents = false, securityAlertReason, securityAlertResponse, + decodingChangeTypes, + decodingResponse, + decodingDescription, }: AssertSignatureMetricsOptions) { const events = await getEventPayloads(driver, mockedEndpoints); const signatureEventProperty = getSignatureEventProperty( @@ -172,6 +208,9 @@ export async function assertSignatureRejectedMetrics({ uiCustomizations, securityAlertReason, securityAlertResponse, + decodingChangeTypes, + decodingResponse, + decodingDescription, ); assertSignatureRequestedMetrics( @@ -223,6 +262,8 @@ function assertEventPropertiesMatch( const actualProperties = { ...event.properties }; const expectedProps = { ...expectedProperties }; + compareDecodingAPIResponse(actualProperties, expectedProps, eventName); + compareSecurityAlertResponse(actualProperties, expectedProps, eventName); assert(event, `${eventName} event not found`); @@ -257,41 +298,72 @@ function compareSecurityAlertResponse( } } +function compareDecodingAPIResponse( + actualProperties: Record, + expectedProperties: Record, + eventName: string, +) { + if ( + !expectedProperties.decoding_response && + !actualProperties.decoding_response + ) { + return; + } + if ( + eventName === 'Signature Rejected' || + eventName === 'Signature Approved' + ) { + assert.deepStrictEqual( + actualProperties.decoding_change_types, + expectedProperties.decoding_change_types, + `${eventName} event properties do not match: decoding_change_types is ${actualProperties.decoding_change_types}`, + ); + assert.equal( + actualProperties.decoding_response, + expectedProperties.decoding_response, + `${eventName} event properties do not match: decoding_response is ${actualProperties.decoding_response}`, + ); + assert.equal( + actualProperties.decoding_description, + expectedProperties.decoding_description, + `${eventName} event properties do not match: decoding_response is ${actualProperties.decoding_description}`, + ); + } + // Remove the property from both objects to avoid comparison + delete expectedProperties.decoding_change_types; + delete expectedProperties.decoding_response; + delete expectedProperties.decoding_description; + delete expectedProperties.decoding_latency; + delete actualProperties.decoding_change_types; + delete actualProperties.decoding_response; + delete actualProperties.decoding_description; + delete actualProperties.decoding_latency; +} + export async function clickHeaderInfoBtn(driver: Driver) { + const confirmation = new Confirmation(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - const accountDetailsButton = await driver.findElement( - '[data-testid="header-info__account-details-button"]', - ); - await accountDetailsButton.sendKeys(Key.RETURN); + confirmation.clickHeaderAccountDetailsButton(); } -export async function assertHeaderInfoBalance(driver: Driver) { - await driver.waitForSelector({ - css: '[data-testid="confirmation-account-details-modal__account-balance"]', - text: `${WALLET_ETH_BALANCE} ETH`, - }); +export async function assertHeaderInfoBalance() { + accountDetailsModal.assertHeaderInfoBalance(WALLET_ETH_BALANCE); } export async function copyAddressAndPasteWalletAddress(driver: Driver) { - await driver.clickElement('[data-testid="address-copy-button-text"]'); + await accountDetailsModal.clickAddressCopyButton(); await driver.delay(500); // Added delay to avoid error Element is not clickable at point (x,y) because another element obscures it, happens as soon as the mouse hovers over the close button - await driver.clickElement( - '[data-testid="confirmation-account-details-modal__close-button"]', - ); + await accountDetailsModal.clickAccountDetailsModalCloseButton(); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.findElement('#eip747ContractAddress'); - await driver.pasteFromClipboardIntoField('#eip747ContractAddress'); + await testDapp.pasteIntoEip747ContractAddressInput(); } -export async function assertPastedAddress(driver: Driver) { - const formFieldEl = await driver.findElement('#eip747ContractAddress'); - assert.equal(await formFieldEl.getAttribute('value'), WALLET_ADDRESS); +export async function assertPastedAddress() { + await testDapp.assertEip747ContractAddressInputValue(WALLET_ADDRESS); } -export async function triggerSignature(driver: Driver, type: string) { - await driver.clickElement(type); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); +export async function assertRejectedSignature() { + testDapp.assertUserRejectedRequest(); } export async function openDappAndTriggerSignature( @@ -299,13 +371,55 @@ export async function openDappAndTriggerSignature( type: string, ) { await unlockWallet(driver); - await openDapp(driver); - await triggerSignature(driver, type); + await testDapp.openTestDappPage({ url: DAPP_URL }); + await triggerSignature(type); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); } export async function openDappAndTriggerDeploy(driver: Driver) { await unlockWallet(driver); - await openDapp(driver); + await testDapp.openTestDappPage({ url: DAPP_URL }); await driver.clickElement('#deployNFTsButton'); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); } + +export async function triggerSignature(type: string) { + switch (type) { + case SignatureType.PersonalSign: + await testDapp.clickPersonalSign(); + break; + case SignatureType.Permit: + await testDapp.clickPermit(); + break; + case SignatureType.SignTypedData: + await testDapp.clickSignTypedData(); + break; + case SignatureType.SignTypedDataV3: + await testDapp.clickSignTypedDatav3(); + break; + case SignatureType.SignTypedDataV4: + await testDapp.clickSignTypedDatav4(); + break; + case SignatureType.SIWE: + await testDapp.clickSiwe(); + break; + case SignatureType.SIWE_BadDomain: + await testDapp.clickSwieBadDomain(); + break; + case SignatureType.NFTPermit: + await testDapp.clickERC721Permit(); + break; + default: + throw new Error('Invalid signature type'); + } +} + +export async function assertVerifiedSiweMessage( + driver: Driver, + message: string, +) { + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + await testDapp.check_successSiwe(message); +} diff --git a/test/e2e/tests/confirmations/signatures/siwe.spec.ts b/test/e2e/tests/confirmations/signatures/siwe.spec.ts index 4c7bec0ae121..4e90ac788a08 100644 --- a/test/e2e/tests/confirmations/signatures/siwe.spec.ts +++ b/test/e2e/tests/confirmations/signatures/siwe.spec.ts @@ -1,8 +1,7 @@ -import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; -import { DAPP_HOST_ADDRESS, WINDOW_TITLES } from '../../../helpers'; +import { WINDOW_TITLES } from '../../../helpers'; import { Driver } from '../../../webdriver/driver'; import { mockSignatureApproved, @@ -11,6 +10,7 @@ import { withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import PersonalSignConfirmation from '../../../page-objects/pages/confirmations/redesign/personal-sign-confirmation'; import { BlockaidReason, BlockaidResultType, @@ -19,10 +19,13 @@ import { assertAccountDetailsMetrics, assertHeaderInfoBalance, assertPastedAddress, + assertRejectedSignature, assertSignatureConfirmedMetrics, assertSignatureRejectedMetrics, + assertVerifiedSiweMessage, clickHeaderInfoBtn, copyAddressAndPasteWalletAddress, + initializePages, openDappAndTriggerSignature, SignatureType, } from './signature-helpers'; @@ -36,13 +39,15 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); await openDappAndTriggerSignature(driver, SignatureType.SIWE); await clickHeaderInfoBtn(driver); - await assertHeaderInfoBalance(driver); + await assertHeaderInfoBalance(); await copyAddressAndPasteWalletAddress(driver); - await assertPastedAddress(driver); + await assertPastedAddress(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await assertInfoValues(driver); await scrollAndConfirmAndAssertConfirm(driver); @@ -52,6 +57,12 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { '0xef8674a92d62a1876624547bdccaef6c67014ae821de18fa910fbff56577a65830f68848585b33d1f4b9ea1c3da1c1b11553b6aabe8446717daf7cd1e38a68271c', ); + await assertAccountDetailsMetrics( + driver, + mockedEndpoints as MockedEndpoint[], + 'personal_sign', + ); + await assertAccountDetailsMetrics( driver, mockedEndpoints as MockedEndpoint[], @@ -81,18 +92,15 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { driver, mockedEndpoint: mockedEndpoints, }: TestSuiteArguments) => { + await initializePages(driver); + const confirmation = new PersonalSignConfirmation(driver); await openDappAndTriggerSignature(driver, SignatureType.SIWE); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="confirm-footer-cancel-button"]', - ); + await confirmation.clickFooterCancelButtonAndAndWaitForWindowToClose(); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#siweResult', - text: 'Error: User rejected the request.', - }); + await assertRejectedSignature(); await assertSignatureRejectedMetrics({ driver, mockedEndpoints: mockedEndpoints as MockedEndpoint[], @@ -112,22 +120,8 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { }); async function assertInfoValues(driver: Driver) { - await driver.clickElement('[data-testid="sectionCollapseButton"]'); - const origin = driver.findElement({ text: DAPP_HOST_ADDRESS }); - const message = driver.findElement({ - text: 'I accept the MetaMask Terms of Service: https://community.metamask.io/tos', - }); - - assert.ok(await origin); - assert.ok(await message); -} - -async function assertVerifiedSiweMessage(driver: Driver, message: string) { - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await driver.waitForSelector({ - css: '#siweResult', - text: message, - }); + const confirmation = new PersonalSignConfirmation(driver); + await confirmation.clickCollapseSectionButton(); + await confirmation.verifyOrigin(); + await confirmation.verifySiweMessage(); } diff --git a/test/e2e/tests/confirmations/transactions/contract-deployment-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/contract-deployment-redesign.spec.ts index 7f937f4be6fc..eccb64af9c0c 100644 --- a/test/e2e/tests/confirmations/transactions/contract-deployment-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/contract-deployment-redesign.spec.ts @@ -25,7 +25,6 @@ describe('Confirmation Redesign Contract Deployment Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -58,7 +57,6 @@ describe('Confirmation Redesign Contract Deployment Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) diff --git a/test/e2e/tests/confirmations/transactions/contract-interaction-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/contract-interaction-redesign.spec.ts index d3a217f21c01..7794261331a2 100644 --- a/test/e2e/tests/confirmations/transactions/contract-interaction-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/contract-interaction-redesign.spec.ts @@ -42,7 +42,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -68,7 +67,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -97,7 +95,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { }) .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -141,7 +138,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, useTransactionSimulations: false, @@ -189,7 +185,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -216,7 +211,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, useNonceField: true, @@ -248,7 +242,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -279,7 +272,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, useNonceField: true, @@ -310,7 +302,6 @@ describe('Confirmation Redesign Contract Interaction Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) diff --git a/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts index cbb85482e6f1..e2a78bac8fd3 100644 --- a/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts @@ -86,6 +86,7 @@ async function createTransactionAssertDetailsAndConfirm( const testDapp = new TestDapp(driver); await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await testDapp.clickERC1155SetApprovalForAllButton(); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); diff --git a/test/e2e/tests/confirmations/transactions/erc20-approve-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc20-approve-redesign.spec.ts index 4e340f5ef3ac..18099cc56144 100644 --- a/test/e2e/tests/confirmations/transactions/erc20-approve-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc20-approve-redesign.spec.ts @@ -4,6 +4,7 @@ import { tinyDelayMs, veryLargeDelayMs, WINDOW_TITLES } from '../../../helpers'; import { Driver } from '../../../webdriver/driver'; import { scrollAndConfirmAndAssertConfirm } from '../helpers'; import { + mocked4BytesApprove, openDAppWithContract, TestSuiteArguments, toggleAdvancedDetails, @@ -29,7 +30,6 @@ describe('Confirmation Redesign ERC20 Approve Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -61,7 +61,6 @@ describe('Confirmation Redesign ERC20 Approve Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -87,30 +86,6 @@ describe('Confirmation Redesign ERC20 Approve Component', function () { }); }); -export async function mocked4BytesApprove(mockServer: MockttpServer) { - return await mockServer - .forGet('https://www.4byte.directory/api/v1/signatures/') - .always() - .withQuery({ hex_signature: '0x095ea7b3' }) - .thenCallback(() => ({ - statusCode: 200, - json: { - count: 1, - next: null, - previous: null, - results: [ - { - id: 149, - created_at: '2016-07-09T03:58:29.617584Z', - text_signature: 'approve(address,uint256)', - hex_signature: '0x095ea7b3', - bytes_signature: '\t^§³', - }, - ], - }, - })); -} - async function mocks(server: MockttpServer) { return [await mocked4BytesApprove(server)]; } diff --git a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts index 2fbe2c553a06..317d97abef6e 100644 --- a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts @@ -9,7 +9,7 @@ import { import { Mockttp } from '../../../mock-e2e'; import WatchAssetConfirmation from '../../../page-objects/pages/confirmations/legacy/watch-asset-confirmation'; import TokenTransferTransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/token-transfer-confirmation'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; @@ -127,6 +127,7 @@ async function createWalletInitiatedTransactionAndAssertDetails( await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + await driver.delay(1000); await testDapp.clickERC20WatchAssetButton(); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); @@ -172,6 +173,7 @@ async function createDAppInitiatedTransactionAndAssertDetails( await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + await driver.delay(1000); await testDapp.clickERC20WatchAssetButton(); await driver.delay(veryLargeDelayMs); diff --git a/test/e2e/tests/confirmations/transactions/erc721-approve-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc721-approve-redesign.spec.ts index c7ceb6c42c94..15570aff4ed5 100644 --- a/test/e2e/tests/confirmations/transactions/erc721-approve-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc721-approve-redesign.spec.ts @@ -29,7 +29,6 @@ describe('Confirmation Redesign ERC721 Approve Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -61,7 +60,6 @@ describe('Confirmation Redesign ERC721 Approve Component', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -145,7 +143,7 @@ async function assertApproveDetails(driver: Driver) { await driver.waitForSelector({ css: 'h2', - text: 'Allowance request', + text: 'Withdrawal request', }); await driver.waitForSelector({ diff --git a/test/e2e/tests/confirmations/transactions/increase-token-allowance-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/increase-token-allowance-redesign.spec.ts index 30c87080c3fc..d23122b25738 100644 --- a/test/e2e/tests/confirmations/transactions/increase-token-allowance-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/increase-token-allowance-redesign.spec.ts @@ -2,8 +2,6 @@ import FixtureBuilder from '../../../fixture-builder'; import { defaultGanacheOptions, defaultGanacheOptionsForType2Transactions, - largeDelayMs, - veryLargeDelayMs, WINDOW_TITLES, withFixtures, } from '../../../helpers'; @@ -12,7 +10,12 @@ import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { SMART_CONTRACTS } from '../../../seeder/smart-contracts'; import { Driver } from '../../../webdriver/driver'; import { scrollAndConfirmAndAssertConfirm } from '../helpers'; -import { openDAppWithContract, TestSuiteArguments } from './shared'; +import { + assertChangedSpendingCap, + editSpendingCap, + openDAppWithContract, + TestSuiteArguments, +} from './shared'; describe('Confirmation Redesign ERC20 Increase Allowance', function () { describe('Submit an increase allowance transaction @no-mmi', function () { @@ -77,7 +80,6 @@ function generateFixtureOptionsForLegacyTx(mochaContext: Mocha.Context) { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -96,7 +98,6 @@ function generateFixtureOptionsForEIP1559Tx(mochaContext: Mocha.Context) { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -158,42 +159,3 @@ async function createERC20IncreaseAllowanceTransaction(driver: Driver) { await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await driver.clickElement('#increaseTokenAllowance'); } - -export async function editSpendingCap(driver: Driver, newSpendingCap: string) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement('[data-testid="edit-spending-cap-icon"'); - - await driver.fill( - '[data-testid="custom-spending-cap-input"]', - newSpendingCap, - ); - - await driver.delay(largeDelayMs); - - await driver.clickElement({ text: 'Save', tag: 'button' }); - - // wait for the confirmation to be updated before submitting tx - await driver.delay(veryLargeDelayMs * 2); -} - -export async function assertChangedSpendingCap( - driver: Driver, - newSpendingCap: string, -) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); - - await driver.clickElement({ text: 'Activity', tag: 'button' }); - - await driver.delay(veryLargeDelayMs); - - await driver.clickElement( - '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', - ); - - await driver.waitForSelector({ - text: `${newSpendingCap} TST`, - tag: 'span', - }); - - await driver.waitForSelector({ text: 'Confirmed', tag: 'div' }); -} diff --git a/test/e2e/tests/confirmations/transactions/metrics.spec.ts b/test/e2e/tests/confirmations/transactions/metrics.spec.ts index aed8c0ba7f20..0812db880194 100644 --- a/test/e2e/tests/confirmations/transactions/metrics.spec.ts +++ b/test/e2e/tests/confirmations/transactions/metrics.spec.ts @@ -32,7 +32,6 @@ describe('Metrics @no-mmi', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) diff --git a/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts index 46318e113c35..bbf7481a9fc1 100644 --- a/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts @@ -7,7 +7,7 @@ import { WINDOW_TITLES, } from '../../../helpers'; import TokenTransferTransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/token-transfer-confirmation'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; import TestDapp from '../../../page-objects/pages/test-dapp'; import { Driver } from '../../../webdriver/driver'; diff --git a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts index 6fc048c7f11b..9b712ebb7a65 100644 --- a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts @@ -10,7 +10,8 @@ import { Mockttp } from '../../../mock-e2e'; import WatchAssetConfirmation from '../../../page-objects/pages/confirmations/legacy/watch-asset-confirmation'; import TokenTransferTransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/token-transfer-confirmation'; import TransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/transaction-confirmation'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; +import NFTListPage from '../../../page-objects/pages/home/nft-list'; import NFTDetailsPage from '../../../page-objects/pages/nft-details-page'; import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; import TestDapp from '../../../page-objects/pages/test-dapp'; @@ -195,6 +196,7 @@ async function createERC721WalletInitiatedTransactionAndAssertDetails( const testDapp = new TestDapp(driver); await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); await testDapp.clickERC721MintButton(); @@ -206,9 +208,8 @@ async function createERC721WalletInitiatedTransactionAndAssertDetails( await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); - const homePage = new HomePage(driver); - await homePage.goToNftTab(); - await homePage.clickNFTIconOnActivityList(); + await new HomePage(driver).goToNftTab(); + await new NFTListPage(driver).clickNFTIconOnActivityList(); const nftDetailsPage = new NFTDetailsPage(driver); await nftDetailsPage.clickNFTSendButton(); @@ -295,9 +296,8 @@ async function createERC1155WalletInitiatedTransactionAndAssertDetails( await watchAssetConfirmation.clickFooterConfirmButton(); await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); - const homePage = new HomePage(driver); - await homePage.goToNftTab(); - await homePage.clickNFTIconOnActivityList(); + await new HomePage(driver).goToNftTab(); + await new NFTListPage(driver).clickNFTIconOnActivityList(); const nftDetailsPage = new NFTDetailsPage(driver); await nftDetailsPage.clickNFTSendButton(); diff --git a/test/e2e/tests/confirmations/transactions/revoke-allowance-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/revoke-allowance-redesign.spec.ts index ba97d9cda4cd..94901dcea846 100644 --- a/test/e2e/tests/confirmations/transactions/revoke-allowance-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/revoke-allowance-redesign.spec.ts @@ -3,12 +3,13 @@ import { MockttpServer } from 'mockttp'; import { WINDOW_TITLES } from '../../../helpers'; import { Driver } from '../../../webdriver/driver'; import { scrollAndConfirmAndAssertConfirm } from '../helpers'; -import { mocked4BytesApprove } from './erc20-approve-redesign.spec'; import { assertChangedSpendingCap, editSpendingCap, -} from './increase-token-allowance-redesign.spec'; -import { openDAppWithContract, TestSuiteArguments } from './shared'; + mocked4BytesApprove, + openDAppWithContract, + TestSuiteArguments, +} from './shared'; const { defaultGanacheOptions, @@ -30,7 +31,6 @@ describe('Confirmation Redesign ERC20 Revoke Allowance', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) @@ -68,7 +68,6 @@ describe('Confirmation Redesign ERC20 Revoke Allowance', function () { .withPermissionControllerConnectedToTestDapp() .withPreferencesController({ preferences: { - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) diff --git a/test/e2e/tests/confirmations/transactions/shared.ts b/test/e2e/tests/confirmations/transactions/shared.ts index f984417fae94..d8f79a2053b2 100644 --- a/test/e2e/tests/confirmations/transactions/shared.ts +++ b/test/e2e/tests/confirmations/transactions/shared.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ -import { MockedEndpoint } from 'mockttp'; -import { veryLargeDelayMs } from '../../../helpers'; +import { MockedEndpoint, MockttpServer } from 'mockttp'; +import { largeDelayMs, veryLargeDelayMs } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; @@ -238,3 +238,66 @@ export async function assertAdvancedGasDetailsWithL2Breakdown(driver: Driver) { await driver.waitForSelector({ css: 'p', text: 'Speed' }); await driver.waitForSelector({ css: 'p', text: 'Max fee' }); } + +export async function editSpendingCap(driver: Driver, newSpendingCap: string) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElement('[data-testid="edit-spending-cap-icon"'); + + await driver.fill( + '[data-testid="custom-spending-cap-input"]', + newSpendingCap, + ); + + await driver.delay(largeDelayMs); + + await driver.clickElement({ text: 'Save', tag: 'button' }); + + // wait for the confirmation to be updated before submitting tx + await driver.delay(veryLargeDelayMs * 2); +} + +export async function assertChangedSpendingCap( + driver: Driver, + newSpendingCap: string, +) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + await driver.clickElement({ text: 'Activity', tag: 'button' }); + + await driver.delay(veryLargeDelayMs); + + await driver.clickElement( + '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', + ); + + await driver.waitForSelector({ + text: `${newSpendingCap} TST`, + tag: 'span', + }); + + await driver.waitForSelector({ text: 'Confirmed', tag: 'div' }); +} + +export async function mocked4BytesApprove(mockServer: MockttpServer) { + return await mockServer + .forGet('https://www.4byte.directory/api/v1/signatures/') + .always() + .withQuery({ hex_signature: '0x095ea7b3' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: 149, + created_at: '2016-07-09T03:58:29.617584Z', + text_signature: 'approve(address,uint256)', + hex_signature: '0x095ea7b3', + bytes_signature: '\t^§³', + }, + ], + }, + })); +} diff --git a/test/e2e/tests/confirmations/transactions/transaction-decoding-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/transaction-decoding-redesign.spec.ts new file mode 100644 index 000000000000..d303c30aef9b --- /dev/null +++ b/test/e2e/tests/confirmations/transactions/transaction-decoding-redesign.spec.ts @@ -0,0 +1,245 @@ +/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ +import { MockttpServer } from 'mockttp'; +import { + createDappTransaction, + DAPP_URL, + unlockWallet, + WINDOW_TITLES, +} from '../../../helpers'; +import TestDapp from '../../../page-objects/pages/test-dapp'; +import { TRANSACTION_DATA_UNISWAP } from '../../../../data/confirmations/transaction-decode'; +import { Ganache } from '../../../seeder/ganache'; +import TransactionConfirmation from '../../../page-objects/pages/confirmations/redesign/transaction-confirmation'; +import ContractAddressRegistry from '../../../seeder/contract-address-registry'; +import { TestSuiteArguments } from './shared'; + +const { defaultGanacheOptions, withFixtures } = require('../../../helpers'); +const FixtureBuilder = require('../../../fixture-builder'); +const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); + +describe('Confirmation Redesign Contract Interaction Transaction Decoding', function () { + const smartContract = SMART_CONTRACTS.NFTS; + + describe('Create a mint nft transaction @no-mmi', function () { + it(`decodes 4 bytes transaction data`, async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + preferences: { + isRedesignedConfirmationsDeveloperEnabled: true, + }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + testSpecificMock: mocked4BytesResponse, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await unlockWallet(driver); + const contractAddress = await ( + contractRegistry as ContractAddressRegistry + ).getContractAddress(smartContract); + + const testDapp = new TestDapp(driver); + const confirmation = new TransactionConfirmation(driver); + + await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + + await testDapp.clickERC721MintButton(); + + await driver.waitAndSwitchToWindowWithTitle(3, WINDOW_TITLES.Dialog); + + await confirmation.clickAdvancedDetailsButton(); + await confirmation.clickScrollToBottomButton(); + await confirmation.verifyAdvancedDetailsIsDisplayed('4Bytes'); + }, + ); + }); + }); + + it(`decodes Sourcify transaction data`, async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + preferences: { + isRedesignedConfirmationsDeveloperEnabled: true, + }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + testSpecificMock: mockedSourcifyResponse, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await unlockWallet(driver); + const contractAddress = await ( + contractRegistry as ContractAddressRegistry + ).getContractAddress(smartContract); + + const testDapp = new TestDapp(driver); + const confirmation = new TransactionConfirmation(driver); + + await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + + await testDapp.clickERC721MintButton(); + await driver.waitAndSwitchToWindowWithTitle(3, WINDOW_TITLES.Dialog); + + await confirmation.clickAdvancedDetailsButton(); + await confirmation.clickScrollToBottomButton(); + await confirmation.verifyAdvancedDetailsIsDisplayed('Sourcify'); + }, + ); + }); + + it(`falls back to raw hexadecimal when no data is retreived`, async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + preferences: { + isRedesignedConfirmationsDeveloperEnabled: true, + }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ driver, contractRegistry }: TestSuiteArguments) => { + await unlockWallet(driver); + const contractAddress = await ( + contractRegistry as ContractAddressRegistry + ).getContractAddress(smartContract); + + const testDapp = new TestDapp(driver); + const confirmation = new TransactionConfirmation(driver); + + await testDapp.openTestDappPage({ contractAddress, url: DAPP_URL }); + + await testDapp.clickERC721MintButton(); + await driver.waitAndSwitchToWindowWithTitle(3, WINDOW_TITLES.Dialog); + + await confirmation.clickAdvancedDetailsButton(); + await confirmation.clickScrollToBottomButton(); + await confirmation.verifyAdvancedDetailsHexDataIsDisplayed(); + }, + ); + }); + + it(`decodes uniswap transaction data`, async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + preferences: { + isRedesignedConfirmationsDeveloperEnabled: true, + }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test?.fullTitle(), + }, + async ({ driver, ganacheServer }: TestSuiteArguments) => { + const addresses = await (ganacheServer as Ganache).getAccounts(); + const publicAddress = addresses?.[0] as string; + + await unlockWallet(driver); + const contractAddress = '0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B'; + + const confirmation = new TransactionConfirmation(driver); + + await createDappTransaction(driver, { + data: TRANSACTION_DATA_UNISWAP, + to: contractAddress, + from: publicAddress, + }); + + await driver.waitAndSwitchToWindowWithTitle(3, WINDOW_TITLES.Dialog); + + await confirmation.clickAdvancedDetailsButton(); + await confirmation.clickScrollToBottomButton(); + await confirmation.verifyUniswapDecodedTransactionAdvancedDetails(); + }, + ); + }); +}); + +async function mocked4BytesResponse(mockServer: MockttpServer) { + return await mockServer + .forGet('https://www.4byte.directory/api/v1/signatures/') + .always() + .withQuery({ hex_signature: '0x3b4b1381' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: 1, + created_at: '2021-09-14T02:07:09.805000Z', + text_signature: 'mintNFTs(uint256)', + hex_signature: '0x3b4b1381', + bytes_signature: ';K\u0013', + }, + ], + }, + })); +} + +export const SOURCIFY_RESPONSE = { + files: [ + { + name: 'metadata.json', + path: 'contracts/partial_match/11155111/0x076146c765189d51bE3160A2140cF80BFC73ad68/metadata.json', + content: + '{"compiler":{"version":"0.8.18+commit.87f61d96"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"numberOfTokens","type":"uint256"}],"name":"mintNFTs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}],"devdoc":{"events":{"Approval(address,address,uint256)":{"details":"Emitted when `owner` enables `approved` to manage the `tokenId` token."},"ApprovalForAll(address,address,bool)":{"details":"Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets."},"Transfer(address,address,uint256)":{"details":"Emitted when `tokenId` token is transferred from `from` to `to`."}},"kind":"dev","methods":{"approve(address,uint256)":{"details":"See {IERC721-approve}."},"balanceOf(address)":{"details":"See {IERC721-balanceOf}."},"getApproved(uint256)":{"details":"See {IERC721-getApproved}."},"isApprovedForAll(address,address)":{"details":"See {IERC721-isApprovedForAll}."},"name()":{"details":"See {IERC721Metadata-name}."},"ownerOf(uint256)":{"details":"See {IERC721-ownerOf}."},"safeTransferFrom(address,address,uint256)":{"details":"See {IERC721-safeTransferFrom}."},"safeTransferFrom(address,address,uint256,bytes)":{"details":"See {IERC721-safeTransferFrom}."},"setApprovalForAll(address,bool)":{"details":"See {IERC721-setApprovalForAll}."},"supportsInterface(bytes4)":{"details":"See {IERC165-supportsInterface}."},"symbol()":{"details":"See {IERC721Metadata-symbol}."},"tokenURI(uint256)":{"details":"See {IERC721Metadata-tokenURI}."},"transferFrom(address,address,uint256)":{"details":"See {IERC721-transferFrom}."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"contracts/TestDappCollectibles.sol":"TestDappNFTs"},"evmVersion":"paris","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"@openzeppelin/contracts/token/ERC721/ERC721.sol":{"keccak256":"0x2c309e7df9e05e6ce15bedfe74f3c61b467fc37e0fae9eab496acf5ea0bbd7ff","license":"MIT","urls":["bzz-raw://7063b5c98711a98018ba4635ac74cee1c1cfa2ea01099498e062699ed9530005","dweb:/ipfs/QmeJ8rGXkcv7RrqLdAW8PCXPAykxVsddfYY6g5NaTwmRFE"]},"@openzeppelin/contracts/token/ERC721/IERC721.sol":{"keccak256":"0x5bce51e11f7d194b79ea59fe00c9e8de9fa2c5530124960f29a24d4c740a3266","license":"MIT","urls":["bzz-raw://7e66dfde185df46104c11bc89d08fa0760737aa59a2b8546a656473d810a8ea4","dweb:/ipfs/QmXvyqtXPaPss2PD7eqPoSao5Szm2n6UMoiG8TZZDjmChR"]},"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol":{"keccak256":"0xa82b58eca1ee256be466e536706850163d2ec7821945abd6b4778cfb3bee37da","license":"MIT","urls":["bzz-raw://6e75cf83beb757b8855791088546b8337e9d4684e169400c20d44a515353b708","dweb:/ipfs/QmYvPafLfoquiDMEj7CKHtvbgHu7TJNPSVPSCjrtjV8HjV"]},"@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol":{"keccak256":"0x75b829ff2f26c14355d1cba20e16fe7b29ca58eb5fef665ede48bc0f9c6c74b9","license":"MIT","urls":["bzz-raw://a0a107160525724f9e1bbbab031defc2f298296dd9e331f16a6f7130cec32146","dweb:/ipfs/QmemujxSd7gX8A9M8UwmNbz4Ms3U9FG9QfudUgxwvTmPWf"]},"@openzeppelin/contracts/utils/Address.sol":{"keccak256":"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa","license":"MIT","urls":["bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931","dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm"]},"@openzeppelin/contracts/utils/Context.sol":{"keccak256":"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7","license":"MIT","urls":["bzz-raw://6df0ddf21ce9f58271bdfaa85cde98b200ef242a05a3f85c2bc10a8294800a92","dweb:/ipfs/QmRK2Y5Yc6BK7tGKkgsgn3aJEQGi5aakeSPZvS65PV8Xp3"]},"@openzeppelin/contracts/utils/Counters.sol":{"keccak256":"0xf0018c2440fbe238dd3a8732fa8e17a0f9dce84d31451dc8a32f6d62b349c9f1","license":"MIT","urls":["bzz-raw://59e1c62884d55b70f3ae5432b44bb3166ad71ae3acd19c57ab6ddc3c87c325ee","dweb:/ipfs/QmezuXg5GK5oeA4F91EZhozBFekhq5TD966bHPH18cCqhu"]},"@openzeppelin/contracts/utils/Strings.sol":{"keccak256":"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0","license":"MIT","urls":["bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f","dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n"]},"@openzeppelin/contracts/utils/introspection/ERC165.sol":{"keccak256":"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b","license":"MIT","urls":["bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d","dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43"]},"@openzeppelin/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1","license":"MIT","urls":["bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f","dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy"]},"@openzeppelin/contracts/utils/math/Math.sol":{"keccak256":"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3","license":"MIT","urls":["bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c","dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS"]},"@openzeppelin/contracts/utils/math/SignedMath.sol":{"keccak256":"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc","license":"MIT","urls":["bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7","dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6"]},"base64-sol/base64.sol":{"keccak256":"0xa73959e6ef0b693e4423a562e612370160b934a75e618361ddd8c9c4b8ddbaaf","license":"MIT","urls":["bzz-raw://17c12e16d8d66f3af15d8787920bd41ca6c1e7517a212a6b9cebd4b6d38f36fe","dweb:/ipfs/QmcXMnZUXEz6LRKsm3CSvqdPboAzmezavi8bTg2dRxM2yE"]},"contracts/TestDappCollectibles.sol":{"keccak256":"0x3d2fa0d37970b903e628d8a7b101f8f73513d41d917fbdfac2749d9d1214176c","license":"MIT","urls":["bzz-raw://e01f3a25371accdfbbc2e3c9aeceabf92c5158f318a5e3f5b6c2b6ea3edb0c3b","dweb:/ipfs/QmT89aSnmzTdpRpoCoqCmnrR3Lp7CyXFtpnn2P52YEJULD"]}},"version":1}', + }, + ], +}; + +async function mockedSourcifyResponse(mockServer: MockttpServer) { + return await mockServer + .forGet('https://sourcify.dev/server/files/any/1337/0x') + .always() + .thenCallback(() => ({ + statusCode: 200, + json: SOURCIFY_RESPONSE, + })); +} + +async function mockInfura(mockServer: MockttpServer) { + return await mockServer + .forPost() + .always() + .withJsonBodyIncluding({ + method: 'eth_getCode', + params: ['0xef1c6e67703c7bd7107eed8303fbe6ec2554bf6b'], + }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1', + result: + '0x6080604052600436101561001b575b361561001957600080fd5b005b6000803560e01c90816301ffc9a7146100be57508063150b7a02146100b557806324856bc3146100ac5780633593564c146100a3578063709a1cc21461009a578063bc197c8114610091578063f23a6e61146100885763fa461e330361000e576100836109f2565b61000e565b50610083610960565b50610083610898565b5061008361061d565b50610083610473565b506100836102c5565b50610083610202565b346101ae5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101ae57600435907fffffffff0000000000000000000000000000000000000000000000000000000082168092036101ae57507f4e2312e0000000000000000000000000000000000000000000000000000000008114908115610184575b811561015a575b50151560805260206080f35b7f01ffc9a7000000000000000000000000000000000000000000000000000000009150148161014e565b7f150b7a020000000000000000000000000000000000000000000000000000000081149150610147565b80fd5b73ffffffffffffffffffffffffffffffffffffffff8116036101cf57565b600080fd5b9181601f840112156101cf5782359167ffffffffffffffff83116101cf57602083818601950101116101cf57565b50346101cf5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5761023d6004356101b1565b6102486024356101b1565b60643567ffffffffffffffff81116101cf576102689036906004016101d4565b505060206040517f150b7a02000000000000000000000000000000000000000000000000000000008152f35b9181601f840112156101cf5782359167ffffffffffffffff83116101cf576020808501948460051b0101116101cf57565b506040807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5767ffffffffffffffff600480358281116101cf5761031290369083016101d4565b90926024359081116101cf5761032b9036908401610294565b9490916001958680540361044b57600287558181036104235760005b8281106103575761001960018055565b61038b61036582858a610bde565b357fff000000000000000000000000000000000000000000000000000000000000001690565b6103a96103a361039c84868a610bf6565b3691610daf565b82611590565b91901590816103f8575b506103c057508701610347565b6103f4879186519384937f2c4029e90000000000000000000000000000000000000000000000000000000085528401610e4c565b0390fd5b7f800000000000000000000000000000000000000000000000000000000000000091501615386103b3565b8483517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b8483517f6f5ffb7e000000000000000000000000000000000000000000000000000000008152fd5b5060607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf57600467ffffffffffffffff81358181116101cf576104bf90369084016101d4565b9290916024359081116101cf576104d99036908301610294565b9360443542116105f457600194858054036105cb57600286558181036105a25760005b82811061050c5761001960018055565b61051a610365828589610bde565b61052b6103a361039c848689610bf6565b9190159081610577575b50610542575086016104fc565b6103f486916040519384937f2c4029e90000000000000000000000000000000000000000000000000000000085528401610e4c565b7f80000000000000000000000000000000000000000000000000000000000000009150161538610535565b836040517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b836040517f6f5ffb7e000000000000000000000000000000000000000000000000000000008152fd5b826040517f5bf6f916000000000000000000000000000000000000000000000000000000008152fd5b50346101cf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5760043567ffffffffffffffff81116101cf5761066e9036906004016101d4565b604092919251928380610686600096879586956124c1565b0390827f0000000000000000000000000554f068365ed43dcc98dcd7fd7a8208a5638c725af16106b4610f1d565b501561086e576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f1e8f03f716bc104bf7d728131967a0c771e85ab54d09c1e2d6ed9e0bc4e2a16c916107f1919073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000f4d2888d29d722226fafa5d9b24f9164c092421e168183602481845afa928315610861575b8693610832575b506040517fa9059cbb0000000000000000000000000000000000000000000000000000000081527f000000000000000000000000ea37093ce161f090e443f304e1bf3a8f14d7bb4073ffffffffffffffffffffffffffffffffffffffff16600482015260248101849052908290829060449082908a905af18015610825575b6107f7575b50506040519081529081906020820190565b0390a180f35b8161081692903d1061081e575b61080e8183610d25565b8101906124cf565b5038806107df565b503d610804565b61082d610f89565b6107da565b610853919350823d841161085a575b61084b8183610d25565b810190610f7a565b913861075b565b503d610841565b610869610f89565b610754565b60046040517f7d529919000000000000000000000000000000000000000000000000000000008152fd5b50346101cf5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf576108d36004356101b1565b6108de6024356101b1565b67ffffffffffffffff6044358181116101cf576108ff903690600401610294565b50506064358181116101cf57610919903690600401610294565b50506084359081116101cf576109339036906004016101d4565b50506040517fbc197c81000000000000000000000000000000000000000000000000000000008152602090f35b50346101cf5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5761099b6004356101b1565b6109a66024356101b1565b60843567ffffffffffffffff81116101cf576109c69036906004016101d4565b505060206040517ff23a6e61000000000000000000000000000000000000000000000000000000008152f35b50346101cf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5760243560043560443567ffffffffffffffff81116101cf57610a489036906004016101d4565b919060009384831393841580610ba4575b610b7a5782610a6d91610a9a940190613b49565b73ffffffffffffffffffffffffffffffffffffffff80911692610a8f83613bcb565b818398929a93614167565b8333911603610b505715610b425750808616908416105b15610ac65750610ac3935033916131e0565b80f35b915091604282511015600014610b0157610afb9350610ae482613c58565b610af6610af133926143d8565b613b90565b614014565b50505080f35b9192905083548211610b1857610ac39233916131e0565b60046040517f739dbe52000000000000000000000000000000000000000000000000000000008152fd5b945080841690861610610ab1565b60046040517f32b13d91000000000000000000000000000000000000000000000000000000008152fd5b60046040517f316cf0eb000000000000000000000000000000000000000000000000000000008152fd5b5085821315610a59565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90821015610bea570190565b610bf2610bae565b0190565b9190811015610c57575b60051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156101cf57019081359167ffffffffffffffff83116101cf5760200182360381136101cf579190565b610c5f610bae565b610c00565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6080810190811067ffffffffffffffff821117610cb057604052565b610cb8610c64565b604052565b6060810190811067ffffffffffffffff821117610cb057604052565b67ffffffffffffffff8111610cb057604052565b6020810190811067ffffffffffffffff821117610cb057604052565b6040810190811067ffffffffffffffff821117610cb057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610cb057604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610da2575b01160190565b610daa610c64565b610d9c565b929192610dbb82610d66565b91610dc96040519384610d25565b8294818452818301116101cf578281602093846000960137010152565b60005b838110610df95750506000910152565b8181015183820152602001610de9565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093610e4581518092818752878088019101610de6565b0116010190565b604090610e63939281528160208201520190610e09565b90565b91908260809103126101cf578151610e7d816101b1565b916020810151610e8c816101b1565b916060604083015192015190565b81601f820112156101cf578051610eb081610d66565b92610ebe6040519485610d25565b818452602082840101116101cf57610e639160208085019101610de6565b9190916040818403126101cf57805192602082015167ffffffffffffffff81116101cf57610e639201610e9a565b60405190610f1782610ced565b60008252565b3d15610f48573d90610f2e82610d66565b91610f3c6040519384610d25565b82523d6000602084013e565b606090565b908160609103126101cf578051610f63816101b1565b9160406020830151610f74816101b1565b92015190565b908160209103126101cf575190565b506040513d6000823e3d90fd5b5190610fa1826101b1565b565b908160209103126101cf5751610e63816101b1565b908160609103126101cf5780519160406020830151610f74816101b1565b60405190610fe382610d09565b601782527f43727970746f50756e6b205472616465204661696c65640000000000000000006020830152565b60009103126101cf57565b60209067ffffffffffffffff8111611034575b60051b0190565b61103c610c64565b61102d565b9060209182818303126101cf5780519067ffffffffffffffff82116101cf570181601f820112156101cf578051926110788461101a565b9360409361108885519687610d25565b818652828087019260071b850101938185116101cf578301915b8483106110b25750505050505090565b6080838303126101cf578360809187516110cb81610c94565b85516110d6816101b1565b8152828601516110e5816101b1565b83820152888601516110f6816101b1565b898201526060808701519061110a826101b1565b8201528152019201916110a2565b91908260409103126101cf5760208251610f74816101b1565b519065ffffffffffff821682036101cf57565b91908260809103126101cf5760405161115c81610c94565b606061119b818395805161116f816101b1565b8552602081015161117f816101b1565b602086015261119060408201611131565b604086015201611131565b910152565b91909180830360e081126101cf5760c0136101cf576040516111c181610cbd565b6111cb8483611144565b815260808201516111db816101b1565b602082015260a082015160408201529260c082015167ffffffffffffffff81116101cf57610e639201610e9a565b90610e63939260409173ffffffffffffffffffffffffffffffffffffffff809116845261127b60208501835160609073ffffffffffffffffffffffffffffffffffffffff80825116845260208201511660208401528165ffffffffffff91826040820151166040860152015116910152565b60208201511660a0840152015160c0820152610100908160e08201520190610e09565b519081151582036101cf57565b9160a0838303126101cf5782516112c1816101b1565b926020918282015193604083015193606084015167ffffffffffffffff81116101cf5784019180601f840112156101cf5782516112fd8161101a565b9361130b6040519586610d25565b818552838086019260051b8201019283116101cf578301905b82821061133c57505050506080610e6391930161129e565b838091835161134a816101b1565b815201910190611324565b9190916040818403126101cf5780519267ffffffffffffffff938481116101cf578201936060858303126101cf5760405161138f81610cbd565b85518281116101cf5786019583601f880112156101cf578651966113b28861101a565b906113c06040519283610d25565b888252602098898084019160071b830101918783116101cf578a809101915b83831061141b57505050509060409183526113fb888201610f96565b8884015201516040820152948301519081116101cf57610e639201610e9a565b906080916114298a85611144565b8152019101908a906113df565b939290919373ffffffffffffffffffffffffffffffffffffffff809316815260209460608683015260c082019381519460608085015285518091528760e0850196019060005b8181106114ac5750505090604091610e639697820151166080840152015160a08201526040818403910152610e09565b909196896080826115016001948c5160609073ffffffffffffffffffffffffffffffffffffffff80825116845260208201511660208401528165ffffffffffff91826040820151166040860152015116910152565b01980192910161147c565b908160609103126101cf578051611522816101b1565b9160406020830151611533816101b1565b920151610e63816101b1565b919060a0838203126101cf578251611556816101b1565b9260208101519260408201519260608301519067ffffffffffffffff82116101cf57611589608091610e63938601610e9a565b930161129e565b600192606092909160f81c601f166010811015611b085760088110156118aa578061161957506115cc81602080610fa19451830101910161153f565b909290156115fa576115f573ffffffffffffffffffffffffffffffffffffffff33955b166124e3565b613d69565b6115f573ffffffffffffffffffffffffffffffffffffffff30956115ef565b60018103611684575061163881602080610fa19451830101910161153f565b909290156116655761166073ffffffffffffffffffffffffffffffffffffffff3395166124e3565b613efb565b61166073ffffffffffffffffffffffffffffffffffffffff30956115ef565b600281036116c557506116a381602080610fa19451830101910161150c565b9173ffffffffffffffffffffffffffffffffffffffff80921691339116612ce5565b600381036117925750806020806116e193518301019101611355565b9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31691823b156101cf5761175f92600092836040518096819582947f2a2d80d10000000000000000000000000000000000000000000000000000000084523360048501611436565b03925af18015611785575b611772575b50565b8061177f610fa192610cd9565b8061100f565b61178d610f89565b61176a565b600481036117da57506117b181602080610fa194518301019101610f4d565b91906117d373ffffffffffffffffffffffffffffffffffffffff8092166124e3565b911661276b565b6005810361182257506117f981602080610fa194518301019101610f4d565b919061181b73ffffffffffffffffffffffffffffffffffffffff8092166124e3565b9116612514565b6006810361186a575061184181602080610fa194518301019101610f4d565b919061186373ffffffffffffffffffffffffffffffffffffffff8092166124e3565b9116612663565b9050600781146118775750565b6040517fd76a1e9e0000000000000000000000000000000000000000000000000000000081526004810191909152602490fd5b6008810361191557506118c981602080610fa1945183010191016112ab565b909290156118f6576118f173ffffffffffffffffffffffffffffffffffffffff3395166124e3565b612fbe565b6118f173ffffffffffffffffffffffffffffffffffffffff30956115ef565b60098103611980575061193481602080610fa1945183010191016112ab565b909290156119615761195c73ffffffffffffffffffffffffffffffffffffffff3395166124e3565b61387e565b61195c73ffffffffffffffffffffffffffffffffffffffff30956115ef565b600a8103611a1a57508060208061199c935183010191016111a0565b9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31691823b156101cf5761175f92600092836040518096819582947f2b67b5700000000000000000000000000000000000000000000000000000000084523360048501611209565b600b8103611a5d575073ffffffffffffffffffffffffffffffffffffffff611a58611a5183602080610fa196518301019101611118565b92166124e3565b612a14565b600c8103611a99575073ffffffffffffffffffffffffffffffffffffffff611a94611a5183602080610fa196518301019101611118565b612baf565b600d8103611abd5750611ab881602080610fa194518301019101611041565b612e78565b92919050600e8303611afb576040517fd76a1e9e00000000000000000000000000000000000000000000000000000000815260048101849052602490fd5b9091600f81146118775750565b919290916018811015611ffd5760108103611b6f5750506000919250611b38816020808594518301019101610edc565b90602082519201907f00000000000000000000000000000000006c3852cbef3e08e8df289169ede5815af1611b6b610f1d565b9091565b60118103611ba6575050611b6b9192507f00000000000000000000000059728544b08ab483533076417fbbb2fd0b17ce3a906121e6565b60128103611bfc5750506000919250611bc9816020808594518301019101610edc565b90602082519201907f0000000000000000000000000fc584529a2aefa997697fafacba5831fac0c22d5af1611b6b610f1d565b60138103611d61575050611c1b91925060208082518301019101610fb8565b9290927f000000000000000000000000b47e3cd837ddf8e4c57f05d70ab865de6e193bbb9260405160208101907f8264fe98000000000000000000000000000000000000000000000000000000008252611cad81611c8185602483019190602083019252565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610d25565b600093849283925191885af194611cc2610f1d565b948615611d5357611cec9073ffffffffffffffffffffffffffffffffffffffff80911692166124e3565b813b15611d4f576040517f8b72a2ec00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481019290925290919082908183816044810161175f565b8380fd5b505050509050610e63610fd6565b60148103611d98575050611b6b9192507f00000000000000000000000059728544b08ab483533076417fbbb2fd0b17ce3a90612386565b9092919060158103611eae5750611dc19350611dfc906020948186808094518301019101610f4d565b604093919351809581927f6352211e000000000000000000000000000000000000000000000000000000008352600483019190602083019252565b038173ffffffffffffffffffffffffffffffffffffffff8096165afa928315611ea1575b600093611e70575b508116911614928315611e385750565b9091507f7dbe7e89000000000000000000000000000000000000000000000000000000006040519182015260048152610e6381610d09565b82919350611e9390873d8911611e9a575b611e8b8183610d25565b810190610fa3565b9290611e28565b503d611e81565b611ea9610f89565b611e20565b60168103611fb45750611ed29350611f306020948286808095518301019101610e66565b6040517efdd58e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff94851660048201526024810192909252949093909284929183919082906044820190565b0392165afa908115611fa7575b600091611f8a575b501092831593611f525750565b9091507f483a6929000000000000000000000000000000000000000000000000000000006040519182015260048152610e6381610d09565b611fa19150853d871161085a5761084b8183610d25565b38611f45565b611faf610f89565b611f3d565b601714611fbe5750565b611fd481602080610fa194518301019101610f4d565b9190611ff673ffffffffffffffffffffffffffffffffffffffff8092166124e3565b9116612888565b60188103612034575050611b6b9192507f00000000000000000000000074312363e45dcaba76c59ec49a7aa8a65a67eed3906121e6565b6019810361208a5750506000919250612057816020808594518301019101610edc565b90602082519201907f0000000000000000000000002b2e8cda09bba9660dca5cb6233787738ad683295af1611b6b610f1d565b601a81036120e057505060009192506120ad816020808594518301019101610edc565b90602082519201907f000000000000000000000000a42f6cada809bcf417deefbdd69c5c5a909249c05af1611b6b610f1d565b601b8103612117575050611b6b9192507f00000000000000000000000074312363e45dcaba76c59ec49a7aa8a65a67eed390612386565b601c810361214e575050611b6b9192507f000000000000000000000000cda72070e455bb31c7690a170224ce43623d0b6f906121e6565b90929190601d810361219b575061217181602080610fa194518301019101610e66565b92909161219473ffffffffffffffffffffffffffffffffffffffff8092166124e3565b9116612928565b92919050601e83036121d9576040517fd76a1e9e00000000000000000000000000000000000000000000000000000000815260048101849052602490fd5b9091601f81146118775750565b9091815182019260a0838503126101cf57602083015193604084015167ffffffffffffffff81116101cf57602080612222930191860101610e9a565b90606084015194612232866101b1565b60a0608086015195612243876101b1565b01519173ffffffffffffffffffffffffffffffffffffffff8096169160009485928392602083519301915af195612278610f1d565b9587612286575b5050505050565b61229091166124e3565b813b15611d4f576040517f42842e0e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116602482015260448101929092529091908290606490829084905af1801561231f575b61230c575b8080808061227f565b8061177f61231992610cd9565b38612303565b612327610f89565b6122fe565b60405161233881610ced565b60008152906000368137565b9192610e6395949160a09473ffffffffffffffffffffffffffffffffffffffff8092168552166020840152604083015260608201528160808201520190610e09565b9091815182019160c0818403126101cf57602081015192604082015167ffffffffffffffff81116101cf576020806123c2930191840101610e9a565b6060820151946123d1866101b1565b6080830151946123e0866101b1565b60c060a08501519401519173ffffffffffffffffffffffffffffffffffffffff8097169160009485928392602083519301915af19661241d610f1d565b968861242c575b505050505050565b61243691166124e3565b9361243f61232c565b94823b156124bd578490612483604051978896879586947ff242432a0000000000000000000000000000000000000000000000000000000086523060048701612344565b03925af180156124b0575b61249d575b8080808080612424565b8061177f6124aa92610cd9565b38612493565b6124b8610f89565b61248e565b8480fd5b908092918237016000815290565b908160209103126101cf57610e639061129e565b73ffffffffffffffffffffffffffffffffffffffff8116600181036125085750503390565b600203610e6357503090565b73ffffffffffffffffffffffffffffffffffffffff1691908261253b57610fa192506142b9565b610fa1927f800000000000000000000000000000000000000000000000000000000000000083036143275791506040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481865afa9081156125d2575b6000916125b4575b5091614327565b6125cc915060203d811161085a5761084b8183610d25565b386125ad565b6125da610f89565b6125a5565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181029291811591840414171561262257565b610fa16125df565b8115612634570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b82158015612760575b6127365773ffffffffffffffffffffffffffffffffffffffff16806126aa57506126a461269c610fa1934761260f565b612710900490565b906142b9565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152610fa1939192916127059161269c9190602081602481895afa908115612729575b60009161270b575b5061260f565b91614327565b612723915060203d811161085a5761084b8183610d25565b386126ff565b612731610f89565b6126f7565b60046040517fdeaa01e6000000000000000000000000000000000000000000000000000000008152fd5b50612710831161266c565b90919073ffffffffffffffffffffffffffffffffffffffff16806127ce5750479081106127a4578061279b575050565b610fa1916142b9565b60046040517f6a12f104000000000000000000000000000000000000000000000000000000008152fd5b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290929091602083602481875afa92831561287b575b60009361285b575b508210612831578161282857505050565b610fa192614327565b60046040517f675cae38000000000000000000000000000000000000000000000000000000008152fd5b61287491935060203d811161085a5761084b8183610d25565b9138612817565b612883610f89565b61280f565b73ffffffffffffffffffffffffffffffffffffffff1691823b156101cf576040517f42842e0e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff9290921660248301526044820152906000908290606490829084905af1801561291b575b6129125750565b610fa190610cd9565b612923610f89565b61290b565b6040517efdd58e00000000000000000000000000000000000000000000000000000000815230600482015260248101849052929391929173ffffffffffffffffffffffffffffffffffffffff9190911690602083604481855afa928315612a07575b6000936129e7575b508210612831576129a1610f0a565b93813b156101cf576000809461175f604051978896879586947ff242432a0000000000000000000000000000000000000000000000000000000086523060048701612344565b612a0091935060203d811161085a5761084b8183610d25565b9138612992565b612a0f610f89565b61298a565b907f80000000000000000000000000000000000000000000000000000000000000008103612b7c575047905b81612a49575050565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21691823b156101cf57612b26926020926040517fd0e30db000000000000000000000000000000000000000000000000000000000815260008160048187875af18015612b6f575b612b5c575b5060006040518096819582947fa9059cbb000000000000000000000000000000000000000000000000000000008452600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af18015612b4f575b612b385750565b61176f9060203d811161081e5761080e8183610d25565b612b57610f89565b612b31565b8061177f612b6992610cd9565b38612acb565b612b77610f89565b612ac6565b9047821115612a405760046040517f6a12f104000000000000000000000000000000000000000000000000000000008152fd5b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21692909190602083602481875afa928315612cd8575b600093612cb8575b5082106127a45781612c4057505050565b823b156101cf576040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815260048101839052610fa1936000908290602490829084905af18015612cab575b612c98575b506142b9565b8061177f612ca592610cd9565b38612c92565b612cb3610f89565b612c8d565b612cd191935060203d811161085a5761084b8183610d25565b9138612c2f565b612ce0610f89565b612c27565b919273ffffffffffffffffffffffffffffffffffffffff91827f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31693843b156101cf5760009484869281608496816040519b8c9a8b997f36c78516000000000000000000000000000000000000000000000000000000008b521660048a01521660248801521660448601521660648401525af1801561291b576129125750565b6001907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114612db3570190565b610bf26125df565b602090805115610bea570190565b604090805160011015610bea570190565b6020918151811015612def575b60051b010190565b612df7610bae565b612de7565b60208082019080835283518092528060408094019401926000905b838210612e2657505050505090565b8451805173ffffffffffffffffffffffffffffffffffffffff90811688528185015181168886015281830151811688840152606091820151169087015260809095019493820193600190910190612e17565b805160005b818110612f0257505073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba316803b156101cf5761175f6000929183926040519485809481937f0d58b1db00000000000000000000000000000000000000000000000000000000835260048301612dfc565b33612f47612f2e612f138487612dda565b515173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b03612f5a57612f5590612d85565b612e7d565b60046040517fe7002877000000000000000000000000000000000000000000000000000000008152fd5b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161262257565b9190820391821161262257565b61312893919294613042612fee612fd485612dbb565b5173ffffffffffffffffffffffffffffffffffffffff1690565b612ffa612fd486612dc9565b907f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f61324a565b9384816131c3575b505050613069612f2e612f2e612fd46130638651612f84565b86612dda565b6040517f70a082310000000000000000000000000000000000000000000000000000000080825273ffffffffffffffffffffffffffffffffffffffff8416600483015290946020948587602481875afa9687156131b6575b600097613183575b50916130dc8694928661310d9795613452565b60405180958194829383526004830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03915afa918215613176575b600092613159575b5050612fb1565b1061312f57565b60046040517f849eaf98000000000000000000000000000000000000000000000000000000008152fd5b61316f9250803d1061085a5761084b8183610d25565b3880613121565b61317e610f89565b613119565b859391975086949261310d966131a86130dc93883d8a1161085a5761084b8183610d25565b9993955096509294506130c9565b6131be610f89565b6130c1565b6131d8926131d3612fd487612dbb565b6131e0565b38808461304a565b92919073ffffffffffffffffffffffffffffffffffffffff808216300361320c575050610fa192612514565b808495941161322057610fa1941692612ce5565b60046040517fc4bd89a9000000000000000000000000000000000000000000000000000000008152fd5b9091610e6393613259916133a4565b9290915b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa06133909161336373ffffffffffffffffffffffffffffffffffffffff96946040519260208401967fffffffffffffffffffffffffffffffffffffffff000000000000000000000000809260601b16885260601b16603484015260288352606083019583871067ffffffffffffffff881117613397575b8660405283519020608084019788917fffffffffffffffffffffffffffffffffffffffff000000000000000000000000605594927fff00000000000000000000000000000000000000000000000000000000000000855260601b166001840152601583015260358201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80810184520182610d25565b5190201690565b61339f610c64565b6132f5565b73ffffffffffffffffffffffffffffffffffffffff8281169082161015611b6b5791565b51906dffffffffffffffffffffffffffff821682036101cf57565b908160609103126101cf576133f7816133c8565b916040613406602084016133c8565b92015163ffffffff811681036101cf5790565b90610e63949360809373ffffffffffffffffffffffffffffffffffffffff92845260208401521660408201528160608201520190610e09565b90600292838351106137945761347f61346d612fd485612dbb565b613479612fd486612dc9565b906133a4565b508351937ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86019501906000935b8685106134df575050505050505050565b6134ec612fd48684612dda565b906134fd612fd46001880185612dda565b73ffffffffffffffffffffffffffffffffffffffff928383169660409081519485937f0902f1ac00000000000000000000000000000000000000000000000000000000855260609788868d60049889915afa978815613787575b6000998a99613748575b50508061360695969798996dffffffffffffffffffffffffffff8091169a16921693168314978860001461373e57918291935b87875180927f70a0823100000000000000000000000000000000000000000000000000000000825281806135ea6020978896830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03915afa918215613731575b600092613714575b5050036137d7565b931561370b578a600094935b878a10156137005761362c612fd4613674938c0189612dda565b907f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f6137be565b9390935b9761368161232c565b95813b156101cf57600086956136c7600199839751988997889687957f022c0d9f0000000000000000000000000000000000000000000000000000000087528601613419565b03925af180156136f3575b6136e0575b509401936134ce565b8061177f6136ed92610cd9565b386136d7565b6136fb610f89565b6136d2565b505088926000613678565b8a600093613612565b61372a9250803d1061085a5761084b8183610d25565b38806135fe565b613739610f89565b6135f6565b9091829193613594565b829a506136069697989950908161377392903d10613780575b61376b8183610d25565b8101906133e3565b5099909998979695613561565b503d613761565b61378f610f89565b613557565b60046040517fae52ad0c000000000000000000000000000000000000000000000000000000008152fd5b926137cc906137d4936133a4565b91819461325d565b91565b811590818015613876575b61384c57613808610e63946103e59283810293818504149015171561383f575b8261260f565b916103e8808502948504141715613832575b82018092111561262a575b61382d6125df565b61262a565b61383a6125df565b61381a565b6138476125df565b613802565b60046040517f7b9c8916000000000000000000000000000000000000000000000000000000008152fd5b5083156137e2565b91939290927f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f947f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f9560009560028551106139d257968451917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff928381019081116139c5575b929190835b61395b5750505050851161393157610fa1948461392c926131d3612fd486612dbb565b613452565b60046040517f8ab0bc16000000000000000000000000000000000000000000000000000000008152fd5b929897509091826139996139928b61398a612fd4613983818488018e8682116139b857612dda565b928c612dda565b9086866139fc565b919b613abc565b9980156139ab575b0192919083613909565b6139b36125df565b6139a1565b6139c06125df565b612dda565b6139cd6125df565b613904565b60046040517f20db8267000000000000000000000000000000000000000000000000000000008152fd5b919392906137cc613a0d92866133a4565b92604051907f0902f1ac00000000000000000000000000000000000000000000000000000000825273ffffffffffffffffffffffffffffffffffffffff606083600481848a165afa928315613aaf575b6000908194613a8d575b5081906dffffffffffffffffffffffffffff80911694169416911614600014611b6b5791565b829450613aa8915060603d81116137805761376b8183610d25565b5093613a67565b613ab7610f89565b613a5d565b909182158015613b41575b61384c57613ad882613b119461260f565b906103e891828102928184041490151715613b34575b82810392818411613b27575b6103e580850294850414911417156138255761262a565b60018101809111613b1f5790565b610e636125df565b613b2f6125df565b613afa565b613b3c6125df565b613aee565b508015613ac7565b91906040838203126101cf57823567ffffffffffffffff81116101cf57830181601f820112156101cf576020918183613b8493359101610daf565b920135610e63816101b1565b7f80000000000000000000000000000000000000000000000000000000000000008114613bbe575b60000390565b613bc66125df565b613bb8565b908151613bd88184613c49565b9260178210613c1f57602b60178201519210613bf557602b015191565b60046040517fa78aa27f000000000000000000000000000000000000000000000000000000008152fd5b60046040517fd9096a3e000000000000000000000000000000000000000000000000000000008152fd5b90601411613bf5576014015190565b8051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe99182810192818411613d5c575b836008830110613d325760178210613d325781835110613d085760178214613cde57601f8416801560051b0183019182010160178201915b818110613cce5750505052565b8251815260209283019201613cc1565b60046040517fcc94a63a000000000000000000000000000000000000000000000000000000008152fd5b60046040517f3b99b53d000000000000000000000000000000000000000000000000000000008152fd5b60046040517f47aaf07a000000000000000000000000000000000000000000000000000000008152fd5b613d646125df565b613c89565b93909192937f80000000000000000000000000000000000000000000000000000000000000008314613e34575b90613dc5613dd3915b613dae604288511015956143d8565b8515613e2e57305b613dbf89613ecd565b91614126565b90919015613e275750613b90565b9115613df357613dc5613dd3913090613deb87613c58565b929190613d9f565b50109050613dfd57565b60046040517f39d35496000000000000000000000000000000000000000000000000000000008152fd5b9050613b90565b84613db6565b9150613dc5613dd391613e4e612f2e612f2e885189613c49565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290602090829060249082905afa908115613ec0575b600091613ea2575b509391509150613d96565b613eba915060203d811161085a5761084b8183610d25565b38613e97565b613ec8610f89565b613e8f565b90602b825110613d0857602b60405192600b810151600b8501520151602b830152602b825260608201604052565b613f1193919492600055610af6610af1866143d8565b90919015613f785750613f2390613b90565b03613f4e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600055565b60046040517fd4e0248e000000000000000000000000000000000000000000000000000000008152fd5b613f829150613b90565b613f23565b9073ffffffffffffffffffffffffffffffffffffffff613fb4602092959495604085526040850190610e09565b9416910152565b91908260409103126101cf576020825192015190565b919360a093610e63969573ffffffffffffffffffffffffffffffffffffffff80941685521515602085015260408401521660608201528160808201520190610e09565b612f2e9293612f2e60006040946140b26140596140308a613bcb565b73ffffffffffffffffffffffffffffffffffffffff9b9297919b808916908d16109b8c98614167565b948484146141085761407d6401000276a49a5b611c818a5193849260208401613f87565b8751998a97889687957f128acb0800000000000000000000000000000000000000000000000000000000875260048701613fd1565b03925af180156140fb575b60009283916140cb57509192565b90506140ef91925060403d81116140f4575b6140e78183610d25565b810190613fbb565b919092565b503d6140dd565b614103610f89565b6140bd565b61407d73fffd8963efd1fc6a506488495d951d5263988d259a61406c565b612f2e9293612f2e60006040946140b26140596141428a613bcb565b73ffffffffffffffffffffffffffffffffffffffff9b9297919b808d16908916109b8c985b73ffffffffffffffffffffffffffffffffffffffff92838316848316116142b1575b62ffffff908460405194816020870195168552166040850152166060830152606082526133907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80608084019284841067ffffffffffffffff8511176142a4575b6040849052845190207fff0000000000000000000000000000000000000000000000000000000000000060a086019081527fffffffffffffffffffffffffffffffffffffffff0000000000000000000000007f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98460601b1660a187015260b58601919091527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460d5909501949094526055835260f50182610d25565b6142ac610c64565b6141e9565b909190614189565b600080809381935af1156142c957565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4554485f5452414e534645525f4641494c4544000000000000000000000000006044820152fd5b60009182604492602095604051937fa9059cbb000000000000000000000000000000000000000000000000000000008552600485015260248401525af13d15601f3d116001600051141617161561437a57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152fd5b7f80000000000000000000000000000000000000000000000000000000000000008110156101cf579056fea26469706673582212202f2e114dd73237126f72d60b80c2baf2d9fc70c6d00948608ca4fefdc9bf009064736f6c63430008110033', + }, + }; + }); +} diff --git a/test/e2e/tests/connections/connect-with-metamask.spec.js b/test/e2e/tests/connections/connect-with-metamask.spec.js deleted file mode 100644 index 5611b40346db..000000000000 --- a/test/e2e/tests/connections/connect-with-metamask.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - WINDOW_TITLES, - logInWithBalanceValidation, - defaultGanacheOptions, - openDapp, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Connections page', function () { - it('should render new connections flow', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await openDapp(driver); - // Connect to dapp - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // should render new connections page - const newConnectionPage = await driver.waitForSelector({ - tag: 'h2', - text: 'Connect with MetaMask', - }); - assert.ok(newConnectionPage, 'Connection Page is defined'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - // It should render connected status for button if dapp is connected - const getConnectedStatus = await driver.waitForSelector({ - css: '#connectButton', - text: 'Connected', - }); - assert.ok(getConnectedStatus, 'Account is connected to Dapp'); - - // Switch to extension Tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - await driver.clickElement({ - text: '127.0.0.1:8080', - tag: 'p', - }); - const connectionsPageAccountInfo = await driver.isElementPresent({ - text: 'See your accounts and suggest transactions', - tag: 'p', - }); - assert.ok(connectionsPageAccountInfo, 'Connections Page is defined'); - const connectionsPageNetworkInfo = await driver.isElementPresent({ - text: 'Use your enabled networks', - tag: 'p', - }); - assert.ok(connectionsPageNetworkInfo, 'Connections Page is defined'); - }, - ); - }); -}); diff --git a/test/e2e/tests/connections/edit-account-flow.spec.js b/test/e2e/tests/connections/edit-account-flow.spec.js deleted file mode 100644 index 7b05f439714c..000000000000 --- a/test/e2e/tests/connections/edit-account-flow.spec.js +++ /dev/null @@ -1,101 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - WINDOW_TITLES, - connectToDapp, - logInWithBalanceValidation, - locateAccountBalanceDOM, - defaultGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -const accountLabel2 = '2nd custom name'; -const accountLabel3 = '3rd custom name'; -describe('Edit Accounts Flow', function () { - it('should be able to edit accounts', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await connectToDapp(driver); - - // It should render connected status for button if dapp is connected - const getConnectedStatus = await driver.waitForSelector({ - css: '#connectButton', - text: 'Connected', - }); - assert.ok(getConnectedStatus, 'Account is connected to Dapp'); - - // Switch to extension Tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-add-account"]', - ); - await driver.fill('[placeholder="Account 2"]', accountLabel2); - await driver.clickElement({ text: 'Add account', tag: 'button' }); - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-add-account"]', - ); - await driver.fill('[placeholder="Account 3"]', accountLabel3); - await driver.clickElement({ text: 'Add account', tag: 'button' }); - await locateAccountBalanceDOM(driver); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - await driver.clickElement({ - text: '127.0.0.1:8080', - tag: 'p', - }); - const connectionsPageAccountInfo = await driver.isElementPresent({ - text: 'See your accounts and suggest transactions', - tag: 'p', - }); - assert.ok(connectionsPageAccountInfo, 'Connections Page is defined'); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Ensure there are edit buttons - assert.ok(editButtons.length > 0, 'Edit buttons are available'); - - // Click the first (0th) edit button - await editButtons[0].click(); - - await driver.clickElement({ - text: '2nd custom name', - tag: 'button', - }); - await driver.clickElement({ - text: '3rd custom name', - tag: 'button', - }); - await driver.clickElement( - '[data-testid="connect-more-accounts-button"]', - ); - const updatedAccountInfo = await driver.isElementPresent({ - text: '3 accounts connected', - tag: 'span', - }); - assert.ok(updatedAccountInfo, 'Accounts List Updated'); - }, - ); - }); -}); diff --git a/test/e2e/tests/connections/edit-account-permissions.spec.ts b/test/e2e/tests/connections/edit-account-permissions.spec.ts new file mode 100644 index 000000000000..bd1eebdf9ffd --- /dev/null +++ b/test/e2e/tests/connections/edit-account-permissions.spec.ts @@ -0,0 +1,74 @@ +import { withFixtures, WINDOW_TITLES } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { + ACCOUNT_TYPE, + DEFAULT_FIXTURE_ACCOUNT, + DAPP_HOST_ADDRESS, +} from '../../constants'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import Homepage from '../../page-objects/pages/home/homepage'; +import PermissionListPage from '../../page-objects/pages/permission/permission-list-page'; +import SitePermissionPage from '../../page-objects/pages/permission/site-permission-page'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +const accountLabel2 = '2nd custom name'; +const accountLabel3 = '3rd custom name'; +describe('Edit Accounts Permissions', function () { + it('should be able to edit accounts', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + await testDapp.connectAccount({ + publicAddress: DEFAULT_FIXTURE_ACCOUNT, + }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await new Homepage(driver).check_pageIsLoaded(); + new HeaderNavbar(driver).openAccountMenu(); + + // create second account with custom label + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: accountLabel2, + }); + const homepage = new Homepage(driver); + await homepage.check_expectedBalanceIsDisplayed(); + + // create third account with custom label + await homepage.headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: accountLabel3, + }); + await homepage.check_expectedBalanceIsDisplayed(); + + // go to connections permissions page + await homepage.headerNavbar.openPermissionsPage(); + const permissionListPage = new PermissionListPage(driver); + await permissionListPage.check_pageIsLoaded(); + await permissionListPage.openPermissionPageForSite(DAPP_HOST_ADDRESS); + const sitePermissionPage = new SitePermissionPage(driver); + await sitePermissionPage.check_pageIsLoaded(DAPP_HOST_ADDRESS); + await sitePermissionPage.editPermissionsForAccount([ + accountLabel2, + accountLabel3, + ]); + await sitePermissionPage.check_connectedAccountsNumber(3); + }, + ); + }); +}); diff --git a/test/e2e/tests/connections/edit-networks-flow.spec.js b/test/e2e/tests/connections/edit-networks-flow.spec.js deleted file mode 100644 index 1db224f0ac0a..000000000000 --- a/test/e2e/tests/connections/edit-networks-flow.spec.js +++ /dev/null @@ -1,77 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - WINDOW_TITLES, - connectToDapp, - logInWithBalanceValidation, - locateAccountBalanceDOM, - defaultGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Edit Networks Flow', function () { - it('should be able to edit networks', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await connectToDapp(driver); - - // It should render connected status for button if dapp is connected - const getConnectedStatus = await driver.waitForSelector({ - css: '#connectButton', - text: 'Connected', - }); - assert.ok(getConnectedStatus, 'Account is connected to Dapp'); - - // Switch to extension Tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement('[data-testid="network-display"]'); - await driver.clickElement('.mm-modal-content__dialog .toggle-button'); - await driver.clickElement( - '.mm-modal-content__dialog button[aria-label="Close"]', - ); - await locateAccountBalanceDOM(driver); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - await driver.clickElement({ - text: '127.0.0.1:8080', - tag: 'p', - }); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Ensure there are edit buttons - assert.ok(editButtons.length > 0, 'Edit buttons are available'); - - // Click the first (0th) edit button - await editButtons[1].click(); - - // Disconnect Mainnet - await driver.clickElement({ - text: 'Ethereum Mainnet', - tag: 'p', - }); - - await driver.clickElement('[data-testid="connect-more-chains-button"]'); - const updatedNetworkInfo = await driver.isElementPresent({ - text: '2 networks connected', - tag: 'span', - }); - assert.ok(updatedNetworkInfo, 'Networks List Updated'); - }, - ); - }); -}); diff --git a/test/e2e/tests/connections/edit-networks-permissions.spec.ts b/test/e2e/tests/connections/edit-networks-permissions.spec.ts new file mode 100644 index 000000000000..55b7cdd2caeb --- /dev/null +++ b/test/e2e/tests/connections/edit-networks-permissions.spec.ts @@ -0,0 +1,49 @@ +import { withFixtures, WINDOW_TITLES } from '../../helpers'; +import { DEFAULT_FIXTURE_ACCOUNT, DAPP_HOST_ADDRESS } from '../../constants'; +import FixtureBuilder from '../../fixture-builder'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import Homepage from '../../page-objects/pages/home/homepage'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import PermissionListPage from '../../page-objects/pages/permission/permission-list-page'; +import SitePermissionPage from '../../page-objects/pages/permission/site-permission-page'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Edit Networks Permissions', function () { + it('should be able to edit networks', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + + await testDapp.connectAccount({ + publicAddress: DEFAULT_FIXTURE_ACCOUNT, + }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await new Homepage(driver).check_pageIsLoaded(); + + // Open permission page for dapp + new HeaderNavbar(driver).openPermissionsPage(); + const permissionListPage = new PermissionListPage(driver); + await permissionListPage.check_pageIsLoaded(); + await permissionListPage.openPermissionPageForSite(DAPP_HOST_ADDRESS); + const sitePermissionPage = new SitePermissionPage(driver); + await sitePermissionPage.check_pageIsLoaded(DAPP_HOST_ADDRESS); + + // Disconnect Mainnet + await sitePermissionPage.editPermissionsForNetwork([ + 'Ethereum Mainnet', + ]); + await sitePermissionPage.check_connectedNetworksNumber(2); + }, + ); + }); +}); diff --git a/test/e2e/tests/connections/review-permissions-page.spec.js b/test/e2e/tests/connections/review-permissions-page.spec.js index d411a343b2c9..60b7df8de4e0 100644 --- a/test/e2e/tests/connections/review-permissions-page.spec.js +++ b/test/e2e/tests/connections/review-permissions-page.spec.js @@ -36,10 +36,6 @@ describe('Review Permissions page', function () { '[data-testid ="account-options-menu-button"]', ); await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); await driver.clickElement({ text: '127.0.0.1:8080', tag: 'p', @@ -90,10 +86,6 @@ describe('Review Permissions page', function () { '[data-testid ="account-options-menu-button"]', ); await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); await driver.clickElement({ text: '127.0.0.1:8080', tag: 'p', diff --git a/test/e2e/tests/connections/review-switch-permission-page.spec.js b/test/e2e/tests/connections/review-switch-permission-page.spec.js index 5fe3d6d19526..121c3b413833 100644 --- a/test/e2e/tests/connections/review-switch-permission-page.spec.js +++ b/test/e2e/tests/connections/review-switch-permission-page.spec.js @@ -21,7 +21,7 @@ describe('Permissions Page when Dapp Switch to an enabled and non permissioned n dapp: true, fixtures: new FixtureBuilder() .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() .build(), ganacheOptions: { diff --git a/test/e2e/tests/dapp-interactions/block-explorer.spec.js b/test/e2e/tests/dapp-interactions/block-explorer.spec.js index 0b01aa65aab3..d6d64dcf4322 100644 --- a/test/e2e/tests/dapp-interactions/block-explorer.spec.js +++ b/test/e2e/tests/dapp-interactions/block-explorer.spec.js @@ -82,7 +82,7 @@ describe('Block Explorer', function () { await driver.clickElement({ text: 'TST', - tag: 'span', + tag: 'p', }); await driver.clickElement('[data-testid="asset-options__button"]'); diff --git a/test/e2e/tests/dapp-interactions/contract-interactions.spec.js b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js deleted file mode 100644 index a685954b5857..000000000000 --- a/test/e2e/tests/dapp-interactions/contract-interactions.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -const { - defaultGanacheOptions, - withFixtures, - openDapp, - unlockWallet, - largeDelayMs, - WINDOW_TITLES, - locateAccountBalanceDOM, - clickNestedButton, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Deploy contract and call contract methods', function () { - const smartContract = SMART_CONTRACTS.PIGGYBANK; - - it('should display the correct account balance after contract interactions', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // deploy contract - await openDapp(driver, contractAddress); - - // wait for deployed contract, calls and confirms a contract method where ETH is sent - await driver.delay(largeDelayMs); - await driver.clickElement('#depositButton'); - - await driver.waitForSelector({ - css: 'span', - text: 'Deposit initiated', - }); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'Activity'); - await driver.waitForSelector( - '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', - ); - await driver.waitForSelector({ - css: '[data-testid="transaction-list-item-primary-currency"]', - text: '-4 ETH', - }); - - // calls and confirms a contract method where ETH is received - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#withdrawButton'); - await driver.waitUntilXWindowHandles(3); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.waitForSelector( - '.transaction-list__completed-transactions .activity-list-item:nth-of-type(2)', - ); - await driver.waitForSelector({ - css: '[data-testid="transaction-list-item-primary-currency"]', - text: '-0 ETH', - }); - - // renders the correct ETH balance - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await locateAccountBalanceDOM(driver, ganacheServer); - }, - ); - }); -}); diff --git a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js index 584408134f1a..05d9528ce6b4 100644 --- a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js +++ b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js @@ -83,11 +83,6 @@ describe('Dapp interactions', function () { ); await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - const connectedDapp1 = await driver.isElementPresent({ text: '127.0.0.1:8080', tag: 'p', diff --git a/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js b/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js deleted file mode 100644 index ad168a2b9332..000000000000 --- a/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js +++ /dev/null @@ -1,81 +0,0 @@ -const { - defaultGanacheOptions, - logInWithBalanceValidation, - openDapp, - WINDOW_TITLES, - withFixtures, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Editing confirmations of dapp initiated contract interactions', function () { - const smartContract = SMART_CONTRACTS.PIGGYBANK; - it('should NOT show an edit button on a contract interaction confirmation initiated by a dapp', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // deploy contract - await openDapp(driver, contractAddress); - // wait for deployed contract, calls and confirms a contract method where ETH is sent - await driver.findClickableElement('#deployButton'); - await driver.clickElement('#depositButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - await driver.assertElementNotPresent( - '[data-testid="confirm-page-back-edit-button"]', - ); - }, - ); - }); - - it('should NOT show an edit button on a simple ETH send initiated by a dapp', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await logInWithBalanceValidation(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await openDapp(driver); - await driver.clickElement('#sendButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Sending ETH', - }); - await driver.assertElementNotPresent( - '[data-testid="confirm-page-back-edit-button"]', - ); - }, - ); - }); -}); diff --git a/test/e2e/tests/dapp-interactions/failing-contract.spec.js b/test/e2e/tests/dapp-interactions/failing-contract.spec.js deleted file mode 100644 index c05938d668e0..000000000000 --- a/test/e2e/tests/dapp-interactions/failing-contract.spec.js +++ /dev/null @@ -1,152 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - openDapp, - unlockWallet, - WINDOW_TITLES, - generateGanacheOptions, - clickNestedButton, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Failing contract interaction ', function () { - const smartContract = SMART_CONTRACTS.FAILING; - it('should display a warning when the contract interaction is expected to fail', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await openDapp(driver, contractAddress); - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - // waits for deployed contract and calls failing contract method - await driver.findClickableElement('#deployButton'); - await driver.clickElement('#sendFailingButton'); - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - // display warning when transaction is expected to fail - const warningText = - 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.'; - await driver.waitForSelector({ - css: '.mm-banner-alert .mm-text', - text: warningText, - }); - const confirmButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); - assert.equal(await confirmButton.isEnabled(), false); - - // dismiss warning and confirm the transaction - await driver.clickElement({ - text: 'I want to proceed anyway', - tag: 'button', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); - await clickNestedButton(driver, 'Activity'); - - await driver.findElement({ - css: '.activity-list-item .transaction-status-label', - text: 'Failed', - }); - }, - ); - }); -}); - -describe('Failing contract interaction on non-EIP1559 network', function () { - const smartContract = SMART_CONTRACTS.FAILING; - it('should display a warning when the contract interaction is expected to fail', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ hardfork: 'berlin' }), - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await openDapp(driver, contractAddress); - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - // waits for deployed contract and calls failing contract method - await driver.findClickableElement('#deployButton'); - - await driver.fill('#toInput', contractAddress); - await driver.fill('#amountInput', '0'); - await driver.fill('#gasInput', '100'); - - await driver.clickElement('#submitForm'); - - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - // display warning when transaction is expected to fail - const warningText = - 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.'; - await driver.waitForSelector({ - css: '.mm-banner-alert .mm-text', - text: warningText, - }); - const confirmButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); - assert.equal(await confirmButton.isEnabled(), false); - - // dismiss warning and confirm the transaction - await driver.clickElement({ - text: 'I want to proceed anyway', - tag: 'button', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindow(extension); - await clickNestedButton(driver, 'Activity'); - await driver.waitForSelector( - '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', - ); - - await driver.findElement({ - css: '.activity-list-item .transaction-status-label', - text: 'Failed', - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/dapp-interactions/permissions.spec.js b/test/e2e/tests/dapp-interactions/permissions.spec.js index 4b6c210f0a98..b8da733d3160 100644 --- a/test/e2e/tests/dapp-interactions/permissions.spec.js +++ b/test/e2e/tests/dapp-interactions/permissions.spec.js @@ -46,10 +46,6 @@ describe('Permissions', function () { text: 'All Permissions', tag: 'div', }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); await driver.waitForSelector({ text: '127.0.0.1:8080', tag: 'p', diff --git a/test/e2e/tests/dapp-interactions/revoke-permissions.spec.js b/test/e2e/tests/dapp-interactions/revoke-permissions.spec.js index 696095e3fd79..ddf4d9c51f43 100644 --- a/test/e2e/tests/dapp-interactions/revoke-permissions.spec.js +++ b/test/e2e/tests/dapp-interactions/revoke-permissions.spec.js @@ -7,7 +7,7 @@ const { const FixtureBuilder = require('../../fixture-builder'); describe('Wallet Revoke Permissions', function () { - it('should revoke eth_accounts permissions via test dapp', async function () { + it('should revoke "eth_accounts" permissions via test dapp', async function () { await withFixtures( { dapp: true, @@ -43,4 +43,47 @@ describe('Wallet Revoke Permissions', function () { }, ); }); + + it('should revoke "endowment:permitted-chains" permissions', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + await openDapp(driver); + + // Get initial accounts permissions + await driver.clickElement('#getPermissions'); + + const revokeChainsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_revokePermissions', + params: [ + { + 'endowment:permitted-chains': {}, + }, + ], + }); + + await driver.executeScript( + `return window.ethereum.request(${revokeChainsRequest})`, + ); + + // Get new allowed permissions + await driver.clickElement('#getPermissions'); + + // Eth_accounts permissions removed + await driver.waitForSelector({ + css: '#permissionsResult', + text: 'No permissions found.', + }); + }, + ); + }); }); diff --git a/test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js b/test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js index b2fe384f9523..05aee96938f8 100644 --- a/test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js +++ b/test/e2e/tests/dapp-interactions/signin-with-ethereum.spec.js @@ -3,8 +3,6 @@ const { defaultGanacheOptions, withFixtures, openDapp, - DAPP_URL, - tempToggleSettingRedesignedConfirmations, unlockWallet, WINDOW_TITLES, } = require('../../helpers'); @@ -13,7 +11,7 @@ const FixtureBuilder = require('../../fixture-builder'); describe('Sign in with ethereum', function () { it('user should be able to confirm sign in with ethereum', async function () { const expectedSigninMessageTitle = - 'This site is requesting to sign in with Account 1'; + 'A site wants you to sign in to prove you own this account.'; const expectedSigninMessage = 'I accept the MetaMask Terms of Service: https://community.metamask.io/tos'; const expectedSignInResult = @@ -29,7 +27,6 @@ describe('Sign in with ethereum', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); // Create a signin with ethereum request in test dapp await openDapp(driver); @@ -42,36 +39,43 @@ describe('Sign in with ethereum', function () { WINDOW_TITLES.Dialog, windowHandles, ); - const title = await driver.findElement( - '.permissions-connect-header__title', - ); - const origin = await driver.findElement('.site-origin'); - assert.equal(await title.getText(), 'Sign-in request'); - assert.equal(await origin.getText(), DAPP_URL); + await driver.findElement({ + css: 'h2', + text: 'Sign-in request', + }); + await driver.findElement({ + css: 'p', + text: expectedSigninMessageTitle, + }); + await driver.findElement({ + css: 'p', + text: '127.0.0.1:8080', + }); - const displayedMessageTitle = await driver.findElement( - '.permissions-connect-header__subtitle', - ); - const account = await driver.findElement( - '.account-list-item__account-name', - ); - assert.equal( - `${await displayedMessageTitle.getText()} ${await account.getText()}`, - expectedSigninMessageTitle, - ); + await driver.clickElement('[data-testid="sectionCollapseButton"]'); // Check the displayed information in popup content - const [message, url, version, chainId] = await driver.findElements( - '.signature-request-siwe-message__sub-text', - ); - assert.equal(await message.getText(), expectedSigninMessage); - assert.equal(await url.getText(), 'https://127.0.0.1:8080'); - assert.equal(await version.getText(), '1'); - assert.equal(await chainId.getText(), '1'); + await driver.findElement({ + css: 'p', + text: expectedSigninMessage, + }); + await driver.findElement({ + css: 'p', + text: 'https://127.0.0.1:8080', + }); + await driver.findElement({ + css: 'p', + text: '1', + }); + await driver.findElement({ + css: 'p', + text: '1', + }); - // Click on extension popup to approve signin with ethereum - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.waitUntilXWindowHandles(2); + await driver.clickElement({ + css: 'button', + text: 'Confirm', + }); // Switch back to the dapp and verify the signed result windowHandles = await driver.getAllWindowHandles(); diff --git a/test/e2e/tests/hardware-wallets/lattice-connect.spec.ts b/test/e2e/tests/hardware-wallets/lattice-connect.spec.ts index b8497a9df692..e20e0bd6fb45 100644 --- a/test/e2e/tests/hardware-wallets/lattice-connect.spec.ts +++ b/test/e2e/tests/hardware-wallets/lattice-connect.spec.ts @@ -2,39 +2,33 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import FixtureBuilder from '../../fixture-builder'; -import { withFixtures, unlockWallet } from '../../helpers'; +import { withFixtures } from '../../helpers'; import { isManifestV3 } from '../../../../shared/modules/mv3.utils'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import ConnectHardwareWalletPage from '../../page-objects/pages/hardware-wallet/connect-hardware-wallet-page'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; describe('Lattice hardware wallet @no-mmi', function (this: Suite) { - it('connects to lattice hardware wallet', async function () { + it('lattice page rendering validation', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); + await loginWithBalanceValidation(driver); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openAccountMenu(); - // choose Connect hardware wallet from the account menu - await driver.clickElement('[data-testid="account-menu-icon"]'); + // Choose connect hardware wallet from the account menu + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.openConnectHardwareWalletModal(); - // Wait until account list is loaded to mitigate race condition - await driver.waitForSelector({ - text: 'Account 1', - tag: 'span', - }); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement({ - text: 'Add hardware wallet', - tag: 'button', - }); - await driver.findClickableElement( - '[data-testid="hardware-connect-close-btn"]', - ); - await driver.clickElement('[data-testid="connect-lattice-btn"]'); - await driver.clickElement({ text: 'Continue', tag: 'button' }); + const connectHardwareWalletPage = new ConnectHardwareWalletPage(driver); + await connectHardwareWalletPage.check_pageIsLoaded(); + await connectHardwareWalletPage.openConnectLatticePage(); const allWindows = await driver.waitUntilXWindowHandles(2); assert.equal(allWindows.length, isManifestV3 ? 3 : 2); diff --git a/test/e2e/tests/hardware-wallets/trezor-account.spec.js b/test/e2e/tests/hardware-wallets/trezor-account.spec.js deleted file mode 100644 index 9abf6e67974d..000000000000 --- a/test/e2e/tests/hardware-wallets/trezor-account.spec.js +++ /dev/null @@ -1,156 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../../fixture-builder'); -const { - defaultGanacheOptions, - unlockWallet, - withFixtures, - regularDelayMs, -} = require('../../helpers'); -const { shortenAddress } = require('../../../../ui/helpers/utils/util'); -const { KNOWN_PUBLIC_KEY_ADDRESSES } = require('../../../stub/keyring-bridge'); - -/** - * Connect Trezor hardware wallet without selecting an account - * - * @param {*} driver - Selenium driver - */ -async function connectTrezor(driver) { - // Open add hardware wallet modal - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement({ text: 'Add hardware wallet' }); - // This delay is needed to mitigate an existing bug in FF - // See https://github.com/metamask/metamask-extension/issues/25851 - await driver.delay(regularDelayMs); - // Select Trezor - await driver.clickElement('[data-testid="connect-trezor-btn"]'); - await driver.clickElement({ text: 'Continue' }); -} - -describe('Trezor Hardware', function () { - it('derives the correct accounts', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - await connectTrezor(driver); - - // Check that the first page of accounts is correct - for (const { address, index } of KNOWN_PUBLIC_KEY_ADDRESSES.slice( - 0, - 4, - )) { - const shortenedAddress = `${address.slice(0, 4)}...${address.slice( - -4, - )}`; - assert( - await driver.isElementPresent({ - text: shortenedAddress, - }), - `Known account ${index} not found`, - ); - } - }, - ); - }); - - it('unlocks the first account', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - await connectTrezor(driver); - - // Select first account of first page and unlock - await driver.clickElement('.hw-account-list__item__checkbox'); - await driver.clickElement({ text: 'Unlock' }); - - // Check that the correct account has been added - await driver.clickElement('[data-testid="account-menu-icon"]'); - assert( - await driver.isElementPresent({ - text: 'Trezor 1', - }), - 'Trezor account not found', - ); - assert( - await driver.isElementPresent({ - text: shortenAddress(KNOWN_PUBLIC_KEY_ADDRESSES[0].address), - }), - 'Unlocked account is wrong', - ); - }, - ); - }); - - it('unlocks multiple accounts at once and removes one', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - await connectTrezor(driver); - - // Unlock 5 Trezor accounts - const accountCheckboxes = await driver.findElements( - '.hw-account-list__item__checkbox', - ); - await accountCheckboxes[0].click(); - await accountCheckboxes[1].click(); - await accountCheckboxes[2].click(); - await accountCheckboxes[3].click(); - await accountCheckboxes[4].click(); - - await driver.clickElement({ text: 'Unlock' }); - - // Check that all 5 Trezor accounts are present - await driver.clickElement('[data-testid="account-menu-icon"]'); - for (let i = 0; i < 5; i++) { - assert( - await driver.isElementPresent({ - text: `Trezor ${i + 1}`, - }), - `Trezor account ${i + 1} not found`, - ); - assert( - await driver.isElementPresent({ - text: shortenAddress(KNOWN_PUBLIC_KEY_ADDRESSES[i].address), - }), - `Unlocked account ${i + 1} is wrong`, - ); - } - - // Remove Trezor account - const accountDetailsButton = await driver.findElements( - '[data-testid="account-list-item-menu-button"', - ); - await accountDetailsButton[1].click(); - await driver.clickElement('[data-testid="account-list-menu-remove"'); - await driver.clickElement({ - text: 'Remove', - tag: 'button', - }); - - // Assert Trezor account is removed - await driver.clickElement('[data-testid="account-menu-icon"]'); - - await driver.assertElementNotPresent({ - text: 'Trezor 1', - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/hardware-wallets/trezor-account.spec.ts b/test/e2e/tests/hardware-wallets/trezor-account.spec.ts new file mode 100644 index 000000000000..44a382023881 --- /dev/null +++ b/test/e2e/tests/hardware-wallets/trezor-account.spec.ts @@ -0,0 +1,117 @@ +import FixtureBuilder from '../../fixture-builder'; +import { withFixtures } from '../../helpers'; +import { shortenAddress } from '../../../../ui/helpers/utils/util'; +import { KNOWN_PUBLIC_KEY_ADDRESSES } from '../../../stub/keyring-bridge'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import ConnectHardwareWalletPage from '../../page-objects/pages/hardware-wallet/connect-hardware-wallet-page'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import HomePage from '../../page-objects/pages/home/homepage'; +import SelectTrezorAccountPage from '../../page-objects/pages/hardware-wallet/select-trezor-account-page'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Trezor Hardware', function () { + it('derives the correct accounts and unlocks the first account', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openAccountMenu(); + + // Choose connect hardware wallet from the account menu + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.openConnectHardwareWalletModal(); + + const connectHardwareWalletPage = new ConnectHardwareWalletPage(driver); + await connectHardwareWalletPage.check_pageIsLoaded(); + await connectHardwareWalletPage.openConnectTrezorPage(); + + const selectTrezorAccountPage = new SelectTrezorAccountPage(driver); + await selectTrezorAccountPage.check_pageIsLoaded(); + + // Check that the first page of accounts is correct + await selectTrezorAccountPage.check_trezorAccountNumber(); + for (const { address } of KNOWN_PUBLIC_KEY_ADDRESSES.slice(0, 4)) { + const shortenedAddress = `${address.slice(0, 4)}...${address.slice( + -4, + )}`; + await selectTrezorAccountPage.check_addressIsDisplayed( + shortenedAddress, + ); + } + + // Unlock first account of first page and check that the correct account has been added + await selectTrezorAccountPage.unlockAccount(1); + await headerNavbar.check_pageIsLoaded(); + await new HomePage(driver).check_expectedBalanceIsDisplayed(); + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_accountDisplayedInAccountList('Trezor 1'); + await accountListPage.check_accountAddressDisplayedInAccountList( + shortenAddress(KNOWN_PUBLIC_KEY_ADDRESSES[0].address), + ); + }, + ); + }); + + it('unlocks multiple accounts at once and removes one', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openAccountMenu(); + + // Choose connect hardware wallet from the account menu + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.openConnectHardwareWalletModal(); + + const connectHardwareWalletPage = new ConnectHardwareWalletPage(driver); + await connectHardwareWalletPage.check_pageIsLoaded(); + await connectHardwareWalletPage.openConnectTrezorPage(); + + // Unlock 5 Trezor accounts + const selectTrezorAccountPage = new SelectTrezorAccountPage(driver); + await selectTrezorAccountPage.check_pageIsLoaded(); + await selectTrezorAccountPage.check_trezorAccountNumber(); + for (let i = 1; i <= 5; i++) { + await selectTrezorAccountPage.selectTrezorAccount(i); + } + await selectTrezorAccountPage.clickUnlockButton(); + + // Check that all 5 Trezor accounts are displayed in account list + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + for (let i = 0; i < 5; i++) { + await accountListPage.check_accountDisplayedInAccountList( + `Trezor ${i + 1}`, + ); + await accountListPage.check_accountAddressDisplayedInAccountList( + shortenAddress(KNOWN_PUBLIC_KEY_ADDRESSES[i].address), + ); + } + + // Remove Trezor 1 account and check Trezor 1 account is removed + await accountListPage.removeAccount('Trezor 1'); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await headerNavbar.openAccountMenu(); + await accountListPage.check_accountIsNotDisplayedInAccountList( + 'Trezor 1', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/hardware-wallets/trezor-send.spec.ts b/test/e2e/tests/hardware-wallets/trezor-send.spec.ts index b10269c69ef0..5bb6fcb45220 100644 --- a/test/e2e/tests/hardware-wallets/trezor-send.spec.ts +++ b/test/e2e/tests/hardware-wallets/trezor-send.spec.ts @@ -2,13 +2,11 @@ import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; import FixtureBuilder from '../../fixture-builder'; -import { - defaultGanacheOptions, - logInWithBalanceValidation, - sendTransaction, - withFixtures, -} from '../../helpers'; +import { logInWithBalanceValidation, withFixtures } from '../../helpers'; import { KNOWN_PUBLIC_KEY_ADDRESSES } from '../../../stub/keyring-bridge'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; +import HomePage from '../../page-objects/pages/home/homepage'; +import { sendRedesignedTransactionToAddress } from '../../page-objects/flows/send-transaction.flow'; const RECIPIENT = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; @@ -17,7 +15,6 @@ describe('Trezor Hardware', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder().withTrezorAccount().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ @@ -33,14 +30,16 @@ describe('Trezor Hardware', function (this: Suite) { '0x100000000000000000000', ); await logInWithBalanceValidation(driver); - - await sendTransaction(driver, RECIPIENT, '1'); - - // Wait for transaction to be confirmed - await driver.waitForSelector({ - css: '.transaction-status-label', - text: 'Confirmed', + await sendRedesignedTransactionToAddress({ + driver, + recipientAddress: RECIPIENT, + amount: '1', }); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + const activityList = new ActivityListPage(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(); + await activityList.check_txAmountInActivity(); }, ); }); diff --git a/test/e2e/tests/hardware-wallets/trezor-sign.spec.ts b/test/e2e/tests/hardware-wallets/trezor-sign.spec.ts index f4cbf87b9dd4..84aaac412f30 100644 --- a/test/e2e/tests/hardware-wallets/trezor-sign.spec.ts +++ b/test/e2e/tests/hardware-wallets/trezor-sign.spec.ts @@ -1,15 +1,10 @@ -import { strict as assert } from 'assert'; import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import FixtureBuilder from '../../fixture-builder'; -import { - defaultGanacheOptions, - openDapp, - unlockWallet, - WINDOW_TITLES, - withFixtures, -} from '../../helpers'; +import { defaultGanacheOptions, withFixtures } from '../../helpers'; import { KNOWN_PUBLIC_KEY_ADDRESSES } from '../../../stub/keyring-bridge'; +import TestDappPage from '../../page-objects/pages/test-dapp'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; describe('Trezor Hardware Signatures', function (this: Suite) { it('sign typed v4', async function () { @@ -26,28 +21,13 @@ describe('Trezor Hardware Signatures', function (this: Suite) { dapp: true, }, async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - - await openDapp(driver); - await driver.clickElement('#signTypedDataV4'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.delay(1000); - - await driver.clickElementSafe('.confirm-scroll-to-bottom__button'); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.delay(1000); - - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#signTypedDataV4Verify'); - - const verifyRecoverAddress = await driver.findElement( - '#signTypedDataV4VerifyResult', - ); - - assert.equal( - await verifyRecoverAddress.getText(), - KNOWN_PUBLIC_KEY_ADDRESSES[0].address.toLocaleLowerCase(), + await loginWithBalanceValidation(driver); + const testDappPage = new TestDappPage(driver); + await testDappPage.openTestDappPage(); + await testDappPage.check_pageIsLoaded(); + await testDappPage.signTypedDataV4(); + await testDappPage.check_successSignTypedDataV4( + KNOWN_PUBLIC_KEY_ADDRESSES[0].address, ); }, ); diff --git a/test/e2e/tests/notifications/account-syncing/helpers.ts b/test/e2e/tests/identity/account-syncing/helpers.ts similarity index 100% rename from test/e2e/tests/notifications/account-syncing/helpers.ts rename to test/e2e/tests/identity/account-syncing/helpers.ts diff --git a/test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts b/test/e2e/tests/identity/account-syncing/importing-private-key-account.spec.ts similarity index 81% rename from test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts rename to test/e2e/tests/identity/account-syncing/importing-private-key-account.spec.ts index 1940d4bf3fd6..6647a78f42d3 100644 --- a/test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts +++ b/test/e2e/tests/identity/account-syncing/importing-private-key-account.spec.ts @@ -2,16 +2,16 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; -import { mockNotificationServices } from '../mocks'; +import { mockIdentityServices } from '../mocks'; import { - NOTIFICATIONS_TEAM_IMPORTED_PRIVATE_KEY, - NOTIFICATIONS_TEAM_PASSWORD, - NOTIFICATIONS_TEAM_SEED_PHRASE, + IDENTITY_TEAM_IMPORTED_PRIVATE_KEY, + IDENTITY_TEAM_PASSWORD, + IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; import AccountListPage from '../../../page-objects/pages/account-list-page'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; import { accountsSyncMockResponse } from './mockData'; import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; @@ -37,17 +37,14 @@ describe('Account syncing - Import With Private Key @no-mmi', function () { }, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); @@ -71,7 +68,7 @@ describe('Account syncing - Import With Private Key @no-mmi', function () { ); await accountListPage.openAccountOptionsMenu(); await accountListPage.addNewImportedAccount( - NOTIFICATIONS_TEAM_IMPORTED_PRIVATE_KEY, + IDENTITY_TEAM_IMPORTED_PRIVATE_KEY, ); }, ); @@ -85,17 +82,14 @@ describe('Account syncing - Import With Private Key @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); diff --git a/test/e2e/tests/notifications/account-syncing/mockData.ts b/test/e2e/tests/identity/account-syncing/mockData.ts similarity index 100% rename from test/e2e/tests/notifications/account-syncing/mockData.ts rename to test/e2e/tests/identity/account-syncing/mockData.ts diff --git a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts b/test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts similarity index 85% rename from test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts rename to test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts index ae283c47fa8b..64a3ddbf5fd7 100644 --- a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts +++ b/test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts @@ -2,12 +2,13 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; -import { mockNotificationServices } from '../mocks'; -import { NOTIFICATIONS_TEAM_PASSWORD } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { ACCOUNT_TYPE } from '../../../constants'; +import { mockIdentityServices } from '../mocks'; +import { IDENTITY_TEAM_PASSWORD } from '../constants'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; import AccountListPage from '../../../page-objects/pages/account-list-page'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import { completeCreateNewWalletOnboardingFlow, completeImportSRPOnboardingFlow, @@ -36,17 +37,14 @@ describe('Account syncing - New User @no-mmi', function () { server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { // Create a new wallet await completeCreateNewWalletOnboardingFlow({ driver, - password: NOTIFICATIONS_TEAM_PASSWORD, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); @@ -67,7 +65,10 @@ describe('Account syncing - New User @no-mmi', function () { // Add a second account await accountListPage.openAccountOptionsMenu(); - await accountListPage.addNewAccount('My Second Account'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: 'My Second Account', + }); // Set SRP to use for retreival const headerNavbar = new HeaderNavbar(driver); @@ -81,9 +82,7 @@ describe('Account syncing - New User @no-mmi', function () { await privacySettings.check_pageIsLoaded(); await privacySettings.openRevealSrpQuiz(); await privacySettings.completeRevealSrpQuiz(); - await privacySettings.fillPasswordToRevealSrp( - NOTIFICATIONS_TEAM_PASSWORD, - ); + await privacySettings.fillPasswordToRevealSrp(IDENTITY_TEAM_PASSWORD); walletSrp = await privacySettings.getSrpInRevealSrpDialog(); if (!walletSrp) { throw new Error('Wallet SRP was not set'); @@ -100,10 +99,7 @@ describe('Account syncing - New User @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { @@ -111,7 +107,7 @@ describe('Account syncing - New User @no-mmi', function () { await completeImportSRPOnboardingFlow({ driver, seedPhrase: walletSrp, - password: NOTIFICATIONS_TEAM_PASSWORD, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); diff --git a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts b/test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts similarity index 87% rename from test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts rename to test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts index 3ec44e4fd07e..bd063c182baf 100644 --- a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts +++ b/test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts @@ -2,15 +2,16 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; -import { mockNotificationServices } from '../mocks'; +import { mockIdentityServices } from '../mocks'; import { - NOTIFICATIONS_TEAM_PASSWORD, - NOTIFICATIONS_TEAM_SEED_PHRASE, + IDENTITY_TEAM_PASSWORD, + IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { ACCOUNT_TYPE } from '../../../constants'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import AccountListPage from '../../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import OnboardingCompletePage from '../../../page-objects/pages/onboarding/onboarding-complete-page'; import OnboardingPrivacySettingsPage from '../../../page-objects/pages/onboarding/onboarding-privacy-settings-page'; import { @@ -45,17 +46,14 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { getResponse: accountsSyncMockResponse, }, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await importSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const onboardingCompletePage = new OnboardingCompletePage(driver); await onboardingCompletePage.check_pageIsLoaded(); @@ -105,16 +103,13 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await createNewWalletOnboardingFlow({ driver, - password: NOTIFICATIONS_TEAM_PASSWORD, + password: IDENTITY_TEAM_PASSWORD, }); const onboardingCompletePage = new OnboardingCompletePage(driver); await onboardingCompletePage.check_pageIsLoaded(); @@ -141,7 +136,11 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'Account 1', ); - await accountListPage.addNewAccount('New Account'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: 'New Account', + }); + // Set SRP to use for retreival const headerNavbar = new HeaderNavbar(driver); await headerNavbar.check_pageIsLoaded(); @@ -154,9 +153,7 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { await privacySettings.check_pageIsLoaded(); await privacySettings.openRevealSrpQuiz(); await privacySettings.completeRevealSrpQuiz(); - await privacySettings.fillPasswordToRevealSrp( - NOTIFICATIONS_TEAM_PASSWORD, - ); + await privacySettings.fillPasswordToRevealSrp(IDENTITY_TEAM_PASSWORD); walletSrp = await privacySettings.getSrpInRevealSrpDialog(); if (!walletSrp) { throw new Error('Wallet SRP was not set'); @@ -174,17 +171,14 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, seedPhrase: walletSrp, - password: NOTIFICATIONS_TEAM_PASSWORD, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts b/test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts similarity index 84% rename from test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts rename to test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts index 1c1f7b3119d7..e02833e7c172 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts @@ -2,15 +2,16 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; -import { mockNotificationServices } from '../mocks'; +import { mockIdentityServices } from '../mocks'; +import { ACCOUNT_TYPE } from '../../../constants'; import { - NOTIFICATIONS_TEAM_PASSWORD, - NOTIFICATIONS_TEAM_SEED_PHRASE, + IDENTITY_TEAM_PASSWORD, + IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; import AccountListPage from '../../../page-objects/pages/account-list-page'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; import { accountsSyncMockResponse } from './mockData'; import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; @@ -36,17 +37,14 @@ describe('Account syncing - Add Account @no-mmi', function () { }, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); @@ -68,7 +66,10 @@ describe('Account syncing - Add Account @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.addNewAccount('My third account'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: 'My third account', + }); }, ); @@ -81,17 +82,14 @@ describe('Account syncing - Add Account @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); @@ -141,17 +139,14 @@ describe('Account syncing - Add Account @no-mmi', function () { }, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); @@ -173,7 +168,9 @@ describe('Account syncing - Add Account @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); }, ); @@ -186,17 +183,14 @@ describe('Account syncing - Add Account @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts b/test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts similarity index 78% rename from test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts rename to test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts index 22618d70f3c5..ce10b3129d58 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts @@ -2,15 +2,16 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; -import { mockNotificationServices } from '../mocks'; +import { mockIdentityServices } from '../mocks'; import { - NOTIFICATIONS_TEAM_PASSWORD, - NOTIFICATIONS_TEAM_SEED_PHRASE, + IDENTITY_TEAM_PASSWORD, + IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; +import AccountDetailsModal from '../../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../../page-objects/pages/account-list-page'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; import { accountsSyncMockResponse } from './mockData'; import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; @@ -36,17 +37,14 @@ describe('Account syncing - Rename Accounts @no-mmi', function () { }, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); @@ -68,8 +66,14 @@ describe('Account syncing - Rename Accounts @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.openAccountOptionsMenu(); - await accountListPage.changeAccountLabel('My Renamed First Account'); + await accountListPage.openAccountDetailsModal( + 'My First Synced Account', + ); + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.changeAccountLabel( + 'My Renamed First Account', + ); }, ); @@ -82,17 +86,14 @@ describe('Account syncing - Rename Accounts @no-mmi', function () { USER_STORAGE_FEATURE_NAMES.accounts, server, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts b/test/e2e/tests/identity/account-syncing/sync-after-onboarding.spec.ts similarity index 82% rename from test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts rename to test/e2e/tests/identity/account-syncing/sync-after-onboarding.spec.ts index be2b2604633c..8d714bb6b273 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-after-onboarding.spec.ts @@ -2,15 +2,15 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; -import { mockNotificationServices } from '../mocks'; +import { mockIdentityServices } from '../mocks'; import { - NOTIFICATIONS_TEAM_PASSWORD, - NOTIFICATIONS_TEAM_SEED_PHRASE, + IDENTITY_TEAM_PASSWORD, + IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; import AccountListPage from '../../../page-objects/pages/account-list-page'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; import { accountsSyncMockResponse } from './mockData'; import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; @@ -35,17 +35,14 @@ describe('Account syncing - Onboarding @no-mmi', function () { getResponse: accountsSyncMockResponse, }, ); - return mockNotificationServices( - server, - userStorageMockttpController, - ); + return mockIdentityServices(server, userStorageMockttpController); }, }, async ({ driver }) => { await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); diff --git a/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts b/test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts similarity index 87% rename from test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts rename to test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts index 8ecaff943721..ec52bb3124bd 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts @@ -3,13 +3,15 @@ import { unlockWallet, withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockInfuraAndAccountSync } from '../mocks'; import { - NOTIFICATIONS_TEAM_PASSWORD, - NOTIFICATIONS_TEAM_SEED_PHRASE, + IDENTITY_TEAM_PASSWORD, + IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; -import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { ACCOUNT_TYPE } from '../../../constants'; +import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; +import AccountDetailsModal from '../../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../../page-objects/pages/account-list-page'; -import HomePage from '../../../page-objects/pages/homepage'; +import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; import { accountsSyncMockResponse } from './mockData'; import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; @@ -81,8 +83,8 @@ describe('Account syncing - User already has balances on multple accounts @no-mm // Complete initial setup with provided seed phrase await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); // Verify initial state and balance @@ -108,7 +110,9 @@ describe('Account syncing - User already has balances on multple accounts @no-mm } // Create new account and prepare for additional accounts - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); accountsToMock = [...INITIAL_ACCOUNTS, ...ADDITIONAL_ACCOUNTS]; }, ); @@ -132,8 +136,8 @@ describe('Account syncing - User already has balances on multple accounts @no-mm // Complete setup again for new session await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); @@ -158,11 +162,13 @@ describe('Account syncing - User already has balances on multple accounts @no-mm // Rename Account 6 to verify update to user storage await accountListPage.switchToAccount('Account 6'); + await header.check_accountLabel('Account 6'); await header.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); await accountListPage.openAccountDetailsModal('Account 6'); - await accountListPage.changeLabelFromAccountDetailsModal( - 'My Renamed Account 6', - ); + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.changeAccountLabel('My Renamed Account 6'); }, ); @@ -185,8 +191,8 @@ describe('Account syncing - User already has balances on multple accounts @no-mm // Complete setup for final verification await completeImportSRPOnboardingFlow({ driver, - seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, - password: NOTIFICATIONS_TEAM_PASSWORD, + seedPhrase: IDENTITY_TEAM_SEED_PHRASE, + password: IDENTITY_TEAM_PASSWORD, }); const homePage = new HomePage(driver); @@ -210,7 +216,7 @@ describe('Account syncing - User already has balances on multple accounts @no-mm // Lock and unlock wallet to ensure that number of preloaded accounts have not gone up await homePage.headerNavbar.lockMetaMask(); await unlockWallet(driver, { - password: NOTIFICATIONS_TEAM_PASSWORD, + password: IDENTITY_TEAM_PASSWORD, waitLoginSuccess: true, navigate: true, }); diff --git a/test/e2e/tests/identity/constants.ts b/test/e2e/tests/identity/constants.ts new file mode 100644 index 000000000000..4819a2368606 --- /dev/null +++ b/test/e2e/tests/identity/constants.ts @@ -0,0 +1,9 @@ +// As we rely on profile syncing for most of our features, we need to use the same SRP for all of our tests +export const IDENTITY_TEAM_SEED_PHRASE = + 'leisure swallow trip elbow prison wait rely keep supply hole general mountain'; +export const IDENTITY_TEAM_PASSWORD = 'notify_password'; +// You can use the storage key below to generate mock data +export const IDENTITY_TEAM_STORAGE_KEY = + '0d55d30da233959674d14076737198c05ae3fb8631a17e20d3c28c60dddd82f7'; +export const IDENTITY_TEAM_IMPORTED_PRIVATE_KEY = + '0179f7453ff337aba89d04b00085764092cf8c0cfe91d5614c39c2be902ad582'; diff --git a/test/e2e/tests/identity/mocks.ts b/test/e2e/tests/identity/mocks.ts new file mode 100644 index 000000000000..e81f70f2d4a5 --- /dev/null +++ b/test/e2e/tests/identity/mocks.ts @@ -0,0 +1,136 @@ +import { Mockttp, RequestRuleBuilder } from 'mockttp'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; +import { UserStorageMockttpController } from '../../helpers/identity/user-storage/userStorageMockttpController'; +import { accountsSyncMockResponse } from './account-syncing/mockData'; + +const AuthMocks = AuthenticationController.Mocks; + +type MockResponse = { + url: string | RegExp; + requestMethod: 'GET' | 'POST' | 'PUT' | 'DELETE'; + response: unknown; +}; + +/** + * E2E mock setup for identity APIs (Auth, UserStorage, Profile syncing) + * + * @param server - server obj used to mock our endpoints + * @param userStorageMockttpControllerInstance - optional instance of UserStorageMockttpController, useful if you need persisted user storage between tests + */ +export async function mockIdentityServices( + server: Mockttp, + userStorageMockttpControllerInstance: UserStorageMockttpController = new UserStorageMockttpController(), +) { + // Auth + mockAPICall(server, AuthMocks.getMockAuthNonceResponse()); + mockAPICall(server, AuthMocks.getMockAuthLoginResponse()); + mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse()); + + // Storage + if ( + !userStorageMockttpControllerInstance?.paths.get( + USER_STORAGE_FEATURE_NAMES.accounts, + ) + ) { + userStorageMockttpControllerInstance.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); + } + if ( + !userStorageMockttpControllerInstance?.paths.get( + USER_STORAGE_FEATURE_NAMES.networks, + ) + ) { + userStorageMockttpControllerInstance.setupPath( + USER_STORAGE_FEATURE_NAMES.networks, + server, + ); + } +} + +function mockAPICall(server: Mockttp, response: MockResponse) { + let requestRuleBuilder: RequestRuleBuilder | undefined; + + if (response.requestMethod === 'GET') { + requestRuleBuilder = server.forGet(response.url); + } + + if (response.requestMethod === 'POST') { + requestRuleBuilder = server.forPost(response.url); + } + + if (response.requestMethod === 'PUT') { + requestRuleBuilder = server.forPut(response.url); + } + + if (response.requestMethod === 'DELETE') { + requestRuleBuilder = server.forDelete(response.url); + } + + requestRuleBuilder?.thenCallback(() => ({ + statusCode: 200, + json: response.response, + })); +} + +type MockInfuraAndAccountSyncOptions = { + accountsToMock?: string[]; + accountsSyncResponse?: typeof accountsSyncMockResponse; +}; + +const MOCK_ETH_BALANCE = '0xde0b6b3a7640000'; +const INFURA_URL = + 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; + +/** + * Sets up mock responses for Infura balance checks and account syncing + * + * @param mockServer - The Mockttp server instance + * @param userStorageMockttpController - Controller for user storage mocks + * @param options - Configuration options for mocking + */ +export async function mockInfuraAndAccountSync( + mockServer: Mockttp, + userStorageMockttpController: UserStorageMockttpController, + options: MockInfuraAndAccountSyncOptions = {}, +): Promise { + const accounts = options.accountsToMock ?? []; + + // Set up User Storage / Account Sync mock + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + mockServer, + ); + + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + mockServer, + { + getResponse: options.accountsSyncResponse ?? undefined, + }, + ); + + // Account Balances + if (accounts.length > 0) { + accounts.forEach((account) => { + mockServer + .forPost(INFURA_URL) + .withJsonBodyIncluding({ + method: 'eth_getBalance', + params: [account.toLowerCase()], + }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: MOCK_ETH_BALANCE, + }, + })); + }); + } + + mockIdentityServices(mockServer, userStorageMockttpController); +} diff --git a/test/e2e/tests/metrics/app-opened.spec.ts b/test/e2e/tests/metrics/app-opened.spec.ts new file mode 100644 index 000000000000..80169e36c369 --- /dev/null +++ b/test/e2e/tests/metrics/app-opened.spec.ts @@ -0,0 +1,104 @@ +import { strict as assert } from 'assert'; +import { Mockttp } from 'mockttp'; +import { + withFixtures, + getEventPayloads, + unlockWallet, + connectToDapp, +} from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; + +/** + * Mocks the segment API for the App Opened event that we expect to see when + * these tests are run. + * + * @param mockServer - The mock server instance. + * @returns The mocked endpoints + */ +async function mockSegment(mockServer: Mockttp) { + return [ + await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: 'App Opened' }], + }) + .thenCallback(() => { + return { + statusCode: 200, + }; + }), + ]; +} + +describe('App Opened metric @no-mmi', function () { + it('should send AppOpened metric when app is opened and metrics are enabled', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-fd20', + participateInMetaMetrics: true, + }) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await loginWithoutBalanceValidation(driver); + + const events = await getEventPayloads(driver, mockedEndpoints); + assert.equal(events.length, 1); + assert.equal(events[0].properties.category, 'App'); + }, + ); + }); + + it('should not send AppOpened metric when metrics are disabled', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-fd20', + participateInMetaMetrics: false, + }) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await unlockWallet(driver); + + const events = await getEventPayloads(driver, mockedEndpoints); + assert.equal(events.length, 0); + }, + ); + }); + + it('should send AppOpened metric when dapp opens MetaMask', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-fd20', + participateInMetaMetrics: true, + }) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await unlockWallet(driver); + + // Connect to dapp which will trigger MetaMask to open + await connectToDapp(driver); + + // Wait for events to be tracked + const events = await getEventPayloads(driver, mockedEndpoints); + assert.equal(events.length, 1); + assert.equal(events[0].properties.category, 'App'); + }, + ); + }); +}); diff --git a/test/e2e/tests/metrics/dapp-viewed.spec.js b/test/e2e/tests/metrics/dapp-viewed.spec.js index 668f93e65dc5..a747ea658937 100644 --- a/test/e2e/tests/metrics/dapp-viewed.spec.js +++ b/test/e2e/tests/metrics/dapp-viewed.spec.js @@ -297,10 +297,6 @@ describe('Dapp viewed Event @no-mmi', function () { text: 'All Permissions', tag: 'div', }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); await driver.clickElement({ text: '127.0.0.1:8080', tag: 'p', diff --git a/test/e2e/tests/metrics/delete-metametrics-data.spec.ts b/test/e2e/tests/metrics/delete-metametrics-data.spec.ts index 308ff8508d0a..621e42b29c85 100644 --- a/test/e2e/tests/metrics/delete-metametrics-data.spec.ts +++ b/test/e2e/tests/metrics/delete-metametrics-data.spec.ts @@ -17,11 +17,7 @@ const selectors = { globalMenuSettingsButton: '[data-testid="global-menu-settings"]', securityAndPrivacySettings: { text: 'Security & privacy', tag: 'div' }, experimentalSettings: { text: 'Experimental', tag: 'div' }, - deletMetaMetricsSettings: '[data-testid="delete-metametrics-data-button"]', - deleteMetaMetricsDataButton: { - text: 'Delete MetaMetrics data', - tag: 'button', - }, + deleteMetaMetricsDataButton: '[data-testid="delete-metametrics-data-button"]', clearButton: { text: 'Clear', tag: 'button' }, backButton: '[data-testid="settings-back-button"]', }; @@ -111,7 +107,6 @@ describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) { await driver.clickElement(selectors.globalMenuSettingsButton); await driver.clickElement(selectors.securityAndPrivacySettings); - await driver.findElement(selectors.deletMetaMetricsSettings); await driver.clickElement(selectors.deleteMetaMetricsDataButton); // there is a race condition, where we need to wait before clicking clear button otherwise an error is thrown in the background @@ -157,65 +152,37 @@ describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) { }, ); }); + it('while user has opted out for metrics tracking', async function () { await withFixtures( { fixtures: new FixtureBuilder() .withMetaMetricsController({ metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: false, }) .build(), defaultGanacheOptions, title: this.test?.fullTitle(), testSpecificMock: mockSegment, }, - async ({ - driver, - mockedEndpoint: mockedEndpoints, - }: TestSuiteArguments) => { + async ({ driver }: TestSuiteArguments) => { await unlockWallet(driver); await driver.clickElement(selectors.accountOptionsMenuButton); await driver.clickElement(selectors.globalMenuSettingsButton); await driver.clickElement(selectors.securityAndPrivacySettings); - await driver.findElement(selectors.deletMetaMetricsSettings); - await driver.clickElement(selectors.deleteMetaMetricsDataButton); - - // there is a race condition, where we need to wait before clicking clear button otherwise an error is thrown in the background - // we cannot wait for a UI conditon, so we a delay to mitigate this until another solution is found - await driver.delay(3000); - await driver.clickElementAndWaitToDisappear(selectors.clearButton); - const deleteMetaMetricsDataButton = await driver.findElement( selectors.deleteMetaMetricsDataButton, ); await ( deleteMetaMetricsDataButton as WebElementWithWaitForElementState ).waitForElementState('disabled'); - - const events = await getEventPayloads( - driver, - mockedEndpoints as MockedEndpoint[], - ); - assert.equal(events.length, 2); - - await driver.clickElementAndWaitToDisappear( - '.mm-box button[aria-label="Close"]', - ); - await driver.clickElement(selectors.accountOptionsMenuButton); - await driver.clickElement(selectors.globalMenuSettingsButton); - await driver.clickElement(selectors.securityAndPrivacySettings); - - const deleteMetaMetricsDataButtonRefreshed = await driver.findElement( - selectors.deleteMetaMetricsDataButton, - ); - await ( - deleteMetaMetricsDataButtonRefreshed as WebElementWithWaitForElementState - ).waitForElementState('disabled'); }, ); }); + it('when the user has never opted in for metrics', async function () { await withFixtures( { @@ -230,7 +197,6 @@ describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) { await driver.clickElement(selectors.accountOptionsMenuButton); await driver.clickElement(selectors.globalMenuSettingsButton); await driver.clickElement(selectors.securityAndPrivacySettings); - await driver.findElement(selectors.deletMetaMetricsSettings); const deleteMetaMetricsDataButton = await driver.findElement( selectors.deleteMetaMetricsDataButton, diff --git a/test/e2e/tests/metrics/errors.spec.js b/test/e2e/tests/metrics/errors.spec.js index b35a38d83206..d22f71524120 100644 --- a/test/e2e/tests/metrics/errors.spec.js +++ b/test/e2e/tests/metrics/errors.spec.js @@ -874,9 +874,12 @@ describe('Sentry errors', function () { srcTokenAmount: true, walletAddress: false, }, + destTokensLoadingStatus: false, quotesLastFetched: true, quotesLoadingStatus: true, quotesRefreshCount: true, + quoteFetchError: true, + quotesInitialLoadTime: true, }, currentPopupId: false, // Initialized as undefined // Part of transaction controller store, but missing from the initial @@ -900,6 +903,13 @@ describe('Sentry errors', function () { // the "resetState" method swapsFeatureFlags: true, }, + // Part of the AuthenticationController store, but initialized as undefined + // Only populated once the client is authenticated + sessionData: { + accessToken: false, + expiresIn: true, + profile: true, + }, // This can get erased due to a bug in the app state controller's // preferences state change handler timeoutMinutes: true, diff --git a/test/e2e/tests/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js index 2ea84d281b50..edbea055ecc9 100644 --- a/test/e2e/tests/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -7,9 +7,7 @@ const { openDapp, unlockWallet, getEventPayloads, - clickSignOnSignatureConfirmation, - tempToggleSettingRedesignedConfirmations, - validateContractDetails, + clickSignOnRedesignedSignatureConfirmation, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -56,6 +54,7 @@ const expectedEventPropertiesBase = { environment_type: 'background', security_alert_reason: 'CheckingChain', security_alert_response: 'loading', + ui_customizations: ['redesigned_confirmation'], }; describe('Signature Approved Event @no-mmi', function () { @@ -76,14 +75,12 @@ describe('Signature Approved Event @no-mmi', function () { }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); // creates a sign typed data signature request await driver.clickElement('#signTypedDataV4'); await switchToNotificationWindow(driver); - await validateContractDetails(driver); - await clickSignOnSignatureConfirmation({ driver }); + await clickSignOnRedesignedSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { @@ -119,14 +116,12 @@ describe('Signature Approved Event @no-mmi', function () { }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); // creates a sign typed data signature request await driver.clickElement('#signTypedDataV3'); await switchToNotificationWindow(driver); - await validateContractDetails(driver); - await clickSignOnSignatureConfirmation({ driver }); + await clickSignOnRedesignedSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { @@ -160,13 +155,12 @@ describe('Signature Approved Event @no-mmi', function () { }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); // creates a sign typed data signature request await driver.clickElement('#signTypedData'); await switchToNotificationWindow(driver); - await clickSignOnSignatureConfirmation({ driver }); + await clickSignOnRedesignedSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { @@ -200,13 +194,12 @@ describe('Signature Approved Event @no-mmi', function () { }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); // creates a sign typed data signature request await driver.clickElement('#personalSign'); await switchToNotificationWindow(driver); - await clickSignOnSignatureConfirmation({ driver }); + await clickSignOnRedesignedSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { 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 3c2430fbe063..b0aedf57415f 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 @@ -42,16 +42,16 @@ "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, - "nftsDropdownState": {}, - "termsOfUseLastAgreed": "number", - "qrHardware": {}, - "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, - "snapsInstallPrivacyWarningShown": true, "surveyLinkLastClickedOrClosed": "object", + "switchedNetworkNeverShowMessage": "boolean", + "slides": "object", + "qrHardware": {}, + "nftsDropdownState": {}, "signatureSecurityAlertResponses": "object", "switchedNetworkDetails": null, - "switchedNetworkNeverShowMessage": "boolean", - "currentExtensionPopupId": "number" + "currentExtensionPopupId": "number", + "termsOfUseLastAgreed": "number", + "snapsInstallPrivacyWarningShown": true }, "ApprovalController": { "pendingApprovals": "object", @@ -62,13 +62,13 @@ "BridgeController": { "bridgeState": { "bridgeFeatureFlags": { - "extensionConfig": "object", - "extensionSupport": "boolean", - "srcNetworkAllowlist": { "0": "string", "1": "string", "2": "string" }, - "destNetworkAllowlist": { "0": "string", "1": "string", "2": "string" } + "extensionConfig": { + "refreshRate": "number", + "maxRefreshCount": "number", + "support": "boolean", + "chains": { "0x1": "object", "0xa4b1": "object", "0xe708": "object" } + } }, - "srcTokens": {}, - "srcTopAssets": {}, "destTokens": {}, "destTopAssets": {}, "quoteRequest": { @@ -80,7 +80,7 @@ } }, "BridgeStatusController": { "bridgeStatusState": { "txHistory": "object" } }, - "CronjobController": { "jobs": "object" }, + "CronjobController": { "jobs": "object", "events": "object" }, "CurrencyController": { "currentCurrency": "usd", "currencyRates": { @@ -171,7 +171,6 @@ "allNfts": "object", "ignoredNfts": "object" }, - "NotificationController": { "notifications": "object" }, "NotificationServicesController": { "subscriptionAccountsSeen": "object", "isMetamaskNotificationsFeatureSeen": "boolean", @@ -210,7 +209,6 @@ "useNftDetection": false, "use4ByteResolution": true, "useCurrencyRateCheck": true, - "useRequestQueue": true, "openSeaEnabled": false, "securityAlertsEnabled": "boolean", "watchEthereumAccountEnabled": "boolean", @@ -235,13 +233,10 @@ "petnamesEnabled": true, "showMultiRpcModal": "boolean", "isRedesignedConfirmationsDeveloperEnabled": "boolean", - "redesignedConfirmationsEnabled": true, - "redesignedTransactionsEnabled": "boolean", "tokenSortConfig": "object", - "tokenNetworkFilter": { - "0x539": "boolean" - }, - "shouldShowAggregatedBalancePopover": "boolean" + "shouldShowAggregatedBalancePopover": "boolean", + "tokenNetworkFilter": { "0x539": "boolean" }, + "smartTransactionsMigrationApplied": "boolean" }, "ipfsGateway": "string", "isIpfsGatewayEnabled": "boolean", @@ -258,6 +253,10 @@ "showIncomingTransactions": "object" }, "QueuedRequestController": { "queuedRequestCount": 0 }, + "RemoteFeatureFlagController": { + "remoteFeatureFlags": {}, + "cacheTimestamp": "number" + }, "SelectedNetworkController": { "domains": "object" }, "SignatureController": { "signatureRequests": "object", @@ -318,14 +317,10 @@ "swapsFeatureFlags": {} } }, - "TokenBalancesController": { - "tokenBalances": "object" - }, + "TokenBalancesController": { "tokenBalances": "object" }, "TokenListController": { "tokenList": "object", - "tokensChainsCache": { - "0x539": "object" - }, + "tokensChainsCache": { "0x539": "object" }, "preventPollingOnNetworkRestart": false }, "TokenRatesController": { "marketData": "object" }, @@ -348,6 +343,7 @@ "isProfileSyncingEnabled": null, "isProfileSyncingUpdateLoading": "boolean", "hasAccountSyncingSyncedAtLeastOnce": "boolean", - "isAccountSyncingReadyToBeDispatched": "boolean" + "isAccountSyncingReadyToBeDispatched": "boolean", + "isAccountSyncingInProgress": "boolean" } } 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 01f94b55d5c7..39d2b3af5aa1 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 @@ -1,7 +1,71 @@ { "DNS": "object", "activeTab": "object", - "appState": "object", + "appState": { + "customNonceValue": "", + "isAccountMenuOpen": false, + "isNetworkMenuOpen": false, + "nextNonce": null, + "pendingTokens": "object", + "welcomeScreenSeen": false, + "confirmationExchangeRates": {}, + "shouldClose": "boolean", + "menuOpen": "boolean", + "modal": "object", + "alertOpen": "boolean", + "alertMessage": null, + "qrCodeData": null, + "networkDropdownOpen": "boolean", + "importNftsModal": "object", + "showPermittedNetworkToastOpen": "boolean", + "showIpfsModalOpen": "boolean", + "showBasicFunctionalityModal": "boolean", + "externalServicesOnboardingToggleState": "boolean", + "keyringRemovalSnapModal": "object", + "showKeyringRemovalSnapModal": "boolean", + "importTokensModalOpen": "boolean", + "deprecatedNetworkModalOpen": "boolean", + "accountDetail": "object", + "isLoading": "boolean", + "isNftStillFetchingIndication": "boolean", + "showNftDetectionEnablementToast": "boolean", + "loadingMessage": "undefined", + "warning": "string", + "buyView": "object", + "defaultHdPaths": "object", + "networksTabSelectedRpcUrl": "string", + "requestAccountTabs": "object", + "openMetaMaskTabs": "object", + "currentWindowTab": "object", + "showWhatsNewPopup": "boolean", + "showTermsOfUsePopup": "boolean", + "singleExceptions": "object", + "gasLoadingAnimationIsShowing": "boolean", + "smartTransactionsError": null, + "smartTransactionsErrorMessageDismissed": "boolean", + "ledgerWebHidConnectedStatus": "string", + "ledgerTransportStatus": "string", + "newNftAddedMessage": "string", + "removeNftMessage": "string", + "newNetworkAddedName": "string", + "editedNetwork": "undefined", + "newNetworkAddedConfigurationId": "string", + "selectedNetworkConfigurationId": "string", + "sendInputCurrencySwitched": "boolean", + "newTokensImported": "string", + "newTokensImportedError": "string", + "onboardedInThisUISession": "boolean", + "customTokenAmount": "string", + "scrollToBottom": "boolean", + "txId": null, + "accountDetailsAddress": "string", + "showDeleteMetaMetricsDataModal": "boolean", + "showDataDeletionErrorModal": "boolean", + "snapsInstallPrivacyWarningShown": "boolean", + "isAddingNewNetwork": "boolean", + "isMultiRpcOnboarding": "boolean", + "errorInSettings": null + }, "bridge": "object", "confirmAlerts": "object", "confirmTransaction": "object", @@ -12,18 +76,12 @@ "metamask": { "isInitialized": true, "isUnlocked": true, - "isAccountMenuOpen": false, - "isNetworkMenuOpen": false, "internalAccounts": { "accounts": "object", "selectedAccount": "string" }, "transactions": "object", "networkConfigurations": "object", "addressBook": "object", - "confirmationExchangeRates": {}, - "pendingTokens": "object", - "customNonceValue": "", "useBlockie": false, "featureFlags": {}, - "welcomeScreenSeen": false, "currentLocale": "en", "preferences": { "hideZeroBalanceTokens": false, @@ -38,8 +96,7 @@ "tokenSortConfig": "object", "shouldShowAggregatedBalancePopover": "boolean", "tokenNetworkFilter": { "0x539": "boolean" }, - "redesignedConfirmationsEnabled": true, - "redesignedTransactionsEnabled": "boolean" + "smartTransactionsMigrationApplied": "boolean" }, "firstTimeFlowType": "import", "completedOnboarding": true, @@ -47,7 +104,6 @@ "use4ByteResolution": true, "participateInMetaMetrics": true, "dataCollectionForMarketing": "boolean", - "nextNonce": null, "currencyRates": { "ETH": { "conversionDate": "number", @@ -86,16 +142,16 @@ "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, - "nftsDropdownState": {}, - "termsOfUseLastAgreed": "number", - "qrHardware": {}, - "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, - "snapsInstallPrivacyWarningShown": true, "surveyLinkLastClickedOrClosed": "object", + "switchedNetworkNeverShowMessage": "boolean", + "slides": "object", + "qrHardware": {}, + "nftsDropdownState": {}, "signatureSecurityAlertResponses": "object", "switchedNetworkDetails": null, - "switchedNetworkNeverShowMessage": "boolean", "currentExtensionPopupId": "number", + "termsOfUseLastAgreed": "number", + "snapsInstallPrivacyWarningShown": true, "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, @@ -120,7 +176,6 @@ "useTokenDetection": true, "useNftDetection": false, "useCurrencyRateCheck": true, - "useRequestQueue": true, "openSeaEnabled": false, "securityAlertsEnabled": "boolean", "watchEthereumAccountEnabled": "boolean", @@ -206,10 +261,10 @@ "cryptocurrencies": ["btc", "sol"], "snaps": "object", "jobs": "object", + "events": "object", "database": null, "lastUpdated": null, "databaseUnavailable": "boolean", - "notifications": "object", "interfaces": "object", "insights": "object", "names": "object", @@ -220,6 +275,7 @@ "isProfileSyncingUpdateLoading": "boolean", "hasAccountSyncingSyncedAtLeastOnce": "boolean", "isAccountSyncingReadyToBeDispatched": "boolean", + "isAccountSyncingInProgress": "boolean", "subscriptionAccountsSeen": "object", "isMetamaskNotificationsFeatureSeen": "boolean", "isNotificationServicesEnabled": "boolean", @@ -232,6 +288,8 @@ "isCheckingAccountsPresence": "boolean", "queuedRequestCount": 0, "fcmToken": "string", + "remoteFeatureFlags": {}, + "cacheTimestamp": "number", "accounts": "object", "accountsByChainId": "object", "marketData": "object", @@ -274,13 +332,13 @@ }, "bridgeState": { "bridgeFeatureFlags": { - "extensionConfig": "object", - "extensionSupport": "boolean", - "srcNetworkAllowlist": { "0": "string", "1": "string", "2": "string" }, - "destNetworkAllowlist": { "0": "string", "1": "string", "2": "string" } + "extensionConfig": { + "refreshRate": "number", + "maxRefreshCount": "number", + "support": "boolean", + "chains": { "0x1": "object", "0xa4b1": "object", "0xe708": "object" } + } }, - "srcTokens": {}, - "srcTopAssets": {}, "destTokens": {}, "destTopAssets": {}, "quoteRequest": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index 07b292d33b3b..7482981d32d7 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -42,12 +42,6 @@ "trezorModel": null, "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", - "usedNetworks": { - "0x1": true, - "0xe708": true, - "0x5": true, - "0x539": true - }, "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { @@ -131,7 +125,6 @@ "useTokenDetection": false, "useCurrencyRateCheck": true, "useMultiAccountBalanceChecker": true, - "useRequestQueue": true, "isMultiAccountBalancesEnabled": "boolean", "showIncomingTransactions": "object" }, @@ -149,22 +142,17 @@ "BridgeController": { "bridgeState": { "bridgeFeatureFlags": { - "extensionSupport": "boolean", - "srcNetworkAllowlist": { - "0": "string", - "1": "string", - "2": "string" - }, - "destNetworkAllowlist": { - "0": "string", - "1": "string", - "2": "string" + "extensionConfig": { + "support": "boolean", + "chains": { + "0x1": "object", + "0xa": "object", + "0xe708": "object" + } } }, "destTokens": {}, - "destTopAssets": {}, - "srcTokens": {}, - "srcTopAssets": {} + "destTopAssets": {} } }, "SubjectMetadataController": { "subjectMetadata": "object" }, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index f997b89bcd28..c3b3f3634ac5 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -42,31 +42,8 @@ "trezorModel": null, "newPrivacyPolicyToastClickedOrClosed": "boolean", "newPrivacyPolicyToastShownDate": "number", - "usedNetworks": { - "0x1": true, - "0xe708": true, - "0x5": true, - "0x539": true - }, "snapsInstallPrivacyWarningShown": true }, - "BridgeController": { - "bridgeState": { - "bridgeFeatureFlags": { - "extensionSupport": "boolean", - "srcNetworkAllowlist": { - "0": "string", - "1": "string", - "2": "string" - }, - "destNetworkAllowlist": { - "0": "string", - "1": "string", - "2": "string" - } - } - } - }, "CurrencyController": { "currentCurrency": "usd", "currencyRates": { @@ -148,7 +125,6 @@ "useTokenDetection": false, "useCurrencyRateCheck": true, "useMultiAccountBalanceChecker": true, - "useRequestQueue": true, "isMultiAccountBalancesEnabled": "boolean", "showIncomingTransactions": "object" }, @@ -175,22 +151,17 @@ "BridgeController": { "bridgeState": { "bridgeFeatureFlags": { - "extensionSupport": "boolean", - "srcNetworkAllowlist": { - "0": "string", - "1": "string", - "2": "string" - }, - "destNetworkAllowlist": { - "0": "string", - "1": "string", - "2": "string" + "extensionConfig": { + "support": "boolean", + "chains": { + "0x1": "object", + "0xa": "object", + "0xe708": "object" + } } }, "destTokens": {}, - "destTopAssets": {}, - "srcTokens": {}, - "srcTopAssets": {} + "destTopAssets": {} } }, "TransactionController": { "transactions": "object" }, diff --git a/test/e2e/tests/metrics/swaps.spec.js b/test/e2e/tests/metrics/swaps.spec.js index 6b73e3f400b7..df2137b0157f 100644 --- a/test/e2e/tests/metrics/swaps.spec.js +++ b/test/e2e/tests/metrics/swaps.spec.js @@ -90,6 +90,7 @@ async function mockSegmentAndMetaswapRequests(mockServer) { ]; } +// TODO: (MM-PENDING) These tests are planned for deprecation as part of swaps testing revamp describe('Swap Eth for another Token @no-mmi', function () { it('Completes a Swap between ETH and DAI after changing initial rate', async function () { const { initialBalanceInHex } = genRandInitBal(); diff --git a/test/e2e/tests/multichain/aggregated-balances.spec.ts b/test/e2e/tests/multichain/aggregated-balances.spec.ts new file mode 100644 index 000000000000..5e99c2e909e7 --- /dev/null +++ b/test/e2e/tests/multichain/aggregated-balances.spec.ts @@ -0,0 +1,137 @@ +import { strict as assert } from 'assert'; +import { Suite } from 'mocha'; +import { Driver } from '../../webdriver/driver'; +import { withFixtures, defaultGanacheOptions } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { Ganache } from '../../seeder/ganache'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import SelectNetwork from '../../page-objects/pages/dialog/select-network'; +import HomePage from '../../page-objects/pages/home/homepage'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import SendTokenPage from '../../page-objects/pages/send/send-token-page'; + +const EXPECTED_MAINNET_BALANCE_USD = '$84,985.04'; +const EXPECTED_CURRENT_NETWORK_BALANCE_USD = '$42,492.52'; +const EXPECTED_SEPOLIA_BALANCE_NATIVE = '24.9956'; +const NETWORK_NAME_MAINNET = 'Ethereum Mainnet'; +const NETWORK_NAME_SEPOLIA = 'Sepolia'; +const SEPOLIA_NATIVE_TOKEN = 'SepoliaETH'; + +describe('Multichain Aggregated Balances', function (this: Suite) { + if (!process.env.PORTFOLIO_VIEW) { + return; + } + + it('shows correct aggregated balance when "Current Network" is selected', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withTokensControllerERC20() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract: SMART_CONTRACTS.HST, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + // Step 1: Log in and set up page objects + await loginWithBalanceValidation(driver, ganacheServer); + + const homepage = new HomePage(driver); + const headerNavbar = new HeaderNavbar(driver); + const selectNetworkDialog = new SelectNetwork(driver); + const settingsPage = new SettingsPage(driver); + const accountListPage = new AccountListPage(driver); + const assetListPage = new AssetListPage(driver); + const sendTokenPage = new SendTokenPage(driver); + + // Step 2: Switch to Ethereum Mainnet + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(NETWORK_NAME_MAINNET); + + // Step 3: Enable fiat balance display in settings + await headerNavbar.openSettingsPage(); + await settingsPage.toggleBalanceSetting(); + await settingsPage.exitSettings(); + + // Step 4: Verify main balance on homepage and account menu + await homepage.check_expectedBalanceIsDisplayed( + EXPECTED_MAINNET_BALANCE_USD, + 'usd', + ); + await headerNavbar.openAccountMenu(); + await accountListPage.check_accountValueAndSuffixDisplayed( + EXPECTED_MAINNET_BALANCE_USD, + ); + await accountListPage.closeAccountModal(); + + // Step 5: Verify balance in send flow + await homepage.closePopover(); + await homepage.startSendFlow(); + await sendTokenPage.checkAccountValueAndSuffix( + EXPECTED_MAINNET_BALANCE_USD, + ); + await sendTokenPage.clickCancelButton(); + + // Step 6: Check balance for "Current Network" in network filter + await assetListPage.openNetworksFilter(); + const networkFilterTotal = + await assetListPage.getCurrentNetworksOptionTotal(); + assert.equal(networkFilterTotal, EXPECTED_CURRENT_NETWORK_BALANCE_USD); + await assetListPage.clickCurrentNetworkOption(); + + // Step 7: Verify balance after selecting "Current Network" + await homepage.check_expectedBalanceIsDisplayed( + EXPECTED_CURRENT_NETWORK_BALANCE_USD, + 'usd', + ); + await headerNavbar.openAccountMenu(); + await accountListPage.check_accountValueAndSuffixDisplayed( + EXPECTED_CURRENT_NETWORK_BALANCE_USD, + ); + await accountListPage.closeAccountModal(); + + // Step 8: Verify balance in send flow after selecting "Current Network" + await homepage.startSendFlow(); + await sendTokenPage.checkAccountValueAndSuffix( + EXPECTED_CURRENT_NETWORK_BALANCE_USD, + ); + await sendTokenPage.clickCancelButton(); + + // Step 9: Switch to Sepolia test network + await headerNavbar.clickSwitchNetworkDropDown(); + await driver.clickElement('.toggle-button'); + await driver.clickElement({ text: NETWORK_NAME_SEPOLIA, tag: 'p' }); + + // Step 10: Verify native balance on Sepolia network + await homepage.check_expectedBalanceIsDisplayed( + EXPECTED_SEPOLIA_BALANCE_NATIVE, + SEPOLIA_NATIVE_TOKEN, + ); + await assetListPage.check_networkFilterText(NETWORK_NAME_SEPOLIA); + + // Step 11: Enable fiat display on testnets in settings + await headerNavbar.openSettingsPage(); + await settingsPage.clickAdvancedTab(); + await settingsPage.toggleShowFiatOnTestnets(); + await settingsPage.closeSettingsPage(); + + // Step 12: Verify USD balance on Sepolia network + await homepage.check_expectedBalanceIsDisplayed( + EXPECTED_CURRENT_NETWORK_BALANCE_USD, + 'usd', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/multichain/all-permissions-page.spec.js b/test/e2e/tests/multichain/all-permissions-page.spec.js deleted file mode 100644 index 5bb718bd8d20..000000000000 --- a/test/e2e/tests/multichain/all-permissions-page.spec.js +++ /dev/null @@ -1,98 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - WINDOW_TITLES, - connectToDapp, - logInWithBalanceValidation, - defaultGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Permissions Page', function () { - it('should show connected site permissions when a single dapp is connected', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await connectToDapp(driver); - - // close test dapp window to avoid future confusion - const windowHandles = await driver.getAllWindowHandles(); - await driver.closeWindowHandle(windowHandles[1]); - // disconnect dapp in fullscreen view - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - const connectedDapp = await driver.isElementPresent({ - text: '127.0.0.1:8080', - tag: 'p', - }); - assert.ok(connectedDapp, 'Account connected to Dapp1'); - }, - ); - }); - - it('should show all permissions listed when experimental settings toggle is off', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await connectToDapp(driver); - - // close test dapp window to avoid future confusion - const windowHandles = await driver.getAllWindowHandles(); - await driver.closeWindowHandle(windowHandles[1]); - // disconnect dapp in fullscreen view - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ - text: 'Experimental', - tag: 'div', - }); - - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"] label', - ); - await driver.clickElement( - '.settings-page__header__title-container__close-button', - ); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - const connectedDapp = await driver.isElementPresent({ - text: '127.0.0.1:8080', - tag: 'p', - }); - assert.ok(connectedDapp, 'Account connected to Dapp1'); - }, - ); - }); -}); diff --git a/test/e2e/tests/multichain/all-permissions-page.spec.ts b/test/e2e/tests/multichain/all-permissions-page.spec.ts new file mode 100644 index 000000000000..4c481520ce81 --- /dev/null +++ b/test/e2e/tests/multichain/all-permissions-page.spec.ts @@ -0,0 +1,76 @@ +import { DEFAULT_FIXTURE_ACCOUNT, DAPP_HOST_ADDRESS } from '../../constants'; +import { withFixtures, WINDOW_TITLES } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import ExperimentalSettings from '../../page-objects/pages/settings/experimental-settings'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import Homepage from '../../page-objects/pages/home/homepage'; +import PermissionListPage from '../../page-objects/pages/permission/permission-list-page'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Permissions Page', function () { + it('should show connected site permissions when a single dapp is connected', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithoutBalanceValidation(driver); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.connectAccount({ + publicAddress: DEFAULT_FIXTURE_ACCOUNT, + }); + + // switch to extension window and check the site permissions + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const homepage = new Homepage(driver); + await homepage.check_pageIsLoaded(); + await homepage.check_expectedBalanceIsDisplayed(); + await homepage.headerNavbar.openPermissionsPage(); + + const permissionListPage = new PermissionListPage(driver); + await permissionListPage.check_pageIsLoaded(); + await permissionListPage.check_connectedToSite(DAPP_HOST_ADDRESS); + }, + ); + }); + + it('should show all permissions listed when experimental settings toggle is off', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithoutBalanceValidation(driver); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openSettingsPage(); + + // go to experimental settings page and toggle request queue + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + await settingsPage.closeSettingsPage(); + + // go to homepage and check site permissions + await new Homepage(driver).check_pageIsLoaded(); + await headerNavbar.openPermissionsPage(); + const permissionListPage = new PermissionListPage(driver); + await permissionListPage.check_pageIsLoaded(); + await permissionListPage.check_connectedToSite(DAPP_HOST_ADDRESS); + }, + ); + }); +}); diff --git a/test/e2e/tests/multichain/asset-list.spec.ts b/test/e2e/tests/multichain/asset-list.spec.ts new file mode 100644 index 000000000000..b206ef1b4e56 --- /dev/null +++ b/test/e2e/tests/multichain/asset-list.spec.ts @@ -0,0 +1,205 @@ +import { strict as assert } from 'assert'; +import { Suite } from 'mocha'; +import { Driver } from '../../webdriver/driver'; +import { withFixtures, defaultGanacheOptions } from '../../helpers'; +import { Ganache } from '../../seeder/ganache'; +import FixtureBuilder from '../../fixture-builder'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import SelectNetwork from '../../page-objects/pages/dialog/select-network'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import SendTokenPage from '../../page-objects/pages/send/send-token-page'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; + +const NETWORK_NAME_MAINNET = 'Ethereum Mainnet'; +const LINEA_NAME_MAINNET = 'Linea Mainnet'; +const POLYGON_NAME_MAINNET = 'Polygon'; +const BALANCE_AMOUNT = '24.9956'; + +function buildFixtures(title: string, chainId: number = 1337) { + return { + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withNetworkControllerOnPolygon() + .withTokensControllerERC20({ chainId }) + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract: SMART_CONTRACTS.HST, + title, + }; +} + +describe('Multichain Asset List', function (this: Suite) { + if (!process.env.PORTFOLIO_VIEW) { + return; + } + + it('persists the preferred asset list preference when changing networks', async function () { + await withFixtures( + buildFixtures(this.test?.fullTitle() as string), + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + const headerNavbar = new HeaderNavbar(driver); + const selectNetworkDialog = new SelectNetwork(driver); + const assetListPage = new AssetListPage(driver); + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(NETWORK_NAME_MAINNET); + await assetListPage.check_tokenItemNumber(3); + await assetListPage.openNetworksFilter(); + await assetListPage.clickCurrentNetworkOption(); + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(LINEA_NAME_MAINNET); + await assetListPage.waitUntilFilterLabelIs(LINEA_NAME_MAINNET); + await assetListPage.check_tokenItemNumber(1); + assert.equal( + await assetListPage.getNetworksFilterLabel(), + LINEA_NAME_MAINNET, + ); + }, + ); + }); + it('allows clicking into the asset details page of native token on another network', async function () { + await withFixtures( + buildFixtures(this.test?.fullTitle() as string), + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + const headerNavbar = new HeaderNavbar(driver); + const selectNetworkDialog = new SelectNetwork(driver); + const assetListPage = new AssetListPage(driver); + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(NETWORK_NAME_MAINNET); + await assetListPage.check_tokenItemNumber(3); + await driver.clickElement('.multichain-token-list-item'); + const coinOverviewElement = await driver.findElement( + '[data-testid="coin-overview-buy"]', + ); + const multichainTokenListButton = await driver.findElement( + '[data-testid="multichain-token-list-button"]', + ); + assert.ok(coinOverviewElement, 'coin-overview-buy is present'); + assert.ok( + multichainTokenListButton, + 'multichain-token-list-button is present', + ); + }, + ); + }); + it('switches networks when clicking on send for a token on another network', async function () { + await withFixtures( + buildFixtures(this.test?.fullTitle() as string, 137), + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + const headerNavbar = new HeaderNavbar(driver); + const selectNetworkDialog = new SelectNetwork(driver); + const assetListPage = new AssetListPage(driver); + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(NETWORK_NAME_MAINNET); + const sendPage = new SendTokenPage(driver); + await assetListPage.check_tokenItemNumber(4); + await assetListPage.clickOnAsset('TST'); + await driver.clickElement('[data-testid="eth-overview-send"]'); + await sendPage.check_networkChange(POLYGON_NAME_MAINNET); + await sendPage.check_pageIsLoaded(); + await sendPage.fillRecipient( + '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + ); + await sendPage.clickAssetPickerButton(); + const assetPickerItems = await sendPage.getAssetPickerItems(); + assert.equal( + assetPickerItems.length, + 2, + 'Two assets should be shown in the asset picker', + ); + }, + ); + }); + it('switches networks when clicking on swap for a token on another network', async function () { + await withFixtures( + buildFixtures(this.test?.fullTitle() as string, 137), + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + const headerNavbar = new HeaderNavbar(driver); + const selectNetworkDialog = new SelectNetwork(driver); + const assetListPage = new AssetListPage(driver); + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(NETWORK_NAME_MAINNET); + await assetListPage.check_tokenItemNumber(4); + await assetListPage.clickOnAsset('TST'); + await driver.clickElement('.mm-box > button:nth-of-type(3)'); + const toastTextElement = await driver.findElement('.toast-text'); + const toastText = await toastTextElement.getText(); + assert.equal( + toastText, + `You're now using ${POLYGON_NAME_MAINNET}`, + 'Toast text is correct', + ); + }, + ); + }); + it('shows correct asset and balance when swapping on a different chain', async function () { + await withFixtures( + buildFixtures(this.test?.fullTitle() as string), + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + const headerNavbar = new HeaderNavbar(driver); + const assetListPage = new AssetListPage(driver); + const selectNetworkDialog = new SelectNetwork(driver); + await headerNavbar.clickSwitchNetworkDropDown(); + await selectNetworkDialog.selectNetworkName(LINEA_NAME_MAINNET); + await assetListPage.check_tokenItemNumber(3); + await assetListPage.clickOnAsset('Ethereum'); + + const swapButton = await driver.findElement( + '[data-testid="token-overview-button-swap"]', + ); + await swapButton.click(); + const toastTextElement = await driver.findElement('.toast-text'); + const toastText = await toastTextElement.getText(); + assert.equal( + toastText, + `You're now using Ethereum Mainnet`, + 'Toast text is correct', + ); + const balanceMessageElement = await driver.findElement( + '.prepare-swap-page__balance-message', + ); + const balanceMessage = await balanceMessageElement.getText(); + assert.equal( + balanceMessage.replace('Max', '').trim(), + `Balance: ${BALANCE_AMOUNT}`, + 'Balance message is correct', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/multichain/asset-picker-send.spec.ts b/test/e2e/tests/multichain/asset-picker-send.spec.ts index a071bec9426d..a6d8e193d537 100644 --- a/test/e2e/tests/multichain/asset-picker-send.spec.ts +++ b/test/e2e/tests/multichain/asset-picker-send.spec.ts @@ -88,7 +88,7 @@ describe('AssetPickerSendFlow @no-mmi', function () { await searchInputField.sendKeys('CHZ'); // check that CHZ is disabled - const [, tkn] = await driver.findElements( + const [tkn] = await driver.findElements( '[data-testid="multichain-token-list-button"]', ); diff --git a/test/e2e/tests/multichain/permission-page.spec.js b/test/e2e/tests/multichain/permission-page.spec.js deleted file mode 100644 index 5ae3f71b6046..000000000000 --- a/test/e2e/tests/multichain/permission-page.spec.js +++ /dev/null @@ -1,88 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - WINDOW_TITLES, - connectToDapp, - logInWithBalanceValidation, - defaultGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Permissions Page', function () { - it('should show connected site permissions when a single dapp is connected', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await connectToDapp(driver); - - // close test dapp window to avoid future confusion - const windowHandles = await driver.getAllWindowHandles(); - await driver.closeWindowHandle(windowHandles[1]); - // disconnect dapp in fullscreen view - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - const connectedDapp = await driver.isElementPresent({ - text: '127.0.0.1:8080', - tag: 'p', - }); - assert.ok(connectedDapp, 'Account connected to Dapp1'); - }, - ); - }); - - it('should redirect users to connections page when users click on connected permission', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - ganacheOptions: defaultGanacheOptions, - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await connectToDapp(driver); - - // close test dapp window to avoid future confusion - const windowHandles = await driver.getAllWindowHandles(); - await driver.closeWindowHandle(windowHandles[1]); - // disconnect dapp in fullscreen view - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - await driver.clickElement({ - text: '127.0.0.1:8080', - tag: 'p', - }); - await driver.clickElement('[data-testid ="connections-page"]'); - const connectionsPage = await driver.isElementPresent({ - text: '127.0.0.1:8080', - tag: 'span', - }); - assert.ok(connectionsPage, 'Connections Page is defined'); - }, - ); - }); -}); diff --git a/test/e2e/tests/multichain/permission-page.spec.ts b/test/e2e/tests/multichain/permission-page.spec.ts new file mode 100644 index 000000000000..a85e4f9ba1a4 --- /dev/null +++ b/test/e2e/tests/multichain/permission-page.spec.ts @@ -0,0 +1,33 @@ +import { DAPP_HOST_ADDRESS } from '../../constants'; +import { withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import PermissionListPage from '../../page-objects/pages/permission/permission-list-page'; +import SitePermissionPage from '../../page-objects/pages/permission/site-permission-page'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Permissions Page', function () { + it('should redirect users to connections page when users click on connected permission', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithoutBalanceValidation(driver); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openPermissionsPage(); + const permissionListPage = new PermissionListPage(driver); + await permissionListPage.check_pageIsLoaded(); + + await permissionListPage.openPermissionPageForSite(DAPP_HOST_ADDRESS); + await new SitePermissionPage(driver).check_pageIsLoaded( + DAPP_HOST_ADDRESS, + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/network/custom-rpc-history.spec.js b/test/e2e/tests/network/custom-rpc-history.spec.js index 6e92f532698f..2d1a52994d10 100644 --- a/test/e2e/tests/network/custom-rpc-history.spec.js +++ b/test/e2e/tests/network/custom-rpc-history.spec.js @@ -264,6 +264,7 @@ describe('Custom RPC history', function () { const customRpcs = await driver.findElements({ text: 'Localhost 8545', tag: 'p', + css: '.multichain-network-list-item__tooltip', }); // click Mainnet to dismiss network dropdown diff --git a/test/e2e/tests/network/multi-rpc.spec.ts b/test/e2e/tests/network/multi-rpc.spec.ts index f5c40259a33b..ebf8604a76ea 100644 --- a/test/e2e/tests/network/multi-rpc.spec.ts +++ b/test/e2e/tests/network/multi-rpc.spec.ts @@ -10,7 +10,7 @@ import { } from '../../helpers/mock-server'; import EditNetworkModal from '../../page-objects/pages/dialog/edit-network'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; import OnboardingPrivacySettingsPage from '../../page-objects/pages/onboarding/onboarding-privacy-settings-page'; import SelectNetwork from '../../page-objects/pages/dialog/select-network'; diff --git a/test/e2e/tests/network/network-error.spec.js b/test/e2e/tests/network/network-error.spec.js deleted file mode 100644 index 61842f482151..000000000000 --- a/test/e2e/tests/network/network-error.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - logInWithBalanceValidation, - openActionMenuAndStartSendFlow, - generateGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); -const { GAS_API_BASE_URL } = require('../../../../shared/constants/swaps'); - -describe('Gas API fallback', function () { - async function mockGasApiDown(mockServer) { - await mockServer - .forGet(`${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`) - .always() - .thenCallback(() => { - return { - statusCode: 200, - json: { - low: { - minWaitTimeEstimate: 180000, - maxWaitTimeEstimate: 300000, - suggestedMaxPriorityFeePerGas: '3', - suggestedMaxFeePerGas: '53', - }, - medium: { - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 60000, - suggestedMaxPriorityFeePerGas: '7', - suggestedMaxFeePerGas: '70', - }, - high: { - minWaitTimeEstimate: 0, - maxWaitTimeEstimate: 15000, - suggestedMaxPriorityFeePerGas: '10', - suggestedMaxFeePerGas: '100', - }, - estimatedBaseFee: '50', - networkCongestion: 0.9, - latestPriorityFeeRange: ['1', '20'], - historicalPriorityFeeRange: ['2', '125'], - historicalBaseFeeRange: ['50', '100'], - priorityFeeTrend: 'up', - baseFeeTrend: 'down', - }, - }; - }); - } - - it('network error message is displayed if network is congested', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - testSpecificMock: mockGasApiDown, - ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await openActionMenuAndStartSendFlow(driver); - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - '0x2f318C334780961FB129D2a6c30D0763d9a5C970', - ); - - const inputAmount = await driver.findElement('input[placeholder="0"]'); - await inputAmount.fill('1'); - - await driver.clickElement({ text: 'Continue', tag: 'button' }); - - const error = await driver.isElementPresent( - '[data-testid="network-busy-tooltip"]', - ); - - assert.equal(error, true, 'Network error is present'); - }, - ); - }); -}); diff --git a/test/e2e/tests/network/switch-network.spec.ts b/test/e2e/tests/network/switch-network.spec.ts index b320e095cc69..78862ecb30b5 100644 --- a/test/e2e/tests/network/switch-network.spec.ts +++ b/test/e2e/tests/network/switch-network.spec.ts @@ -4,7 +4,7 @@ import { withFixtures, defaultGanacheOptions } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import { Ganache } from '../../seeder/ganache'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import { switchToNetworkFlow, diff --git a/test/e2e/tests/network/update-network.spec.ts b/test/e2e/tests/network/update-network.spec.ts index 3f0b9882688f..f7861f46ab99 100644 --- a/test/e2e/tests/network/update-network.spec.ts +++ b/test/e2e/tests/network/update-network.spec.ts @@ -23,7 +23,7 @@ const selectors = { deleteButton: { text: 'Delete', tag: 'button' }, cancelButton: { text: 'Cancel', tag: 'button' }, saveButton: { text: 'Save', tag: 'button' }, - updatedNetworkDropDown: { tag: 'span', text: 'Update Network' }, + updatedNetworkDropDown: { tag: 'p', text: 'Update Network' }, errorMessageInvalidUrl: { text: 'URLs require the appropriate HTTP/HTTPS prefix.', }, diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts index b7069447fd45..c6f65d58dbb7 100644 --- a/test/e2e/tests/notifications/mocks.ts +++ b/test/e2e/tests/notifications/mocks.ts @@ -1,14 +1,11 @@ import { Mockttp, RequestRuleBuilder } from 'mockttp'; -import { AuthenticationController } from '@metamask/profile-sync-controller'; import { NotificationServicesController, NotificationServicesPushController, } from '@metamask/notification-services-controller'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; -import { UserStorageMockttpController } from '../../helpers/user-storage/userStorageMockttpController'; -import { accountsSyncMockResponse } from './account-syncing/mockData'; +import { UserStorageMockttpController } from '../../helpers/identity/user-storage/userStorageMockttpController'; -const AuthMocks = AuthenticationController.Mocks; const NotificationMocks = NotificationServicesController.Mocks; const PushMocks = NotificationServicesPushController.Mocks; @@ -19,7 +16,7 @@ type MockResponse = { }; /** - * E2E mock setup for notification APIs (Auth, UserStorage, Notifications, Push Notifications, Profile syncing) + * E2E mock setup for notification APIs (Notifications, Push Notifications) * * @param server - server obj used to mock our endpoints * @param userStorageMockttpControllerInstance - optional instance of UserStorageMockttpController, useful if you need persisted user storage between tests @@ -28,32 +25,7 @@ export async function mockNotificationServices( server: Mockttp, userStorageMockttpControllerInstance: UserStorageMockttpController = new UserStorageMockttpController(), ) { - // Auth - mockAPICall(server, AuthMocks.getMockAuthNonceResponse()); - mockAPICall(server, AuthMocks.getMockAuthLoginResponse()); - mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse()); - // Storage - if ( - !userStorageMockttpControllerInstance?.paths.get( - USER_STORAGE_FEATURE_NAMES.accounts, - ) - ) { - userStorageMockttpControllerInstance.setupPath( - USER_STORAGE_FEATURE_NAMES.accounts, - server, - ); - } - if ( - !userStorageMockttpControllerInstance?.paths.get( - USER_STORAGE_FEATURE_NAMES.networks, - ) - ) { - userStorageMockttpControllerInstance.setupPath( - USER_STORAGE_FEATURE_NAMES.networks, - server, - ); - } if ( !userStorageMockttpControllerInstance?.paths.get( USER_STORAGE_FEATURE_NAMES.notifications, @@ -106,63 +78,3 @@ function mockAPICall(server: Mockttp, response: MockResponse) { json: response.response, })); } - -type MockInfuraAndAccountSyncOptions = { - accountsToMock?: string[]; - accountsSyncResponse?: typeof accountsSyncMockResponse; -}; - -const MOCK_ETH_BALANCE = '0xde0b6b3a7640000'; -const INFURA_URL = - 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; - -/** - * Sets up mock responses for Infura balance checks and account syncing - * - * @param mockServer - The Mockttp server instance - * @param userStorageMockttpController - Controller for user storage mocks - * @param options - Configuration options for mocking - */ -export async function mockInfuraAndAccountSync( - mockServer: Mockttp, - userStorageMockttpController: UserStorageMockttpController, - options: MockInfuraAndAccountSyncOptions = {}, -): Promise { - const accounts = options.accountsToMock ?? []; - - // Set up User Storage / Account Sync mock - userStorageMockttpController.setupPath( - USER_STORAGE_FEATURE_NAMES.accounts, - mockServer, - ); - - userStorageMockttpController.setupPath( - USER_STORAGE_FEATURE_NAMES.accounts, - mockServer, - { - getResponse: options.accountsSyncResponse ?? undefined, - }, - ); - - // Account Balances - if (accounts.length > 0) { - accounts.forEach((account) => { - mockServer - .forPost(INFURA_URL) - .withJsonBodyIncluding({ - method: 'eth_getBalance', - params: [account.toLowerCase()], - }) - .thenCallback(() => ({ - statusCode: 200, - json: { - jsonrpc: '2.0', - id: '1111111111111111', - result: MOCK_ETH_BALANCE, - }, - })); - }); - } - - mockNotificationServices(mockServer, userStorageMockttpController); -} diff --git a/test/e2e/tests/onboarding/onboarding.spec.ts b/test/e2e/tests/onboarding/onboarding.spec.ts index 979e416f5090..b55ddc193569 100644 --- a/test/e2e/tests/onboarding/onboarding.spec.ts +++ b/test/e2e/tests/onboarding/onboarding.spec.ts @@ -7,7 +7,7 @@ import { import { Driver } from '../../webdriver/driver'; import FixtureBuilder from '../../fixture-builder'; import { FirstTimeFlowType } from '../../../../shared/constants/onboarding'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; import OnboardingMetricsPage from '../../page-objects/pages/onboarding/onboarding-metrics-page'; import OnboardingPasswordPage from '../../page-objects/pages/onboarding/onboarding-password-page'; diff --git a/test/e2e/tests/petnames/petnames-helpers.js b/test/e2e/tests/petnames/petnames-helpers.js index 03747caba4be..da823fd67323 100644 --- a/test/e2e/tests/petnames/petnames-helpers.js +++ b/test/e2e/tests/petnames/petnames-helpers.js @@ -1,5 +1,5 @@ -async function rejectSignatureOrTransactionRequest(driver) { - await driver.clickElement({ text: 'Reject', tag: 'button' }); +async function rejectRedesignedSignatureOrTransactionRequest(driver) { + await driver.clickElement({ text: 'Cancel', tag: 'button' }); await driver.delay(3000); } @@ -48,7 +48,7 @@ async function saveName(driver, value, name, proposedName) { } module.exports = { - rejectSignatureOrTransactionRequest, + rejectRedesignedSignatureOrTransactionRequest, focusTestDapp, expectName, clickName, diff --git a/test/e2e/tests/petnames/petnames-signatures.spec.js b/test/e2e/tests/petnames/petnames-signatures.spec.js index 6c472901057e..770002a6b2f2 100644 --- a/test/e2e/tests/petnames/petnames-signatures.spec.js +++ b/test/e2e/tests/petnames/petnames-signatures.spec.js @@ -2,18 +2,18 @@ const { openDapp, switchToNotificationWindow, withFixtures, - tempToggleSettingRedesignedConfirmations, unlockWallet, defaultGanacheOptions, + WINDOW_TITLES, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('../../snaps/enums'); const { expectName, focusTestDapp, - rejectSignatureOrTransactionRequest, saveName, clickName, + rejectRedesignedSignatureOrTransactionRequest, } = require('./petnames-helpers'); const SIGNATURE_TYPE = { @@ -65,19 +65,6 @@ async function createSignatureRequest(driver, type) { await driver.delay(3000); } -async function showThirdPartyDetails(driver) { - await driver.clickElement( - '.signature-request-content__verify-contract-details', - ); -} - -async function closeThirdPartyDetails(driver) { - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); -} - async function expectProposedNames(driver, value, options) { await clickName(driver, value); await driver.clickElement('.form-combo-field'); @@ -95,7 +82,7 @@ async function expectProposedNames(driver, value, options) { } } -describe('Petnames - Signatures', function () { +describe('Petnames - Signatures', function () { it('can save names for addresses in type 3 signatures', async function () { await withFixtures( { @@ -109,25 +96,21 @@ describe('Petnames - Signatures', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V3); - await switchToNotificationWindow(driver, 3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await expectName(driver, '0xCD2a3...DD826', false); await expectName(driver, '0xbBbBB...bBBbB', false); await saveName(driver, '0xCD2a3...DD826', undefined, 'test.lens'); await saveName(driver, '0xbBbBB...bBBbB', undefined, 'test2.lens'); - await showThirdPartyDetails(driver); await expectName(driver, '0xCcCCc...ccccC', false); await saveName(driver, '0xCcCCc...ccccC', 'Custom Name'); - await closeThirdPartyDetails(driver); - await rejectSignatureOrTransactionRequest(driver); + await rejectRedesignedSignatureOrTransactionRequest(driver); await focusTestDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V3); - await switchToNotificationWindow(driver, 3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await expectName(driver, 'test.lens', true); await expectName(driver, 'test2.lens', true); - await showThirdPartyDetails(driver); await expectName(driver, 'Custom Name', true); }, ); @@ -146,10 +129,9 @@ describe('Petnames - Signatures', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4); - await switchToNotificationWindow(driver, 3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await expectName(driver, '0xCD2a3...DD826', false); await expectName(driver, '0xDeaDb...DbeeF', false); await expectName(driver, '0xbBbBB...bBBbB', false); @@ -157,17 +139,14 @@ describe('Petnames - Signatures', function () { await expectName(driver, '0xB0B0b...00000', false); await saveName(driver, '0xCD2a3...DD826', undefined, 'test.lens'); await saveName(driver, '0xB0Bda...bEa57', undefined, 'Test Token 2'); - await showThirdPartyDetails(driver); await expectName(driver, '0xCcCCc...ccccC', false); await saveName(driver, '0xCcCCc...ccccC', 'Custom Name'); - await closeThirdPartyDetails(driver); - await rejectSignatureOrTransactionRequest(driver); + await rejectRedesignedSignatureOrTransactionRequest(driver); await focusTestDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4); - await switchToNotificationWindow(driver, 3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await expectName(driver, 'test.lens', true); - await expectName(driver, 'Test Token 2', true); - await showThirdPartyDetails(driver); + await expectName(driver, 'Test Toke...', true); await expectName(driver, 'Custom Name', true); }, ); @@ -186,13 +165,12 @@ describe('Petnames - Signatures', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); await openTestSnaps(driver); await installNameLookupSnap(driver); await focusTestDapp(driver); await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4); - await switchToNotificationWindow(driver, 4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await expectProposedNames(driver, '0xCD2a3...DD826', [ ['test.lens', 'Lens Protocol'], ['cd2.1337.test.domain', 'Name Lookup Example Snap'], diff --git a/test/e2e/tests/petnames/petnames-transactions.spec.js b/test/e2e/tests/petnames/petnames-transactions.spec.js index 55c295c51c3a..e6ddf0229e99 100644 --- a/test/e2e/tests/petnames/petnames-transactions.spec.js +++ b/test/e2e/tests/petnames/petnames-transactions.spec.js @@ -5,14 +5,13 @@ const { unlockWallet, defaultGanacheOptions, openActionMenuAndStartSendFlow, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { expectName, focusTestDapp, - rejectSignatureOrTransactionRequest, saveName, + rejectRedesignedSignatureOrTransactionRequest, } = require('./petnames-helpers'); async function createDappSendTransaction(driver) { @@ -51,8 +50,6 @@ describe('Petnames - Transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openDapp(driver); await createDappSendTransaction(driver); await switchToNotificationWindow(driver, 3); @@ -65,7 +62,7 @@ describe('Petnames - Transactions', function () { CUSTOM_NAME_MOCK, undefined, ); - await rejectSignatureOrTransactionRequest(driver); + await rejectRedesignedSignatureOrTransactionRequest(driver); await focusTestDapp(driver); await createDappSendTransaction(driver); await switchToNotificationWindow(driver, 3); @@ -73,7 +70,7 @@ describe('Petnames - Transactions', function () { // Test proposed name. await saveName(driver, CUSTOM_NAME_MOCK, undefined, PROPOSED_NAME_MOCK); - await rejectSignatureOrTransactionRequest(driver); + await rejectRedesignedSignatureOrTransactionRequest(driver); await focusTestDapp(driver); await createDappSendTransaction(driver); await switchToNotificationWindow(driver, 3); @@ -98,7 +95,6 @@ describe('Petnames - Transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); await createWalletSendTransaction(driver, ADDRESS_MOCK); await expectName(driver, ABBREVIATED_ADDRESS_MOCK, false); @@ -110,13 +106,13 @@ describe('Petnames - Transactions', function () { CUSTOM_NAME_MOCK, undefined, ); - await rejectSignatureOrTransactionRequest(driver); + await rejectRedesignedSignatureOrTransactionRequest(driver); await createWalletSendTransaction(driver, ADDRESS_MOCK); await expectName(driver, CUSTOM_NAME_MOCK, true); // Test proposed name. await saveName(driver, CUSTOM_NAME_MOCK, undefined, PROPOSED_NAME_MOCK); - await rejectSignatureOrTransactionRequest(driver); + await rejectRedesignedSignatureOrTransactionRequest(driver); await createWalletSendTransaction(driver, ADDRESS_MOCK); await expectName(driver, PROPOSED_NAME_MOCK, true); }, diff --git a/test/e2e/tests/phishing-controller/mocks.js b/test/e2e/tests/phishing-controller/mocks.js index fe11118c6fd2..3165847740bf 100644 --- a/test/e2e/tests/phishing-controller/mocks.js +++ b/test/e2e/tests/phishing-controller/mocks.js @@ -10,7 +10,9 @@ const { const lastUpdated = 1; const defaultHotlist = { data: [] }; const defaultC2DomainBlocklist = { - recentlyAdded: [], + recentlyAdded: [ + '33c8e026e76cea2df82322428554c932961cd80080fa379454350d7f13371f36', // hash for malicious.localhost + ], recentlyRemoved: [], lastFetchedAt: '2024-08-27T15:30:45Z', }; @@ -95,15 +97,12 @@ async function setupPhishingDetectionMocks( }; }); - await mockServer - .forGet(C2_DOMAIN_BLOCKLIST_URL) - .withQuery({ timestamp: '2024-08-27T15:30:45Z' }) - .thenCallback(() => { - return { - statusCode: 200, - json: defaultC2DomainBlocklist, - }; - }); + await mockServer.forGet(C2_DOMAIN_BLOCKLIST_URL).thenCallback(() => { + return { + statusCode: 200, + json: defaultC2DomainBlocklist, + }; + }); await mockServer .forGet('https://github.com/MetaMask/eth-phishing-detect/issues/new') diff --git a/test/e2e/tests/phishing-controller/phishing-detection.spec.js b/test/e2e/tests/phishing-controller/phishing-detection.spec.js index ad199cea1e70..1fabd8f901bc 100644 --- a/test/e2e/tests/phishing-controller/phishing-detection.spec.js +++ b/test/e2e/tests/phishing-controller/phishing-detection.spec.js @@ -9,6 +9,7 @@ const { openDapp, unlockWallet, WINDOW_TITLES, + createWebSocketConnection, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { @@ -307,10 +308,82 @@ describe('Phishing Detection', function () { text: 'Back to safety', }); + await driver.waitForUrl({ + url: `https://portfolio.metamask.io/?metamaskEntry=phishing_page_portfolio_button`, + }); + }, + ); + }); + + it('should block a website that makes a websocket connection to a malicious command and control server', async function () { + const testPageURL = 'http://localhost:8080'; + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + testSpecificMock: async (mockServer) => { + await mockServer.forAnyWebSocket().thenEcho(); + await setupPhishingDetectionMocks(mockServer, { + blockProvider: BlockProvider.MetaMask, + }); + }, + dapp: true, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await driver.openNewPage(testPageURL); + + await createWebSocketConnection(driver, 'malicious.localhost'); + + await driver.switchToWindowWithTitle( + 'MetaMask Phishing Detection', + 10000, + ); + + await driver.waitForSelector({ + testId: 'unsafe-continue-loaded', + }); + + await driver.clickElement({ + text: 'Back to safety', + }); + + await driver.waitForUrl({ + url: `https://portfolio.metamask.io/?metamaskEntry=phishing_page_portfolio_button`, + }); + }, + ); + }); + + it('should not block a website that makes a safe WebSocket connection', async function () { + const testPageURL = 'http://localhost:8080/'; + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + testSpecificMock: async (mockServer) => { + await mockServer.forAnyWebSocket().thenEcho(); + await setupPhishingDetectionMocks(mockServer, { + blockProvider: BlockProvider.MetaMask, + }); + }, + dapp: true, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await driver.openNewPage(testPageURL); + + await createWebSocketConnection(driver, 'safe.localhost'); + + await driver.wait(until.titleIs(WINDOW_TITLES.TestDApp), 10000); + const currentUrl = await driver.getCurrentUrl(); - const expectedPortfolioUrl = `https://portfolio.metamask.io/?metamaskEntry=phishing_page_portfolio_button`; - assert.equal(currentUrl, expectedPortfolioUrl); + assert.equal(currentUrl, testPageURL); }, ); }); diff --git a/test/e2e/tests/portfolio/portfolio-site.spec.js b/test/e2e/tests/portfolio/portfolio-site.spec.js deleted file mode 100644 index cba9c0452522..000000000000 --- a/test/e2e/tests/portfolio/portfolio-site.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -const { - withFixtures, - unlockWallet, - defaultGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); -const { emptyHtmlPage } = require('../../mock-e2e'); - -describe('Portfolio site', function () { - async function mockPortfolioSite(mockServer) { - return await mockServer - .forGet('https://portfolio.metamask.io/') - .withQuery({ - metamaskEntry: 'ext_portfolio_button', - metametricsId: 'null', - }) - .thenCallback(() => { - return { - statusCode: 200, - body: emptyHtmlPage(), - }; - }); - } - - it('should link to the portfolio site @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockPortfolioSite, - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Click Portfolio site - await driver.clickElement('[data-testid="portfolio-link"]'); - await driver.waitUntilXWindowHandles(2); - const windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); - - // Verify site - await driver.waitForUrl({ - url: 'https://portfolio.metamask.io/?metamaskEntry=ext_portfolio_button&metametricsId=null&metricsEnabled=false&marketingEnabled=false', - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/portfolio/portfolio-site.spec.ts b/test/e2e/tests/portfolio/portfolio-site.spec.ts new file mode 100644 index 000000000000..d7952b5f1baa --- /dev/null +++ b/test/e2e/tests/portfolio/portfolio-site.spec.ts @@ -0,0 +1,50 @@ +import { MockttpServer } from 'mockttp'; +import { withFixtures } from '../../helpers'; +import { PORTFOLIO_PAGE_TITLE, MOCK_META_METRICS_ID } from '../../constants'; +import FixtureBuilder from '../../fixture-builder'; +import { emptyHtmlPage } from '../../mock-e2e'; +import HomePage from '../../page-objects/pages/home/homepage'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Portfolio site', function () { + async function mockPortfolioSite(mockServer: MockttpServer) { + return await mockServer + .forGet('https://portfolio.metamask.io/') + .withQuery({ + metamaskEntry: 'ext_portfolio_button', + metametricsId: 'null', + }) + .thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); + } + + it('should link to the portfolio site @no-mmi', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: MOCK_META_METRICS_ID, + participateInMetaMetrics: true, + }) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockPortfolioSite, + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + await new HomePage(driver).openPortfolioPage(); + await driver.switchToWindowWithTitle(PORTFOLIO_PAGE_TITLE); + + // Verify site + await driver.waitForUrl({ + url: `https://portfolio.metamask.io/?metamaskEntry=ext_portfolio_button&metametricsId=${MOCK_META_METRICS_ID}&metricsEnabled=true&marketingEnabled=false`, + }); + }, + ); + }); +}); diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js index e35c23f22c49..6723c93119ca 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js @@ -195,8 +195,6 @@ describe('PPOM Blockaid Alert - Malicious Contract interaction @no-mmi', functio .withPreferencesController({ securityAlertsEnabled: true, preferences: { - redesignedTransactionsEnabled: true, - redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, }, }) diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js index 5368c2617f13..d3d0da9020cd 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js @@ -6,7 +6,6 @@ const { sendScreenToConfirmScreen, logInWithBalanceValidation, WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { mockMultiNetworkBalancePolling, @@ -209,8 +208,6 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { async ({ driver }) => { await logInWithBalanceValidation(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await sendScreenToConfirmScreen( driver, '0xB8c77482e45F1F44dE1745F52C74426C631bDD52', @@ -219,7 +216,7 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { const expectedTitle = 'Be careful'; const bannerAlert = await driver.findElement({ - css: bannerAlertSelector, + css: '[data-testid="confirm-banner-alert"]', text: expectedTitle, }); diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js index ac17614bc5af..d312daa340e1 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-trade-order-farming.spec.js @@ -118,7 +118,9 @@ describe('PPOM Blockaid Alert - Set Trade farming order @no-mmi', function () { await driver.clickElement('#maliciousTradeOrder'); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.assertElementNotPresent('.loading-indicator'); + await driver.assertElementNotPresent('.loading-indicator', { + timeout: 20000, + }); await driver.waitForSelector({ css: '.mm-text--body-lg-medium', diff --git a/test/e2e/tests/privacy-mode/privacy-mode.spec.js b/test/e2e/tests/privacy-mode/privacy-mode.spec.js deleted file mode 100644 index ede37f900e66..000000000000 --- a/test/e2e/tests/privacy-mode/privacy-mode.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - unlockWallet, - defaultGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Privacy Mode', function () { - it('should activate privacy mode, then deactivate it', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withPreferencesController().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - async function checkForHeaderValue(value) { - const balanceElement = await driver.findElement( - '[data-testid="account-value-and-suffix"]', - ); - const surveyText = await balanceElement.getText(); - assert.equal( - surveyText, - value, - `Header balance should be "${value}"`, - ); - } - - async function checkForTokenValue(value) { - const balanceElement = await driver.findElement( - '[data-testid="multichain-token-list-item-value"]', - ); - const surveyText = await balanceElement.getText(); - assert.equal(surveyText, value, `Token balance should be "${value}"`); - } - - async function checkForPrivacy() { - await checkForHeaderValue('••••••'); - await checkForTokenValue('••••••'); - } - - async function checkForNoPrivacy() { - await checkForHeaderValue('25'); - await checkForTokenValue('25 ETH'); - } - - async function togglePrivacy() { - const balanceElement = await driver.findElement( - '[data-testid="account-value-and-suffix"]', - ); - const initialText = await balanceElement.getText(); - - await driver.clickElement('[data-testid="sensitive-toggle"]'); - await driver.wait(async () => { - const currentText = await balanceElement.getText(); - return currentText !== initialText; - }, 2e3); - } - - await unlockWallet(driver); - await checkForNoPrivacy(); - await togglePrivacy(); - await checkForPrivacy(); - await togglePrivacy(); - await checkForNoPrivacy(); - }, - ); - }); - - it('should hide fiat balance and token balance when privacy mode is activated', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().withPreferencesController().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - async function togglePrivacy() { - const balanceElement = await driver.findElement( - '[data-testid="account-value-and-suffix"]', - ); - const initialText = await balanceElement.getText(); - - await driver.clickElement('[data-testid="sensitive-toggle"]'); - await driver.wait(async () => { - const currentText = await balanceElement.getText(); - return currentText !== initialText; - }, 2e3); - } - - await togglePrivacy(); - await driver.clickElement('[data-testid="account-menu-icon"]'); - const valueText = await driver.findElement( - '[data-testid="account-value-and-suffix"]', - ); - const valueTextContent = await valueText.getText(); - - assert.equal(valueTextContent, '••••••'); - }, - ); - }); -}); diff --git a/test/e2e/tests/privacy-mode/privacy-mode.spec.ts b/test/e2e/tests/privacy-mode/privacy-mode.spec.ts new file mode 100644 index 000000000000..60d9faedc080 --- /dev/null +++ b/test/e2e/tests/privacy-mode/privacy-mode.spec.ts @@ -0,0 +1,74 @@ +import { Driver } from '../../webdriver/driver'; +import { defaultGanacheOptions, withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import HomePage from '../../page-objects/pages/home/homepage'; +import { + loginWithBalanceValidation, + loginWithoutBalanceValidation, +} from '../../page-objects/flows/login.flow'; +import { Ganache } from '../../seeder/ganache'; + +describe('Privacy Mode', function () { + it('should hide fiat balance and token balance when privacy mode is activated', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.togglePrivacyBalance(); + await homePage.check_expectedBalanceIsDisplayed('••••••', '••••••'); + + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openAccountMenu(); + + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + await accountList.check_balanceIsPrivateEverywhere(); + }, + ); + }); + + it('should show fiat balance and token balance when privacy mode is deactivated', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withPreferencesController({ + preferences: { + privacyMode: true, + }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await loginWithoutBalanceValidation(driver); + + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.togglePrivacyBalance(); + await homePage.check_expectedBalanceIsDisplayed('25 ETH'); + + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openAccountMenu(); + + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + await accountList.check_accountBalanceDisplayed('25'); + }, + ); + }); +}); diff --git a/test/e2e/tests/privacy/account-tracker-api-usage.spec.ts b/test/e2e/tests/privacy/account-tracker-api-usage.spec.ts index 24f2318fa13b..d5c9b3e82076 100644 --- a/test/e2e/tests/privacy/account-tracker-api-usage.spec.ts +++ b/test/e2e/tests/privacy/account-tracker-api-usage.spec.ts @@ -8,7 +8,7 @@ import { withFixtures, } from '../../helpers'; import { Mockttp } from '../../mock-e2e'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; async function mockInfura(mockServer: Mockttp): Promise { diff --git a/test/e2e/tests/privacy/basic-functionality.spec.js b/test/e2e/tests/privacy/basic-functionality.spec.js deleted file mode 100644 index 77e9dad0b46f..000000000000 --- a/test/e2e/tests/privacy/basic-functionality.spec.js +++ /dev/null @@ -1,176 +0,0 @@ -const { strict: assert } = require('assert'); -const { defaultGanacheOptions, withFixtures } = require('../../helpers'); -const { METAMASK_STALELIST_URL } = require('../phishing-controller/helpers'); -const FixtureBuilder = require('../../fixture-builder'); -const { - importSRPOnboardingFlow, -} = require('../../page-objects/flows/onboarding.flow'); - -async function mockApis(mockServer) { - return [ - await mockServer.forGet(METAMASK_STALELIST_URL).thenCallback(() => { - return { - statusCode: 200, - body: [{ fakedata: true }], - }; - }), - await mockServer - .forGet('https://token.api.cx.metamask.io/tokens/1') - .thenCallback(() => { - return { - statusCode: 200, - body: [{ fakedata: true }], - }; - }), - await mockServer - .forGet('https://min-api.cryptocompare.com/data/pricemulti') - .withQuery({ fsyms: 'ETH', tsyms: 'usd' }) - .thenCallback(() => { - return { - statusCode: 200, - json: { - fakedata: 0, - }, - }; - }), - ]; -} - -describe('MetaMask onboarding @no-mmi', function () { - it('should prevent network requests to basic functionality endpoints when the basic functionality toggle is off', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockApis, - }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await importSRPOnboardingFlow({ driver }); - - await driver.clickElement({ - text: 'Manage default privacy settings', - tag: 'button', - }); - await driver.clickElement('[data-testid="category-item-General"]'); - - await driver.clickElement( - '[data-testid="basic-functionality-toggle"] .toggle-button', - ); - - await driver.clickElement('[id="basic-configuration-checkbox"]'); - await driver.clickElement({ text: 'Turn off', tag: 'button' }); - await driver.clickElement('[data-testid="category-back-button"]'); - await driver.clickElement('[data-testid="category-item-Assets"]'); - await driver.clickElement( - '[data-testid="currency-rate-check-toggle"] .toggle-button', - ); - 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"]', - ); - - await driver.clickElementAndWaitToDisappear({ - text: 'Done', - tag: 'button', - }); - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - // Wait until the onboarding carousel has stopped moving - // otherwise the click has no effect. - await driver.waitForElementToStopMoving({ - text: 'Done', - tag: 'button', - }); - await driver.clickElementAndWaitToDisappear({ - text: 'Done', - tag: 'button', - }); - - await driver.clickElement('[data-testid="network-display"]'); - - await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'p' }); - - // Wait until network is fully switched and refresh tokens before asserting to mitigate flakiness - await driver.assertElementNotPresent('.loading-overlay'); - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement('[data-testid="refreshList"]'); - - for (let i = 0; i < mockedEndpoints.length; i += 1) { - const requests = await mockedEndpoints[i].getSeenRequests(); - - assert.equal( - requests.length, - 0, - `${mockedEndpoints[i]} should make requests after onboarding`, - ); - } - }, - ); - }); - - it('should not prevent network requests to basic functionality endpoints when the basic functionality toggle is on', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder({ onboarding: true }).build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockApis, - }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await importSRPOnboardingFlow({ driver }); - - await driver.clickElement({ - text: 'Manage default privacy settings', - tag: 'button', - }); - await driver.clickElement('[data-testid="category-item-General"]'); - // Wait until the onboarding carousel has stopped moving - // otherwise the click has no effect. - await driver.waitForElementToStopMoving( - '[data-testid="category-back-button"]', - ); - 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"]', - ); - await driver.clickElement({ text: 'Done', tag: 'button' }); - await driver.clickElement('[data-testid="pin-extension-next"]'); - await driver.clickElement({ text: 'Done', tag: 'button' }); - - await driver.clickElement('[data-testid="network-display"]'); - - await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'p' }); - - // Wait until network is fully switched and refresh tokens before asserting to mitigate flakiness - await driver.assertElementNotPresent('.loading-overlay'); - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement('[data-testid="refreshList"]'); - // intended delay to allow for network requests to complete - await driver.delay(1000); - for (let i = 0; i < mockedEndpoints.length; i += 1) { - const requests = await mockedEndpoints[i].getSeenRequests(); - assert.equal( - requests.length, - 1, - `${mockedEndpoints[i]} should make requests after onboarding`, - ); - } - }, - ); - }); -}); diff --git a/test/e2e/tests/privacy/basic-functionality.spec.ts b/test/e2e/tests/privacy/basic-functionality.spec.ts new file mode 100644 index 000000000000..3d438f8d3474 --- /dev/null +++ b/test/e2e/tests/privacy/basic-functionality.spec.ts @@ -0,0 +1,120 @@ +import { strict as assert } from 'assert'; +import { Mockttp } from 'mockttp'; +import { defaultGanacheOptions, withFixtures } from '../../helpers'; +import { METAMASK_STALELIST_URL } from '../phishing-controller/helpers'; +import FixtureBuilder from '../../fixture-builder'; +import HomePage from '../../page-objects/pages/home/homepage'; +import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; +import OnboardingPrivacySettingsPage from '../../page-objects/pages/onboarding/onboarding-privacy-settings-page'; +import { switchToNetworkFlow } from '../../page-objects/flows/network.flow'; +import { + completeImportSRPOnboardingFlow, + importSRPOnboardingFlow, +} from '../../page-objects/flows/onboarding.flow'; + +async function mockApis(mockServer: Mockttp) { + return [ + await mockServer.forGet(METAMASK_STALELIST_URL).thenCallback(() => { + return { + statusCode: 200, + json: [{ fakedata: true }], + }; + }), + await mockServer + .forGet('https://token.api.cx.metamask.io/tokens/1') + .thenCallback(() => { + return { + statusCode: 200, + json: [{ fakedata: true }], + }; + }), + await mockServer + .forGet('https://min-api.cryptocompare.com/data/pricemulti') + .withQuery({ fsyms: 'ETH', tsyms: 'usd' }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + fakedata: 0, + }, + }; + }), + ]; +} + +describe('MetaMask onboarding @no-mmi', function () { + it('should prevent network requests to basic functionality endpoints when the basic functionality toggle is off', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockApis, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await importSRPOnboardingFlow({ driver }); + + const onboardingCompletePage = new OnboardingCompletePage(driver); + await onboardingCompletePage.check_pageIsLoaded(); + await onboardingCompletePage.check_walletReadyMessageIsDisplayed(); + await onboardingCompletePage.navigateToDefaultPrivacySettings(); + + const onboardingPrivacySettingsPage = new OnboardingPrivacySettingsPage( + driver, + ); + await onboardingPrivacySettingsPage.toggleBasicFunctionalitySettings(); + await onboardingPrivacySettingsPage.toggleAssetsSettings(); + await onboardingPrivacySettingsPage.navigateBackToOnboardingCompletePage(); + + await onboardingCompletePage.check_pageIsLoaded(); + await onboardingCompletePage.completeOnboarding(); + + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + + await switchToNetworkFlow(driver, 'Ethereum Mainnet'); + await homePage.refreshErc20TokenList(); + + for (const mockedEndpoint of mockedEndpoints) { + const requests = await mockedEndpoint.getSeenRequests(); + assert.equal( + requests.length, + 0, + `${mockedEndpoint} should make requests after onboarding`, + ); + } + }, + ); + }); + + it('should not prevent network requests to basic functionality endpoints when the basic functionality toggle is on', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockApis, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await completeImportSRPOnboardingFlow({ driver }); + + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + + await switchToNetworkFlow(driver, 'Ethereum Mainnet'); + await homePage.refreshErc20TokenList(); + + // intended delay to allow for network requests to complete + await driver.delay(1000); + for (const mockedEndpoint of mockedEndpoints) { + const requests = await mockedEndpoint.getSeenRequests(); + assert.equal( + requests.length, + 1, + `${mockedEndpoint} should make requests after onboarding`, + ); + } + }, + ); + }); +}); 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 f1bdc08166b7..aca018eb52ee 100644 --- a/test/e2e/tests/privacy/onboarding-infura-call-privacy.spec.ts +++ b/test/e2e/tests/privacy/onboarding-infura-call-privacy.spec.ts @@ -2,7 +2,7 @@ import assert from 'assert'; import { Mockttp, MockedEndpoint } from 'mockttp'; import { withFixtures, regularDelayMs } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; import { importSRPOnboardingFlow, diff --git a/test/e2e/tests/privacy/onboarding-token-price-call-privacy.spec.ts b/test/e2e/tests/privacy/onboarding-token-price-call-privacy.spec.ts index f565c0bb354e..7085c1317610 100644 --- a/test/e2e/tests/privacy/onboarding-token-price-call-privacy.spec.ts +++ b/test/e2e/tests/privacy/onboarding-token-price-call-privacy.spec.ts @@ -2,7 +2,7 @@ import assert from 'assert'; import { Mockttp, MockedEndpoint } from 'mockttp'; import { withFixtures, regularDelayMs } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; import { importSRPOnboardingFlow, diff --git a/test/e2e/tests/privacy/polling.spec.ts b/test/e2e/tests/privacy/polling.spec.ts index b63e1bce6191..b91a72e9d23e 100644 --- a/test/e2e/tests/privacy/polling.spec.ts +++ b/test/e2e/tests/privacy/polling.spec.ts @@ -5,7 +5,7 @@ import { expect } from '@playwright/test'; import FixtureBuilder from '../../fixture-builder'; import { defaultGanacheOptions, withFixtures } from '../../helpers'; import { Mockttp } from '../../mock-e2e'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; const infuraMainnetUrl = diff --git a/test/e2e/tests/remote-feature-flag/mock-data.ts b/test/e2e/tests/remote-feature-flag/mock-data.ts new file mode 100644 index 000000000000..f9da6d052f19 --- /dev/null +++ b/test/e2e/tests/remote-feature-flag/mock-data.ts @@ -0,0 +1,8 @@ +export const MOCK_REMOTE_FEATURE_FLAGS_RESPONSE = { + feature1: true, + feature2: false, + feature3: { + name: 'groupC', + value: 'valueC', + }, +}; diff --git a/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts b/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts new file mode 100644 index 000000000000..42ebe7dc762c --- /dev/null +++ b/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts @@ -0,0 +1,48 @@ +import { strict as assert } from 'assert'; +import { Suite } from 'mocha'; +import { getCleanAppState, withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { TestSuiteArguments } from '../confirmations/transactions/shared'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import { MOCK_META_METRICS_ID } from '../../constants'; +import { MOCK_REMOTE_FEATURE_FLAGS_RESPONSE } from './mock-data'; + +describe('Remote feature flag', function (this: Suite) { + it('should be fetched with threshold value when basic functionality toggle is on', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: MOCK_META_METRICS_ID, + participateInMetaMetrics: true, + }) + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }: TestSuiteArguments) => { + await loginWithBalanceValidation(driver); + const uiState = await getCleanAppState(driver); + assert.deepStrictEqual( + uiState.metamask.remoteFeatureFlags, + MOCK_REMOTE_FEATURE_FLAGS_RESPONSE, + ); + }, + ); + }); + + it('should not be fetched when basic functionality toggle is off', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withUseBasicFunctionalityDisabled() + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }: TestSuiteArguments) => { + await loginWithBalanceValidation(driver); + const uiState = await getCleanAppState(driver); + assert.deepStrictEqual(uiState.metamask.remoteFeatureFlags, {}); + }, + ); + }); +}); diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js index 958c1351d8b3..659ed4cfbc41 100644 --- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js +++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js @@ -9,246 +9,119 @@ const { WINDOW_TITLES, defaultGanacheOptions, largeDelayMs, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing for Multiple Dapps and Txs on different networks', function () { - describe('Old confirmation screens', function () { - it('should batch confirmation txs for different dapps on different networks.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should batch confirmation txs for different dapps on different networks.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, + title: this.test.fullTitle(), + }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + // Dapp one send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - // Dapp one send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + await driver.delay(largeDelayMs); - await driver.delay(largeDelayMs); + // Dapp two send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - // Dapp two send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); + // Wait for confirmation to close + await driver.delay(2000); - // TODO: Do we want to confirm here? - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); + // Wait for new confirmations queued from second dapp to open + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Wait for confirmation to close - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); - // Wait for new confirmations queued from second dapp to open - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - }, - ); - }); - }); - - describe('Redesigned confirmation screens', function () { - it('should batch confirmation txs for different dapps on different networks.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Dapp one send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.delay(largeDelayMs); - - // Dapp two send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//p[normalize-space(.)='1 of 2']"), - ); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - // Wait for confirmation to close - await driver.delay(2000); - - // Wait for new confirmations queued from second dapp to open - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//p[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: 'p', - text: 'Localhost 8546', - }); - }, - ); - }); + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js index 066acacab23a..2c56b23e03cc 100644 --- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js +++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js @@ -9,326 +9,159 @@ const { unlockWallet, WINDOW_TITLES, withFixtures, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing for Multiple Dapps and Txs on different networks', function () { - describe('Old confirmation flows', function () { - it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Ensure Dapp One is on Localhost 8546 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Dapp 1 send 2 tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - - // Dapp 2 send 2 tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - // We cannot wait for the dialog, since it is already opened from before - await driver.delay(largeDelayMs); - - // Dapp 1 send 1 tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - // We cannot switch directly, as the dialog is sometimes closed and re-opened - await driver.delay(largeDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); - - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); - - // TODO: Do we want to confirm here? - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - // Wait for new confirmations queued from second dapp to open - // We need a big delay to make sure dialog is not invalidated - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - // Wait for new confirmations queued from second dapp to open - // We need a big delay to make sure dialog is not invalidated - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - }, - ); - }); - }); - - describe('Redesigned confirmation flows', function () { - it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Ensure Dapp One is on Localhost 8546 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Dapp 1 send 2 tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - - // Dapp 2 send 2 tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - // We cannot wait for the dialog, since it is already opened from before - await driver.delay(largeDelayMs); - - // Dapp 1 send 1 tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - // We cannot switch directly, as the dialog is sometimes closed and re-opened - await driver.delay(largeDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//p[normalize-space(.)='1 of 2']"), - ); - - // Reject All Transactions - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - // Wait for new confirmations queued from second dapp to open - // We need a big delay to make sure dialog is not invalidated - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//p[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: 'p', - text: 'Localhost 8546', - }); - - // Reject All Transactions - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - // Wait for new confirmations queued from second dapp to open - // We need a big delay to make sure dialog is not invalidated - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - }, - ); - }); + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Ensure Dapp One is on Localhost 8546 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // Dapp 1 send 2 tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + + // Dapp 2 send 2 tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + // We cannot wait for the dialog, since it is already opened from before + await driver.delay(largeDelayMs); + + // Dapp 1 send 1 tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + // We cannot switch directly, as the dialog is sometimes closed and re-opened + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Reject All Transactions + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // Wait for new confirmations queued from second dapp to open + // We need a big delay to make sure dialog is not invalidated + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + + // Reject All Transactions + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + // Wait for new confirmations queued from second dapp to open + // We need a big delay to make sure dialog is not invalidated + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js index d3241c95c9d5..7ba5ca579298 100644 --- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js +++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js @@ -10,317 +10,156 @@ const { WINDOW_TITLES, defaultGanacheOptions, largeDelayMs, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing for Multiple Dapps and Txs on same networks', function () { - describe('Old confirmation screens', function () { - it('should batch confirmation txs for different dapps on same networks ', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 3 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should batch confirmation txs for different dapps on same networks ', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .build(), + dappOptions: { numberOfDapps: 3 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, + title: this.test.fullTitle(), + }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Connect to dapp 1 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - // Connect to dapp 1 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.delay(regularDelayMs); - await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithUrl(DAPP_URL); - await driver.switchToWindowWithUrl(DAPP_URL); + let switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x3e8' }], + }); - let switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x3e8' }], - }); + // Ensure Dapp One is on Localhost 7777 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // Ensure Dapp One is on Localhost 7777 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + // Should auto switch without prompt since already approved via connect - // Should auto switch without prompt since already approved via connect + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.delay(regularDelayMs); - await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); - switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); + // Ensure Dapp Two is on Localhost 8545 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // Ensure Dapp Two is on Localhost 8545 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + // Should auto switch without prompt since already approved via connect - // Should auto switch without prompt since already approved via connect + // Dapp one send two tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - // Dapp one send two tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + await driver.delay(largeDelayMs); - await driver.delay(largeDelayMs); + // Dapp two send two tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - // Dapp two send two tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 7777', + }); - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 7777', - }); + // Reject All Transactions + await driver.clickElement({ text: 'Reject all', tag: 'button' }); - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); + // Wait for confirmation to close + await driver.waitUntilXWindowHandles(4); - await driver.clickElement({ text: 'Reject all', tag: 'button' }); // TODO: Do we want to confirm here? + // Wait for new confirmations queued from second dapp to open + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Wait for confirmation to close - await driver.waitUntilXWindowHandles(4); + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); - // Wait for new confirmations queued from second dapp to open - await driver.delay(largeDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - }, - ); - }); - }); - - describe('Redesigned confirmation screens', function () { - it('should batch confirmation txs for different dapps on same networks ', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 3 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - let switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x3e8' }], - }); - - // Ensure Dapp One is on Localhost 7777 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Ensure Dapp Two is on Localhost 8545 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Should auto switch without prompt since already approved via connect - - // Dapp one send two tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.delay(largeDelayMs); - - // Dapp two send two tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//p[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: 'p', - text: 'Localhost 7777', - }); - - // Reject All Transactions - await driver.clickElement({ text: 'Reject all', tag: 'button' }); - - // Wait for confirmation to close - await driver.waitUntilXWindowHandles(4); - - // Wait for new confirmations queued from second dapp to open - await driver.delay(largeDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//p[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: 'p', - text: 'Localhost 8546', - }); - }, - ); - }); + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/chainid-check.spec.js b/test/e2e/tests/request-queuing/chainid-check.spec.js index 1579a8ae5aa4..95ed3ca6bd8c 100644 --- a/test/e2e/tests/request-queuing/chainid-check.spec.js +++ b/test/e2e/tests/request-queuing/chainid-check.spec.js @@ -13,304 +13,145 @@ const { const { PAGES } = require('../../webdriver/driver'); describe('Request Queueing chainId proxy sync', function () { - describe('request queue is on', function () { - it('should preserve per dapp network selections after connecting and switching without refresh calls @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should preserve per dapp network selections after connecting and switching without refresh calls @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - const chainIdRequest = JSON.stringify({ - method: 'eth_chainId', - }); + const chainIdRequest = JSON.stringify({ + method: 'eth_chainId', + }); - const chainIdBeforeConnect = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); - - assert.equal(chainIdBeforeConnect, '0x539'); // 1337 - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Ethereum Mainnet', - css: 'p', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + const chainIdBeforeConnect = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); - const chainIdBeforeConnectAfterManualSwitch = - await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + assert.equal(chainIdBeforeConnect, '0x539'); // 1337 - // before connecting the chainId should change with the wallet - assert.equal(chainIdBeforeConnectAfterManualSwitch, '0x1'); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await switchToNotificationWindow(driver); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const chainIdAfterConnect = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // should still be on the same chainId as the wallet after connecting - assert.equal(chainIdAfterConnect, '0x1'); + // Switch to second network + await driver.clickElement({ + text: 'Ethereum Mainnet', + css: 'p', + }); - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + const chainIdBeforeConnectAfterManualSwitch = await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - await switchToNotificationWindow(driver); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const chainIdAfterDappSwitch = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); - - // should be on the new chainId that was requested - assert.equal(chainIdAfterDappSwitch, '0x539'); // 1337 - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const chainIdAfterManualSwitch = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); - // after connecting the dapp should now have its own selected network - // independent from the globally selected and therefore should not have changed when - // the globally selected network was manually changed via the wallet UI - assert.equal(chainIdAfterManualSwitch, '0x539'); // 1337 - }, - ); - }); - }); - - describe('request queue is off', function () { - it('should always follow the globally selected network after connecting and switching without refresh calls @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Experimental', tag: 'div' }); - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - await driver.delay(regularDelayMs); - - const chainIdRequest = JSON.stringify({ - method: 'eth_chainId', - }); - - const chainIdBeforeConnect = await driver.executeScript( `return window.ethereum.request(${chainIdRequest})`, ); - assert.equal(chainIdBeforeConnect, '0x539'); // 1337 - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + // before connecting the chainId should change with the wallet + assert.equal(chainIdBeforeConnectAfterManualSwitch, '0x1'); - // Switch to second network - await driver.clickElement({ - text: 'Ethereum Mainnet', - css: 'p', - }); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.delay(regularDelayMs); - const chainIdBeforeConnectAfterManualSwitch = - await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + await switchToNotificationWindow(driver); - // before connecting the chainId should change with the wallet - assert.equal(chainIdBeforeConnectAfterManualSwitch, '0x1'); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); - // Connect to dapp - await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const chainIdAfterConnect = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + // should still be on the same chainId as the wallet after connecting + assert.equal(chainIdAfterConnect, '0x1'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); - const chainIdAfterConnect = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // should still be on the same chainId as the wallet after connecting - assert.equal(chainIdAfterConnect, '0x1'); - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x1', - }); + await switchToNotificationWindow(driver); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const chainIdAfterDappSwitch = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); + // should be on the new chainId that was requested + assert.equal(chainIdAfterDappSwitch, '0x539'); // 1337 - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - const chainIdAfterDappSwitch = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // should be on the new chainId that was requested - assert.equal(chainIdAfterDappSwitch, '0x539'); // 1337 + // Switch network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - // TODO: check that the wallet network has changed too - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch network - await driver.clickElement({ - text: 'Ethereum Mainnet', - css: 'p', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const chainIdAfterManualSwitch = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); - // after connecting the dapp should still be following the - // globally selected network and therefore should have changed when - // the globally selected network was manually changed via the wallet UI - assert.equal(chainIdAfterManualSwitch, '0x1'); - }, - ); - }); + const chainIdAfterManualSwitch = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + // after connecting the dapp should now have its own selected network + // independent from the globally selected and therefore should not have changed when + // the globally selected network was manually changed via the wallet UI + assert.equal(chainIdAfterManualSwitch, '0x539'); // 1337 + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js index 28b19efbeb04..d716b8bf1f12 100644 --- a/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js @@ -7,303 +7,148 @@ const { DAPP_ONE_URL, regularDelayMs, defaultGanacheOptions, - tempToggleSettingRedesignedConfirmations, WINDOW_TITLES, largeDelayMs, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { - describe('Old confirmation screens', function () { - it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open and connect Dapp One - await openDapp(driver, undefined, DAPP_URL); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - // Open and connect to Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Switch Dapp Two to Localhost 8546 - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - let switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Initiate switchEthereumChain on Dapp one - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x53a', - }); - - // Should auto switch without prompt since already approved via connect - - // Switch back to Dapp One - await driver.switchToWindowWithUrl(DAPP_URL); - - // switch chain for Dapp One - switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x3e8' }], - }); - - // Initiate switchEthereumChain on Dapp one - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x3e8', - }); - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithUrl(DAPP_URL); - - // eth_sendTransaction request - await driver.clickElement('#sendButton'); - await driver.waitUntilXWindowHandles(3); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - // signTypedData request - await driver.clickElement('#signTypedData'); - - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the send confirmation. - await driver.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Localhost 7777', - }); - - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.delay(largeDelayMs); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the signTypedData confirmation. - await driver.waitForSelector({ - css: '[data-testid="signature-request-network-display"]', - text: 'Localhost 8546', - }); - - await driver.clickElement({ text: 'Reject', tag: 'button' }); - }, - ); - }); - }); - - describe('Redesigned confirmation screens', function () { - it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open and connect Dapp One - await openDapp(driver, undefined, DAPP_URL); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - // Open and connect to Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Switch Dapp Two to Localhost 8546 - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - let switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Initiate switchEthereumChain on Dapp one - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x53a', - }); - - // Should auto switch without prompt since already approved via connect - - // Switch back to Dapp One - await driver.switchToWindowWithUrl(DAPP_URL); - - // switch chain for Dapp One - switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x3e8' }], - }); - - // Initiate switchEthereumChain on Dapp one - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x3e8', - }); - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithUrl(DAPP_URL); - - // eth_sendTransaction request - await driver.clickElement('#sendButton'); - await driver.waitUntilXWindowHandles(3); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - // signTypedData request - await driver.clickElement('#signTypedData'); - - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the send confirmation. - await driver.waitForSelector({ - css: 'p', - text: 'Localhost 7777', - }); - - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.delay(largeDelayMs); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the signTypedData confirmation. - await driver.waitForSelector({ - css: 'p', - text: 'Localhost 8546', - }); - - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - }, - ); - }); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open and connect Dapp One + await openDapp(driver, undefined, DAPP_URL); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Open and connect to Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // Switch Dapp Two to Localhost 8546 + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + let switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp one + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x53a', + }); + + // Should auto switch without prompt since already approved via connect + + // Switch back to Dapp One + await driver.switchToWindowWithUrl(DAPP_URL); + + // switch chain for Dapp One + switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x3e8' }], + }); + + // Initiate switchEthereumChain on Dapp one + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x3e8', + }); + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithUrl(DAPP_URL); + + // eth_sendTransaction request + await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(3); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + // signTypedData request + await driver.clickElement('#signTypedData'); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the send confirmation. + await driver.waitForSelector({ + css: 'p', + text: 'Localhost 7777', + }); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + await driver.delay(largeDelayMs); + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the signTypedData confirmation. + await driver.waitForSelector({ + css: 'p', + text: 'Localhost 8546', + }); + + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js b/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js deleted file mode 100644 index 53c763d8891f..000000000000 --- a/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js +++ /dev/null @@ -1,95 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../../fixture-builder'); -const { - withFixtures, - openDapp, - unlockWallet, - DAPP_URL, - regularDelayMs, - WINDOW_TITLES, - switchToNotificationWindow, - defaultGanacheOptions, -} = require('../../helpers'); - -describe('Request Queueing', function () { - it('should keep subscription on dapp network when switching different mm network', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await switchToNotificationWindow(driver); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Navigate to test dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const subscribeRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'eth_subscribe', - params: ['newHeads'], - }); - - const subscribe = await driver.executeScript( - `return window.ethereum.request(${subscribeRequest})`, - ); - - const subscriptionMessage = await driver.executeAsyncScript( - `const callback = arguments[arguments.length - 1];` + - `window.ethereum.on('message', (message) => callback(message))`, - ); - - assert.strictEqual(subscribe, subscriptionMessage.data.subscription); - assert.strictEqual(subscriptionMessage.type, 'eth_subscription'); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - assert.strictEqual(subscribe, subscriptionMessage.data.subscription); - assert.strictEqual(subscriptionMessage.type, 'eth_subscription'); - }, - ); - }); -}); diff --git a/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts b/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts new file mode 100644 index 000000000000..6b87a7142fbe --- /dev/null +++ b/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts @@ -0,0 +1,97 @@ +import { strict as assert } from 'assert'; +import FixtureBuilder from '../../fixture-builder'; +import { + defaultGanacheOptions, + WINDOW_TITLES, + withFixtures, +} from '../../helpers'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; +import { switchToNetworkFlow } from '../../page-objects/flows/network.flow'; + +describe('Request Queueing', function () { + it('should keep subscription on dapp network when switching different mm network', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test?.fullTitle(), + }, + + async ({ driver, ganacheServer }) => { + await loginWithoutBalanceValidation(driver); + + // Connect to dapp + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + await testDapp.connectAccount({}); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + // Subscribe to newHeads event + const subscribeRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_subscribe', + params: ['newHeads'], + }); + + await driver.executeScript( + `return window.ethereum.request(${subscribeRequest})`, + ); + + // Save event logs into the messages variable in the window context, to access it later + await driver.executeScript(` + window.messages = []; + window.ethereum.on('message', (message) => { + if (message.type === 'eth_subscription') { + console.log('New block header:', message.data.result); + window.messages.push(message.data.result); + } + }); + `); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Switch networks + await switchToNetworkFlow(driver, 'Localhost 8546'); + + // Navigate back to the test dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + // Access to the window messages variable + const messagesBeforeMining = await driver.executeScript( + 'return window.messages;', + ); + + // Mine a block deterministically + await ganacheServer.mineBlock(); + + // Wait a couple of seconds for the logs to populate into the messages window variable + await driver.delay(5000); + + // Access the window messages variable and check there are more events than before mining + const messagesAfterMining = await driver.executeScript( + 'return window.messages;', + ); + + assert.ok(messagesBeforeMining.length < messagesAfterMining.length); + }, + ); + }); +}); diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js index 67efbfe6fee9..d6a95d44e530 100644 --- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js @@ -9,247 +9,148 @@ const { regularDelayMs, WINDOW_TITLES, defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing Dapp 1 Send Tx -> Dapp 2 Request Accounts Tx', function () { - describe('Old confirmation screens', function () { - it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withPermissionControllerConnectedToTestDapp() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPermissionControllerConnectedToTestDapp() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Dapp Send Button + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Dapp Send Button - await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.waitForSelector({ + text: 'Cancel', + tag: 'button', + }); - await driver.waitForSelector({ - text: 'Reject', - tag: 'button', - }); + await driver.delay(regularDelayMs); - await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + // Leave the confirmation pending + await openDapp(driver, undefined, DAPP_ONE_URL); - // Leave the confirmation pending - await openDapp(driver, undefined, DAPP_ONE_URL); + const accountsOnload = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsOnload, ''); - const accountsOnload = await ( - await driver.findElement('#accounts') - ).getText(); - assert.deepStrictEqual(accountsOnload, ''); + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.delay(regularDelayMs); - await driver.delay(regularDelayMs); + const accountsBeforeConnect = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsBeforeConnect, ''); - const accountsBeforeConnect = await ( - await driver.findElement('#accounts') - ).getText(); - assert.deepStrictEqual(accountsBeforeConnect, ''); + // Reject the pending confirmation from the first dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Reject the pending confirmation from the first dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject', - tag: 'button', - }); + await driver.clickElement({ + text: 'Cancel', + tag: 'button', + }); - // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp - await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.waitForSelector({ - text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - css: '#accounts', - }); - }, - ); - }); + await driver.waitForSelector({ + text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + css: '#accounts', + }); + }, + ); }); - describe('Redesigned confirmation screens', function () { - it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withPermissionControllerConnectedToTestDapp() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Dapp Send Button - await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector({ - text: 'Cancel', - tag: 'button', - }); - - await driver.delay(regularDelayMs); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Leave the confirmation pending - await openDapp(driver, undefined, DAPP_ONE_URL); - - const accountsOnload = await ( - await driver.findElement('#accounts') - ).getText(); - assert.deepStrictEqual(accountsOnload, ''); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - const accountsBeforeConnect = await ( - await driver.findElement('#accounts') - ).getText(); - assert.deepStrictEqual(accountsBeforeConnect, ''); - - // Reject the pending confirmation from the first dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Cancel', - tag: 'button', - }); - - // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp - await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - await driver.waitForSelector({ - text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - css: '#accounts', - }); + it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPermissionControllerConnectedToTwoTestDapps() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - ); - }); - - it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withPermissionControllerConnectedToTwoTestDapps() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Dapp Send Button - await driver.clickElement('#sendButton'); + // Dapp Send Button + await driver.clickElement('#sendButton'); - // Leave the confirmation pending + // Leave the confirmation pending - await openDapp(driver, undefined, DAPP_ONE_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); - const ethRequestAccounts = JSON.stringify({ - jsonrpc: '2.0', - method: 'eth_requestAccounts', - }); + const ethRequestAccounts = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_requestAccounts', + }); - const accounts = await driver.executeScript( - `return window.ethereum.request(${ethRequestAccounts})`, - ); + const accounts = await driver.executeScript( + `return window.ethereum.request(${ethRequestAccounts})`, + ); - assert.deepStrictEqual(accounts, [ - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - ]); - }, - ); - }); + assert.deepStrictEqual(accounts, [ + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + ]); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js index 24c09ee18d09..e0aa4f7fa538 100644 --- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js @@ -7,619 +7,302 @@ const { unlockWallet, WINDOW_TITLES, withFixtures, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { - describe('Old confirmation screens', function () { - it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); - const editButtons = await driver.findElements('[data-testid="edit"]'); + await editButtons[1].click(); - await editButtons[1].click(); + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); + await driver.clickElement('[data-testid="connect-more-chains-button"]'); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findElement({ - text: 'Use your enabled networks', - tag: 'p', - }); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - await driver.clickElement('#sendButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - // Wait for switch confirmation to close then tx confirmation to show. - // There is an extra window appearing and disappearing - // so we leave this delay until the issue is fixed (#27360) - await driver.delay(5000); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Check correct network on the send confirmation. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); - - it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + await driver.switchToWindowWithUrl(DAPP_URL); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - await editButtons[1].click(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findElement({ + text: 'Use your enabled networks', + tag: 'p', + }); - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // Wait for switch confirmation to close then tx confirmation to show. + // There is an extra window appearing and disappearing + // so we leave this delay until the issue is fixed (#27360) + await driver.delay(5000); - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Check correct network on the send confirmation. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); - await driver.switchToWindowWithUrl(DAPP_URL); + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', ); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - await driver.clickElement('#sendButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - // Wait for switch confirmation to close then tx confirmation to show. - // There is an extra window appearing and disappearing - // so we leave this delay until the issue is fixed (#27360) - await driver.delay(5000); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the send confirmation. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); + return confirmedTxes.length === 1; + }, 10000); + }, + ); }); - describe('Redesigned confirmation screens', function () { - it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); - const editButtons = await driver.findElements('[data-testid="edit"]'); + await editButtons[1].click(); - await editButtons[1].click(); + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); + await driver.clickElement('[data-testid="connect-more-chains-button"]'); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithUrl(DAPP_URL); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); + await driver.switchToWindowWithUrl(DAPP_URL); - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findElement({ - text: 'Use your enabled networks', - tag: 'p', - }); + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + // Wait for switch confirmation to close then tx confirmation to show. + // There is an extra window appearing and disappearing + // so we leave this delay until the issue is fixed (#27360) + await driver.delay(5000); - // Wait for switch confirmation to close then tx confirmation to show. - // There is an extra window appearing and disappearing - // so we leave this delay until the issue is fixed (#27360) - await driver.delay(5000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Check correct network on the send confirmation. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); - // Check correct network on the send confirmation. - await driver.findElement({ - css: 'p', - text: 'Localhost 8546', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', ); - - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); - - it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - await editButtons[1].click(); - - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement( - '[data-testid="connect-more-chains-button"]', - ); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - await driver.clickElement('#sendButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - // Wait for switch confirmation to close then tx confirmation to show. - // There is an extra window appearing and disappearing - // so we leave this delay until the issue is fixed (#27360) - await driver.delay(5000); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the send confirmation. - await driver.findElement({ - css: 'p', - text: 'Localhost 8546', - }); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); + return confirmedTxes.length === 1; + }, 10000); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/enable-queuing.spec.js b/test/e2e/tests/request-queuing/enable-queuing.spec.js deleted file mode 100644 index ccc5f7cec2c7..000000000000 --- a/test/e2e/tests/request-queuing/enable-queuing.spec.js +++ /dev/null @@ -1,47 +0,0 @@ -const { - withFixtures, - defaultGanacheOptions, - unlockWallet, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Toggle Request Queuing Setting', function () { - it('enables the request queuing experimental setting', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - failOnConsoleError: false, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open account menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const securityAndPrivacyTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(securityAndPrivacyTabRawLocator); - - // Toggle request queue setting - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - }, - ); - }); -}); diff --git a/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js b/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js index 3a413f147e06..f147b8d87fc8 100644 --- a/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js +++ b/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js @@ -7,247 +7,122 @@ const { DAPP_ONE_URL, WINDOW_TITLES, defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing for Multiple Dapps and Txs on different networks revokePermissions', function () { - describe('Old confirmation screens', function () { - it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Dapp 1 send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - await driver.delay(3000); - - // Dapp 2 send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.waitUntilXWindowHandles(4); - - // Dapp 1 revokePermissions - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.assertElementNotPresent({ - css: '[id="chainId"]', - text: '0x53a', - }); - - // Confirmation will close then reopen - await driver.clickElement('#revokeAccountsPermission'); - // TODO: find a better way to handle different dialog ids - await driver.delay(3000); - - // Check correct network on confirm tx. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - }, - ); - }); - }); - - describe('New confirmation screens', function () { - it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Dapp 1 send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - await driver.delay(3000); - - // Dapp 2 send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.waitUntilXWindowHandles(4); - - // Dapp 1 revokePermissions - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.assertElementNotPresent({ - css: '[id="chainId"]', - text: '0x53a', - }); - - // Confirmation will close then reopen - await driver.clickElement('#revokeAccountsPermission'); - // TODO: find a better way to handle different dialog ids - await driver.delay(3000); - - // Check correct network on confirm tx. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.findElement({ - css: 'p', - text: 'Localhost 8546', - }); - }, - ); - }); + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Dapp 1 send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x539', + }); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + await driver.delay(3000); + + // Dapp 2 send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(4); + + // Dapp 1 revokePermissions + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x539', + }); + await driver.assertElementNotPresent({ + css: '[id="chainId"]', + text: '0x53a', + }); + + // Confirmation will close then reopen + await driver.clickElement('#revokeAccountsPermission'); + // TODO: find a better way to handle different dialog ids + await driver.delay(3000); + + // Check correct network on confirm tx. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js index f831ff1ff38d..000727f8bd5c 100644 --- a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js +++ b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js @@ -8,277 +8,135 @@ const { WINDOW_TITLES, defaultGanacheOptions, largeDelayMs, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing for Multiple Dapps and Txs on different networks.', function () { - describe('Old confirmation screens', function () { - it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Dapp one send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // Dapp two send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // First switch network - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - // Wait for confirm tx after switch network confirmation. - await driver.delay(largeDelayMs); + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Reject Transaction - await driver.findClickableElement({ text: 'Reject', tag: 'button' }); - await driver.clickElement( - '[data-testid="page-container-footer-cancel"]', - ); - - // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - - // Check for unconfirmed transaction in tx list - await driver.wait(async () => { - const unconfirmedTxes = await driver.findElements( - '.transaction-list-item--unconfirmed', - ); - return unconfirmedTxes.length === 1; - }, 10000); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - // Click Unconfirmed Tx - await driver.clickElement('.transaction-list-item--unconfirmed'); + // Dapp one send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); - await driver.assertElementNotPresent({ - tag: 'p', - text: 'Network switched to Localhost 8546', - }); + await driver.waitUntilXWindowHandles(4); - // Confirm Tx - await driver.clickElement( - '[data-testid="page-container-footer-next"]', - ); - - // Check for Confirmed Transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); - }); + // Dapp two send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); - describe('Redesigned confirmation screens', function () { - it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + // First switch network + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Wait for confirm tx after switch network confirmation. + await driver.delay(largeDelayMs); - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Reject Transaction + await driver.findClickableElement({ text: 'Cancel', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, + // Check for unconfirmed transaction in tx list + await driver.wait(async () => { + const unconfirmedTxes = await driver.findElements( + '.transaction-list-item--unconfirmed', ); + return unconfirmedTxes.length === 1; + }, 10000); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Dapp one send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - - // Dapp two send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); + // Click Unconfirmed Tx + await driver.clickElement('.transaction-list-item--unconfirmed'); - // First switch network - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.assertElementNotPresent({ + tag: 'p', + text: 'Network switched to Localhost 8546', + }); - // Wait for confirm tx after switch network confirmation. - await driver.delay(largeDelayMs); + // Confirm Tx + await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Reject Transaction - await driver.findClickableElement({ text: 'Cancel', tag: 'button' }); - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', + // Check for Confirmed Transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', ); - - // Check for unconfirmed transaction in tx list - await driver.wait(async () => { - const unconfirmedTxes = await driver.findElements( - '.transaction-list-item--unconfirmed', - ); - return unconfirmedTxes.length === 1; - }, 10000); - - // Click Unconfirmed Tx - await driver.clickElement('.transaction-list-item--unconfirmed'); - - await driver.assertElementNotPresent({ - tag: 'p', - text: 'Network switched to Localhost 8546', - }); - - // Confirm Tx - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Check for Confirmed Transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); + return confirmedTxes.length === 1; + }, 10000); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.js b/test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.js deleted file mode 100644 index 363f7c94cc74..000000000000 --- a/test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -const FixtureBuilder = require('../../fixture-builder'); -const { - withFixtures, - openDapp, - unlockWallet, - DAPP_URL, - defaultGanacheOptions, -} = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); - -describe('Request Queuing', function () { - it('should clear tx confirmation when revokePermission is called from origin dapp', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // wallet_revokePermissions request - const revokePermissionsRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_revokePermissions', - params: [ - { - eth_accounts: {}, - }, - ], - }); - - await driver.executeScript( - `return window.ethereum.request(${revokePermissionsRequest})`, - ); - - // Should have cleared the tx confirmation - await driver.waitUntilXWindowHandles(2); - - // Cleared eth_accounts account label - await driver.findElement({ xpath: '//span[@id="accounts"][.=""]' }); - }, - ); - }); -}); diff --git a/test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.ts b/test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.ts new file mode 100644 index 000000000000..620735761c69 --- /dev/null +++ b/test/e2e/tests/request-queuing/sendTx-revokePermissions.spec.ts @@ -0,0 +1,75 @@ +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import TransactionConfirmation from '../../page-objects/pages/confirmations/redesign/transaction-confirmation'; +import { Ganache } from '../../seeder/ganache'; +import { Driver } from '../../webdriver/driver'; +import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; +import FixtureBuilder from '../../fixture-builder'; +import { + withFixtures, + defaultGanacheOptions, + WINDOW_TITLES, +} from '../../helpers'; + +describe('Request Queuing', function () { + // TODO: add a new spec which checks that after revoking and connecting again + // a pending tx is still closed when using revokePermissions. + // To be done once this bug is fixed: #29272 + it('should clear tx confirmation when revokePermission is called from origin dapp', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + }, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + // Open test dapp + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_connectedAccounts(DEFAULT_FIXTURE_ACCOUNT); + + // Trigger a tx + await testDapp.clickSimpleSendButton(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const transactionConfirmation = new TransactionConfirmation(driver); + await transactionConfirmation.check_dappInitiatedHeadingTitle(); + + // wallet_revokePermissions request + const revokePermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_revokePermissions', + params: [ + { + eth_accounts: {}, + }, + ], + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.executeScript( + `return window.ethereum.request(${revokePermissionsRequest})`, + ); + + // Should have cleared the tx confirmation + await driver.waitUntilXWindowHandles(2); + + // Cleared eth_accounts account label + await testDapp.check_connectedAccounts(DEFAULT_FIXTURE_ACCOUNT, false); + }, + ); + }); +}); diff --git a/test/e2e/tests/request-queuing/sendTx-switchChain-sendTx.spec.js b/test/e2e/tests/request-queuing/sendTx-switchChain-sendTx.spec.js index 5b0c4dca2f07..13d34ecbbb66 100644 --- a/test/e2e/tests/request-queuing/sendTx-switchChain-sendTx.spec.js +++ b/test/e2e/tests/request-queuing/sendTx-switchChain-sendTx.spec.js @@ -21,7 +21,7 @@ describe('Request Queuing Send Tx -> SwitchChain -> SendTx', function () { fixtures: new FixtureBuilder() .withNetworkControllerDoubleGanache() .withPermissionControllerConnectedToTestDapp() - .withPreferencesControllerUseRequestQueueEnabled() + .build(), ganacheOptions: { ...defaultGanacheOptions, diff --git a/test/e2e/tests/request-queuing/switch-network.spec.js b/test/e2e/tests/request-queuing/switch-network.spec.js index 913bdf459a26..8e813869ca6d 100644 --- a/test/e2e/tests/request-queuing/switch-network.spec.js +++ b/test/e2e/tests/request-queuing/switch-network.spec.js @@ -7,178 +7,87 @@ const { regularDelayMs, WINDOW_TITLES, defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing Switch Network on Dapp Send Tx while on different networks.', function () { - describe('Old confirmation screens', function () { - it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPermissionControllerConnectedToTestDapp() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), + it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open dapp + await openDapp(driver, undefined, DAPP_URL); - // Open dapp - await openDapp(driver, undefined, DAPP_URL); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Queue confirm tx should first auto switch network - await driver.clickElement('#sendButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.delay(regularDelayMs); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Confirm Transaction - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement( - '[data-testid="page-container-footer-next"]', - ); + // Queue confirm tx should first auto switch network + await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.navigate(PAGES.HOME); + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Check correct network switched and on the correct network - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8545', - }); + // Confirm Transaction + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); - }); + await driver.delay(regularDelayMs); - describe('Redesigned confirmation screens', function () { - it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPermissionControllerConnectedToTestDapp() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.navigate(PAGES.HOME); - // Open dapp - await openDapp(driver, undefined, DAPP_URL); + // Check correct network switched and on the correct network + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8545', + }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - // Queue confirm tx should first auto switch network - await driver.clickElement('#sendButton'); - - await driver.delay(regularDelayMs); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Confirm Transaction - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.delay(regularDelayMs); - - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.navigate(PAGES.HOME); - - // Check correct network switched and on the correct network - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8545', - }); - - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); + return confirmedTxes.length === 1; + }, 10000); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/switchChain-sendTx.spec.js b/test/e2e/tests/request-queuing/switchChain-sendTx.spec.js index df33600413e1..dd34215fe007 100644 --- a/test/e2e/tests/request-queuing/switchChain-sendTx.spec.js +++ b/test/e2e/tests/request-queuing/switchChain-sendTx.spec.js @@ -16,7 +16,7 @@ describe('Request Queuing SwitchChain -> SendTx', function () { dapp: true, fixtures: new FixtureBuilder() .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() + .build(), ganacheOptions: { ...defaultGanacheOptions, diff --git a/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js b/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js index 5767bd26def5..080d9ef38903 100644 --- a/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js +++ b/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js @@ -20,7 +20,7 @@ describe('Request Queue SwitchChain -> WatchAsset', function () { dapp: true, fixtures: new FixtureBuilder() .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() + .build(), ganacheOptions: { ...defaultGanacheOptions, diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index 707c252396b7..a2b36784e65b 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -11,10 +11,8 @@ const { regularDelayMs, WINDOW_TITLES, defaultGanacheOptions, - tempToggleSettingRedesignedConfirmations, veryLargeDelayMs, DAPP_TWO_URL, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); const { @@ -103,40 +101,6 @@ async function selectDappClickPersonalSign(driver, dappUrl) { await driver.clickElement('#personalSign'); } -async function switchToDialogPopoverValidateDetails(driver, expectedDetails) { - // Switches to the MetaMask Dialog window for confirmation - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.findElement({ - css: '[data-testid="network-display"], [data-testid="signature-request-network-display"]', - text: expectedDetails.networkText, - }); - - await driver.findElement({ - css: '.confirm-page-container-summary__origin bdi, .request-signature__origin .chip__label', - text: expectedDetails.originText, - }); - - // Get state details - await driver.waitForControllersLoaded(); - const notificationWindowState = await driver.executeScript(() => - window.stateHooks?.getCleanAppState?.(), - ); - - const { - metamask: { selectedNetworkClientId, networkConfigurationsByChainId }, - } = notificationWindowState; - - const { chainId } = Object.values(networkConfigurationsByChainId).find( - ({ rpcEndpoints }) => - rpcEndpoints.some( - ({ networkClientId }) => networkClientId === selectedNetworkClientId, - ), - ); - - assert.equal(chainId, expectedDetails.chainId); -} - async function switchToDialogPopoverValidateDetailsRedesign( driver, expectedDetails, @@ -169,13 +133,6 @@ async function switchToDialogPopoverValidateDetailsRedesign( assert.equal(chainId, expectedDetails.chainId); } -async function rejectTransaction(driver) { - await driver.clickElementAndWaitForWindowToClose({ - tag: 'button', - text: 'Reject', - }); -} - async function rejectTransactionRedesign(driver) { await driver.clickElementAndWaitForWindowToClose({ tag: 'button', @@ -230,1036 +187,552 @@ async function validateBalanceAndActivity( } describe('Request-queue UI changes', function () { - describe('Old confirmation screens', function () { - it('should show network specific to domain @no-mmi', async function () { - const port = 8546; - const chainId = 1338; // 0x53a - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - - // Go to the first dapp, ensure it uses localhost - await selectDappClickSend(driver, DAPP_URL); - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - await rejectTransaction(driver); - - // Go to the second dapp, ensure it uses Ethereum Mainnet - await selectDappClickSend(driver, DAPP_ONE_URL); - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x53a', - networkText: 'Localhost 8546', - originText: DAPP_ONE_URL, - }); - await rejectTransaction(driver); - }, - ); - }); - - it('handles three confirmations on three confirmations concurrently @no-mmi', async function () { - const port = 8546; - const chainId = 1338; // 0x53a - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - // Ganache for network 1 - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - // Ganache for network 3 - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 3 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); - - if (!IS_FIREFOX) { - // Open the third dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8'); - } - - // Trigger a send confirmation on the first dapp, do not confirm or reject - await selectDappClickSend(driver, DAPP_URL); - - // Trigger a send confirmation on the second dapp, do not confirm or reject - await selectDappClickSend(driver, DAPP_ONE_URL); - - if (!IS_FIREFOX) { - // Trigger a send confirmation on the third dapp, do not confirm or reject - await selectDappClickSend(driver, DAPP_TWO_URL); - } - - // Switch to the Notification window, ensure first transaction still showing - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - - // Confirm transaction, wait for first confirmation window to close, second to display - await confirmTransaction(driver); - await driver.delay(veryLargeDelayMs); - - // Switch to the new Notification window, ensure second transaction showing - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x53a', - networkText: 'Localhost 8546', - originText: DAPP_ONE_URL, - }); - - // Reject this transaction, wait for second confirmation window to close, third to display - await rejectTransaction(driver); - await driver.delay(veryLargeDelayMs); - - if (!IS_FIREFOX) { - // Switch to the new Notification window, ensure third transaction showing - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x3e8', - networkText: 'Localhost 7777', - originText: DAPP_TWO_URL, - }); - - // Confirm transaction - await confirmTransaction(driver); - } - - // With first and last confirmations confirmed, and second rejected, - // Ensure only first and last network balances were affected - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Wait for transaction to be completed on final confirmation - await driver.delay(veryLargeDelayMs); - - if (!IS_FIREFOX) { - // Start on the last joined network, whose send transaction was just confirmed - await validateBalanceAndActivity(driver, '24.9998'); - } - - // Switch to second network, ensure full balance - await switchToNetworkByName(driver, 'Localhost 8546'); - await validateBalanceAndActivity(driver, '25', 0); - - // Turn on test networks in Networks menu so Localhost 8545 is available - await driver.clickElement('[data-testid="network-display"]'); - await driver.clickElement('.mm-modal-content__dialog .toggle-button'); - await driver.clickElement( - '.mm-modal-content__dialog button[aria-label="Close"]', - ); - - // Switch to first network, whose send transaction was just confirmed - await switchToNetworkByName(driver, 'Localhost 8545'); - await validateBalanceAndActivity(driver, '24.9998'); - }, - ); - }); - - it('should gracefully handle deleted network @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesController({ - preferences: { showTestNetworks: true }, - }) - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - await driver.clickElement('[data-testid="network-display"]'); - - const networkRow = await driver.findElement({ - css: '.multichain-network-list-item', - text: 'Localhost 8545', - }); - - const networkMenu = await driver.findNestedElement( - networkRow, - `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`, - ); - - await networkMenu.click(); - await driver.clickElement( - '[data-testid="network-list-item-options-delete"]', - ); - - await driver.clickElement({ tag: 'button', text: 'Delete' }); - - // Go back to first dapp, try an action, ensure deleted network doesn't block UI - // The current globally selected network, Ethereum Mainnet, should be used - await selectDappClickSend(driver, DAPP_URL); - await driver.delay(veryLargeDelayMs); - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x1', - networkText: 'Ethereum Mainnet', - originText: DAPP_URL, - }); - }, - ); - }); - - it('should autoswitch networks when last confirmation from another network is rejected', async function () { - const port = 8546; - const chainId = 1338; - - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - driverOptions: { constrainWindowSize: true }, - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open tab 2, switch to Ethereum Mainnet - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - await driver.waitForSelector({ - css: '.error-message-text', - text: 'You are on the Ethereum Mainnet.', - }); - await driver.delay(veryLargeDelayMs); - - // Start a Send on Ethereum Mainnet - await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Ensure the confirmation pill shows Ethereum Mainnet - await driver.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Reject the confirmation - await driver.clickElement( - '[data-testid="page-container-footer-cancel"]', - ); - - // Wait for network to automatically change to localhost - await driver.waitForSelector({ - css: '.multichain-app-header__contents--avatar-network .mm-text', - text: 'Localhost 8545', - }); - - // Ensure toast is shown to the user - await driver.waitForSelector({ - css: '.toast-text', - text: 'Localhost 8545 is now active on 127.0.0.1:8080', - }); - }, - ); - }); - - it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - // This test intentionally quits Ganache while the extension is using it, causing - // PollingBlockTracker errors and others. These are expected. - ignoredConsoleErrors: ['ignore-all'], - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer, secondaryGanacheServer }) => { - await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Kill ganache servers - await ganacheServer.quit(); - await secondaryGanacheServer[0].quit(); - - // Go back to first dapp, try an action, ensure network connection failure doesn't block UI - await selectDappClickPersonalSign(driver, DAPP_URL); - - // When the network is down, there is a performance degradation that causes the - // popup to take a few seconds to open in MV3 (issue #25690) - await driver.waitUntilXWindowHandles(4, 1000, 15000); - - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); + it('should show network specific to domain @no-mmi', async function () { + const port = 8546; + const chainId = 1338; // 0x53a + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - ); - }); - - it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - // Presently confirmations take up to 10 seconds to display on a dead network - driverOptions: { timeOut: 30000 }, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - // This test intentionally quits Ganache while the extension is using it, causing - // PollingBlockTracker errors and others. These are expected. - ignoredConsoleErrors: ['ignore-all'], - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer, secondaryGanacheServer }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Kill ganache servers - await ganacheServer.quit(); - await secondaryGanacheServer[0].quit(); - - // Go back to first dapp, try an action, ensure network connection failure doesn't block UI - await selectDappClickSend(driver, DAPP_URL); - - // When the network is down, there is a performance degradation that causes the - // popup to take a few seconds to open in MV3 (issue #25690) - await driver.waitUntilXWindowHandles(4, 1000, 15000); - - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - }, - ); - }); + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + + // Go to the first dapp, ensure it uses localhost + await selectDappClickSend(driver, DAPP_URL); + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + await rejectTransactionRedesign(driver); + + // Go to the second dapp, ensure it uses Ethereum Mainnet + await selectDappClickSend(driver, DAPP_ONE_URL); + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + await rejectTransactionRedesign(driver); + }, + ); }); - describe('Redesigned confirmation screens', function () { - it('should show network specific to domain @no-mmi', async function () { - const port = 8546; - const chainId = 1338; // 0x53a - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - - // Go to the first dapp, ensure it uses localhost - await selectDappClickSend(driver, DAPP_URL); - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - await rejectTransactionRedesign(driver); - - // Go to the second dapp, ensure it uses Ethereum Mainnet - await selectDappClickSend(driver, DAPP_ONE_URL); - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x53a', - networkText: 'Localhost 8546', - originText: DAPP_ONE_URL, - }); - await rejectTransactionRedesign(driver); - }, - ); - }); - - it('handles three confirmations on three confirmations concurrently @no-mmi', async function () { - const port = 8546; - const chainId = 1338; // 0x53a - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - // Ganache for network 1 - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - // Ganache for network 3 - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 3 }, - title: this.test.fullTitle(), + it('handles three confirmations on three confirmations concurrently @no-mmi', async function () { + const port = 8546; + const chainId = 1338; // 0x53a + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + // Ganache for network 1 + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + // Ganache for network 3 + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); - - if (!IS_FIREFOX) { - // Open the third dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8'); - } - - // Trigger a send confirmation on the first dapp, do not confirm or reject - await selectDappClickSend(driver, DAPP_URL); - - // Trigger a send confirmation on the second dapp, do not confirm or reject - await selectDappClickSend(driver, DAPP_ONE_URL); - - if (!IS_FIREFOX) { - // Trigger a send confirmation on the third dapp, do not confirm or reject - await selectDappClickSend(driver, DAPP_TWO_URL); - } - - // Switch to the Notification window, ensure first transaction still showing + dappOptions: { numberOfDapps: 3 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); + + if (!IS_FIREFOX) { + // Open the third dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8'); + } + + // Trigger a send confirmation on the first dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_URL); + + // Trigger a send confirmation on the second dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_ONE_URL); + + if (!IS_FIREFOX) { + // Trigger a send confirmation on the third dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_TWO_URL); + } + + // Switch to the Notification window, ensure first transaction still showing + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + + // Confirm transaction, wait for first confirmation window to close, second to display + await confirmTransaction(driver); + await driver.delay(veryLargeDelayMs); + + // Switch to the new Notification window, ensure second transaction showing + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + + // Reject this transaction, wait for second confirmation window to close, third to display + await rejectTransactionRedesign(driver); + await driver.delay(veryLargeDelayMs); + + if (!IS_FIREFOX) { + // Switch to the new Notification window, ensure third transaction showing await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, + chainId: '0x3e8', + networkText: 'Localhost 7777', + originText: DAPP_TWO_URL, }); - // Confirm transaction, wait for first confirmation window to close, second to display + // Confirm transaction await confirmTransaction(driver); - await driver.delay(veryLargeDelayMs); + } - // Switch to the new Notification window, ensure second transaction showing - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x53a', - networkText: 'Localhost 8546', - originText: DAPP_ONE_URL, - }); - - // Reject this transaction, wait for second confirmation window to close, third to display - await rejectTransactionRedesign(driver); - await driver.delay(veryLargeDelayMs); - - if (!IS_FIREFOX) { - // Switch to the new Notification window, ensure third transaction showing - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x3e8', - networkText: 'Localhost 7777', - originText: DAPP_TWO_URL, - }); - - // Confirm transaction - await confirmTransaction(driver); - } - - // With first and last confirmations confirmed, and second rejected, - // Ensure only first and last network balances were affected - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Wait for transaction to be completed on final confirmation - await driver.delay(veryLargeDelayMs); - - if (!IS_FIREFOX) { - // Start on the last joined network, whose send transaction was just confirmed - await validateBalanceAndActivity(driver, '24.9998'); - } - - // Switch to second network, ensure full balance - await switchToNetworkByName(driver, 'Localhost 8546'); - await validateBalanceAndActivity(driver, '25', 0); - - // Turn on test networks in Networks menu so Localhost 8545 is available - await driver.clickElement('[data-testid="network-display"]'); - await driver.clickElement('.mm-modal-content__dialog .toggle-button'); - await driver.clickElement( - '.mm-modal-content__dialog button[aria-label="Close"]', - ); - - // Switch to first network, whose send transaction was just confirmed - await switchToNetworkByName(driver, 'Localhost 8545'); - await validateBalanceAndActivity(driver, '24.9998'); - }, - ); - }); - - it('should gracefully handle deleted network @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesController({ - preferences: { showTestNetworks: true }, - }) - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - await driver.clickElement('[data-testid="network-display"]'); - - const networkRow = await driver.findElement({ - css: '.multichain-network-list-item', - text: 'Localhost 8545', - }); - - const networkMenu = await driver.findNestedElement( - networkRow, - `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`, - ); - - await networkMenu.click(); - await driver.clickElement( - '[data-testid="network-list-item-options-delete"]', - ); + // With first and last confirmations confirmed, and second rejected, + // Ensure only first and last network balances were affected + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.clickElement({ tag: 'button', text: 'Delete' }); + // Wait for transaction to be completed on final confirmation + await driver.delay(veryLargeDelayMs); - // Go back to first dapp, try an action, ensure deleted network doesn't block UI - // The current globally selected network, Ethereum Mainnet, should be used - await selectDappClickSend(driver, DAPP_URL); - await driver.delay(veryLargeDelayMs); - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x1', - networkText: 'Ethereum Mainnet', - originText: DAPP_URL, - }); - }, - ); - }); - - it('should signal from UI to dapp the network change @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - driverOptions: { constrainWindowSize: true }, - }, - async ({ driver }) => { - // Navigate to extension home screen - await unlockWallet(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Ensure the dapp starts on the correct network - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x539', - }); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Switch to mainnet - await switchToNetworkByName(driver, 'Ethereum Mainnet'); - - // Switch back to the Dapp tab - await driver.switchToWindowWithUrl(DAPP_URL); - - // Check to make sure the dapp network changed - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x1', - }); - }, - ); - }); - - it('should autoswitch networks to the last used network for domain', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - // Open fullscreen - await unlockWallet(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open tab 2, switch to Ethereum Mainnet - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Ensure network was reset to original - await driver.findElement({ - css: '.multichain-app-header__contents--avatar-network .mm-text', - text: 'Localhost 8545', - }); - - // Ensure toast is shown to the user - await driver.findElement({ - css: '.toast-text', - text: 'Localhost 8545 is now active on 127.0.0.1:8080', - }); - }, - ); - }); + if (!IS_FIREFOX) { + // Start on the last joined network, whose send transaction was just confirmed + await validateBalanceAndActivity(driver, '24.9998'); + } + + // Switch to second network, ensure full balance + await switchToNetworkByName(driver, 'Localhost 8546'); + await validateBalanceAndActivity(driver, '25', 0); + + // Turn on test networks in Networks menu so Localhost 8545 is available + await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement('.mm-modal-content__dialog .toggle-button'); + await driver.clickElement( + '.mm-modal-content__dialog button[aria-label="Close"]', + ); + + // Switch to first network, whose send transaction was just confirmed + await switchToNetworkByName(driver, 'Localhost 8545'); + await validateBalanceAndActivity(driver, '24.9998'); + }, + ); + }); - it('should autoswitch networks when last confirmation from another network is rejected', async function () { - const port = 8546; - const chainId = 1338; - - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - driverOptions: { constrainWindowSize: true }, + it('should gracefully handle deleted network @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesController({ + preferences: { showTestNetworks: true }, + }) + + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open tab 2, switch to Ethereum Mainnet - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - await driver.waitForSelector({ - css: '.error-message-text', - text: 'You are on the Ethereum Mainnet.', - }); - await driver.delay(veryLargeDelayMs); - - // Start a Send on Ethereum Mainnet - await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Ensure the confirmation pill shows Ethereum Mainnet - await driver.waitForSelector({ - css: 'p', - text: 'Ethereum Mainnet', - }); - - // Reject the confirmation - await driver.clickElement({ css: 'button', text: 'Cancel' }); - - // Wait for network to automatically change to localhost - await driver.waitForSelector({ - css: '.multichain-app-header__contents--avatar-network .mm-text', - text: 'Localhost 8545', - }); + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + await driver.clickElement('[data-testid="network-display"]'); + + const networkRow = await driver.findElement({ + css: '.multichain-network-list-item', + text: 'Localhost 8545', + }); + + const networkMenu = await driver.findNestedElement( + networkRow, + `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`, + ); + + await networkMenu.click(); + await driver.clickElement( + '[data-testid="network-list-item-options-delete"]', + ); + + await driver.clickElement({ tag: 'button', text: 'Delete' }); + + // Go back to first dapp, try an action, ensure deleted network doesn't block UI + // The current globally selected network, Ethereum Mainnet, should be used + await selectDappClickSend(driver, DAPP_URL); + await driver.delay(veryLargeDelayMs); + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x1', + networkText: 'Ethereum Mainnet', + originText: DAPP_URL, + }); + }, + ); + }); - // Ensure toast is shown to the user - await driver.waitForSelector({ - css: '.toast-text', - text: 'Localhost 8545 is now active on 127.0.0.1:8080', - }); - }, - ); - }); + it('should signal from UI to dapp the network change @no-mmi', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + driverOptions: { constrainWindowSize: true }, + }, + async ({ driver }) => { + // Navigate to extension home screen + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Ensure the dapp starts on the correct network + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x539', + }); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Switch to mainnet + await switchToNetworkByName(driver, 'Ethereum Mainnet'); + + // Switch back to the Dapp tab + await driver.switchToWindowWithUrl(DAPP_URL); + + // Check to make sure the dapp network changed + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x1', + }); + }, + ); + }); - it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - // This test intentionally quits Ganache while the extension is using it, causing - // PollingBlockTracker errors and others. These are expected. - ignoredConsoleErrors: ['ignore-all'], - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), + it('should autoswitch networks to the last used network for domain', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver, ganacheServer, secondaryGanacheServer }) => { - await unlockWallet(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Kill ganache servers - await ganacheServer.quit(); - await secondaryGanacheServer[0].quit(); - - // Go back to first dapp, try an action, ensure network connection failure doesn't block UI - await selectDappClickPersonalSign(driver, DAPP_URL); - - // When the network is down, there is a performance degradation that causes the - // popup to take a few seconds to open in MV3 (issue #25690) - await driver.waitUntilXWindowHandles(4, 1000, 15000); + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + // Open fullscreen + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure network was reset to original + await driver.findElement({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.findElement({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); + }, + ); + }); - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); + it('should autoswitch networks when last confirmation from another network is rejected', async function () { + const port = 8546; + const chainId = 1338; + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - ); - }); + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + driverOptions: { constrainWindowSize: true }, + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + await driver.waitForSelector({ + css: '.error-message-text', + text: 'You are on the Ethereum Mainnet.', + }); + await driver.delay(veryLargeDelayMs); + + // Start a Send on Ethereum Mainnet + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure the confirmation pill shows Ethereum Mainnet + await driver.waitForSelector({ + css: 'p', + text: 'Ethereum Mainnet', + }); + + // Reject the confirmation + await driver.clickElement({ css: 'button', text: 'Cancel' }); + + // Wait for network to automatically change to localhost + await driver.waitForSelector({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.waitForSelector({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); + }, + ); + }); - it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - // Presently confirmations take up to 10 seconds to display on a dead network - driverOptions: { timeOut: 30000 }, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - // This test intentionally quits Ganache while the extension is using it, causing - // PollingBlockTracker errors and others. These are expected. - ignoredConsoleErrors: ['ignore-all'], - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), + it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - async ({ driver, ganacheServer, secondaryGanacheServer }) => { - await unlockWallet(driver); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Kill ganache servers - await ganacheServer.quit(); - await secondaryGanacheServer[0].quit(); - - // Go back to first dapp, try an action, ensure network connection failure doesn't block UI - await selectDappClickSend(driver, DAPP_URL); - - // When the network is down, there is a performance degradation that causes the - // popup to take a few seconds to open in MV3 (issue #25690) - await driver.waitUntilXWindowHandles(4, 1000, 15000); + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors and others. These are expected. + ignoredConsoleErrors: ['ignore-all'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.waitForSelector({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickPersonalSign(driver, DAPP_URL); + + // When the network is down, there is a performance degradation that causes the + // popup to take a few seconds to open in MV3 (issue #25690) + await driver.waitUntilXWindowHandles(4, 1000, 15000); + + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); - await switchToDialogPopoverValidateDetailsRedesign(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); + it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + // Presently confirmations take up to 10 seconds to display on a dead network + driverOptions: { timeOut: 30000 }, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, - ); - }); + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors and others. These are expected. + ignoredConsoleErrors: ['ignore-all'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickSend(driver, DAPP_URL); + + // When the network is down, there is a performance degradation that causes the + // popup to take a few seconds to open in MV3 (issue #25690) + await driver.waitUntilXWindowHandles(4, 1000, 15000); + + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); }); }); diff --git a/test/e2e/tests/request-queuing/watchAsset-switchChain-watchAsset.spec.js b/test/e2e/tests/request-queuing/watchAsset-switchChain-watchAsset.spec.js index 64ac781a20e0..c60d1d0a9278 100644 --- a/test/e2e/tests/request-queuing/watchAsset-switchChain-watchAsset.spec.js +++ b/test/e2e/tests/request-queuing/watchAsset-switchChain-watchAsset.spec.js @@ -22,7 +22,7 @@ describe('Request Queue WatchAsset -> SwitchChain -> WatchAsset', function () { fixtures: new FixtureBuilder() .withNetworkControllerDoubleGanache() .withPermissionControllerConnectedToTestDapp() - .withPreferencesControllerUseRequestQueueEnabled() + .build(), ganacheOptions: { ...defaultGanacheOptions, diff --git a/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js b/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js index c1ff9f3477ab..4c682fafa402 100644 --- a/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js @@ -6,7 +6,6 @@ const { logInWithBalanceValidation, openActionMenuAndStartSendFlow, withFixtures, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -96,7 +95,7 @@ describe('MetaMask Responsive UI', function () { // Import Secret Recovery Phrase await driver.waitForSelector({ - tag: 'span', + tag: 'p', text: 'Localhost 8545', }); await driver.clickElement({ @@ -131,8 +130,6 @@ describe('MetaMask Responsive UI', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Send ETH from inside MetaMask // starts to send a transaction await openActionMenuAndStartSendFlow(driver); @@ -149,8 +146,8 @@ describe('MetaMask Responsive UI', function () { // wait for transaction value to be rendered and confirm await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '1.000042', + css: 'h2', + text: '1 ETH', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); diff --git a/test/e2e/tests/settings/4byte-directory.spec.js b/test/e2e/tests/settings/4byte-directory.spec.js deleted file mode 100644 index b36f72f0575c..000000000000 --- a/test/e2e/tests/settings/4byte-directory.spec.js +++ /dev/null @@ -1,100 +0,0 @@ -const FixtureBuilder = require('../../fixture-builder'); -const { - logInWithBalanceValidation, - openDapp, - openMenuSafe, - unlockWallet, - withFixtures, - WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); - -describe('4byte setting', function () { - it('makes a call to 4byte when the setting is on', async function () { - const smartContract = SMART_CONTRACTS.PIGGYBANK; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // deploy contract - await openDapp(driver, contractAddress); - - // wait for deployed contract, calls and confirms a contract method where ETH is sent - await driver.clickElement('#depositButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - tag: 'span', - text: 'Deposit', - }); - await driver.assertElementNotPresent({ - tag: 'span', - text: 'Contract interaction', - }); - }, - ); - }); - - it('does not try to get contract method name from 4byte when the setting is off', async function () { - const smartContract = SMART_CONTRACTS.PIGGYBANK; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // goes to the settings screen - await openMenuSafe(driver); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Security & privacy', tag: 'div' }); - - // turns off 4Byte Directory contract method name resolution - await driver.clickElement( - '[data-testid="4byte-resolution-container"] .toggle-button', - ); - - // deploy contract - await openDapp(driver, contractAddress); - - // wait for deployed contract, calls and confirms a contract method where ETH is sent - await driver.clickElement('#depositButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.assertElementNotPresent({ - tag: 'span', - text: 'Deposit', - }); - await driver.waitForSelector({ - tag: 'span', - text: 'Contract interaction', - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/settings/change-language.spec.ts b/test/e2e/tests/settings/change-language.spec.ts index 1bd9915a33da..234f83eb1271 100644 --- a/test/e2e/tests/settings/change-language.spec.ts +++ b/test/e2e/tests/settings/change-language.spec.ts @@ -1,21 +1,17 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; - import { Driver } from '../../webdriver/driver'; -import { - defaultGanacheOptions, - withFixtures, - unlockWallet, -} from '../../helpers'; +import { withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; +import AdvancedSettings from '../../page-objects/pages/settings/advanced-settings'; +import GeneralSettings from '../../page-objects/pages/settings/general-settings'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import Homepage from '../../page-objects/pages/home/homepage'; +import SendTokenPage from '../../page-objects/pages/send/send-token-page'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; const selectors = { - accountOptionsMenuButton: '[data-testid="account-options-menu-button"]', - settingsOption: { text: 'Settings', tag: 'div' }, - localeSelect: '[data-testid="locale-select"]', - ethOverviewSend: '[data-testid="eth-overview-send"]', - ensInput: '[data-testid="ens-input"]', - nftsTab: '[data-testid="account-overview__nfts-tab"]', labelSpanish: { tag: 'p', text: 'Idioma actual' }, currentLanguageLabel: { tag: 'p', text: 'Current language' }, advanceText: { text: 'Avanceret', tag: 'div' }, @@ -29,50 +25,37 @@ const selectors = { headerText: { text: 'الإعدادات', tag: 'h3' }, }; -async function changeLanguage(driver: Driver, languageIndex: number) { - await driver.clickElement(selectors.accountOptionsMenuButton); - await driver.clickElement(selectors.settingsOption); - - const dropdownElement = await driver.findElement(selectors.localeSelect); - await dropdownElement.click(); - - const options = await dropdownElement.findElements({ css: 'option' }); - await options[languageIndex].click(); -} - describe('Settings - general tab @no-mmi', function (this: Suite) { it('validate the change language functionality', async function () { - let languageIndex = 10; - await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, - async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await changeLanguage(driver, languageIndex); + await loginWithBalanceValidation(driver); + await new HeaderNavbar(driver).openSettingsPage(); + const generalSettings = new GeneralSettings(driver); + await generalSettings.check_pageIsLoaded(); - // Validate the label changes to Spanish + // Change language to Spanish and validate that the word has changed correctly + await generalSettings.changeLanguage('Español'); const isLanguageLabelChanged = await driver.isElementPresent( selectors.labelSpanish, ); assert.equal(isLanguageLabelChanged, true, 'Language did not change'); + // Refresh the page and validate that the language is still Spanish await driver.refresh(); - - // Change back to English and verify that the word is correctly changed back to English - languageIndex = 9; - - const dropdownElement = await driver.findElement( - selectors.localeSelect, + await generalSettings.check_pageIsLoaded(); + assert.equal( + await driver.isElementPresent(selectors.labelSpanish), + true, + 'Language did not change after refresh', ); - await dropdownElement.click(); - const options = await dropdownElement.findElements({ css: 'option' }); - await options[languageIndex].click(); + // Change language back to English and validate that the word has changed correctly + await generalSettings.changeLanguage('English'); const isLabelTextChanged = await driver.isElementPresent( selectors.currentLanguageLabel, ); @@ -82,21 +65,22 @@ describe('Settings - general tab @no-mmi', function (this: Suite) { }); it('validate "Dansk" language on page navigation', async function () { - const languageIndex = 6; await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, - async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await changeLanguage(driver, languageIndex); - - await driver.assertElementNotPresent('.loading-overlay__spinner'); + await loginWithBalanceValidation(driver); + await new HeaderNavbar(driver).openSettingsPage(); + const generalSettings = new GeneralSettings(driver); + await generalSettings.check_pageIsLoaded(); + // Select "Dansk" language + await generalSettings.changeLanguage('Dansk'); await driver.clickElement(selectors.advanceText); + const advancedSettings = new AdvancedSettings(driver); + await advancedSettings.check_pageIsLoaded(); // Confirm that the language change is reflected in search box water text const isWaterTextChanged = await driver.isElementPresent( @@ -132,22 +116,30 @@ describe('Settings - general tab @no-mmi', function (this: Suite) { }); it('validate "Deutsch" language on error messages', async function () { - const languageIndex = 7; await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, - async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await changeLanguage(driver, languageIndex); - await driver.navigate(); - await driver.clickElement(selectors.ethOverviewSend); - await driver.pasteIntoField( - selectors.ensInput, - // use wrong checksum address; other inputs don't show error until snaps name-lookup has happened + await loginWithBalanceValidation(driver); + await new HeaderNavbar(driver).openSettingsPage(); + const generalSettings = new GeneralSettings(driver); + await generalSettings.check_pageIsLoaded(); + + // Select "Deutsch" language + await generalSettings.changeLanguage('Deutsch'); + await new SettingsPage(driver).closeSettingsPage(); + + const homepage = new Homepage(driver); + await homepage.check_pageIsLoaded(); + await homepage.check_expectedBalanceIsDisplayed(); + await homepage.startSendFlow(); + + const sendToPage = new SendTokenPage(driver); + await sendToPage.check_pageIsLoaded(); + // use wrong address for recipient to allow error message to show + await sendToPage.fillRecipient( '0xAAAA6BF26964aF9D7eEd9e03E53415D37aA96045', ); @@ -165,18 +157,23 @@ describe('Settings - general tab @no-mmi', function (this: Suite) { }); it('validate "मानक हिन्दी" language on tooltips', async function () { - const languageIndex = 19; await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, - async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await changeLanguage(driver, languageIndex); - await driver.navigate(); + await loginWithBalanceValidation(driver); + await new HeaderNavbar(driver).openSettingsPage(); + const generalSettings = new GeneralSettings(driver); + await generalSettings.check_pageIsLoaded(); + + // Select "मानक हिन्दी" language + await generalSettings.changeLanguage('मानक हिन्दी'); + await new SettingsPage(driver).closeSettingsPage(); + const homepage = new Homepage(driver); + await homepage.check_pageIsLoaded(); + await homepage.check_expectedBalanceIsDisplayed(); // Validate the account tooltip const isAccountTooltipChanged = await driver.isElementPresent( @@ -202,20 +199,24 @@ describe('Settings - general tab @no-mmi', function (this: Suite) { }); it('validate "Magyar" language change on hypertext', async function () { - const languageIndex = 23; await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, - async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - // selects "Magyar" language - await changeLanguage(driver, languageIndex); - await driver.navigate(); - await driver.clickElement(selectors.nftsTab); + await loginWithBalanceValidation(driver); + await new HeaderNavbar(driver).openSettingsPage(); + const generalSettings = new GeneralSettings(driver); + await generalSettings.check_pageIsLoaded(); + + // Select "Magyar" language + await generalSettings.changeLanguage('Magyar'); + await new SettingsPage(driver).closeSettingsPage(); + const homepage = new Homepage(driver); + await homepage.check_pageIsLoaded(); + await homepage.check_expectedBalanceIsDisplayed(); + await homepage.goToNftTab(); // Validate the hypertext const isHyperTextChanged = await driver.isElementPresent( @@ -231,18 +232,20 @@ describe('Settings - general tab @no-mmi', function (this: Suite) { }); it('validate "العربية" language change on page indent', async function () { - const languageIndex = 1; await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await changeLanguage(driver, languageIndex); + await loginWithBalanceValidation(driver); + await new HeaderNavbar(driver).openSettingsPage(); + const generalSettings = new GeneralSettings(driver); + await generalSettings.check_pageIsLoaded(); + + // Select "العربية" language and validate that the header text has changed + await generalSettings.changeLanguage('العربية'); - // Validate the header text const isHeaderTextChanged = await driver.isElementPresent( selectors.headerText, ); 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 6ee57ae02490..eb9739e67bce 100644 --- a/test/e2e/tests/settings/settings-security-reveal-srp.spec.ts +++ b/test/e2e/tests/settings/settings-security-reveal-srp.spec.ts @@ -2,7 +2,7 @@ import { withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import { E2E_SRP } from '../../default-fixture'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import PrivacySettings from '../../page-objects/pages/settings/privacy-settings'; import SettingsPage from '../../page-objects/pages/settings/settings-page'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; diff --git a/test/e2e/tests/settings/show-hex-data.spec.js b/test/e2e/tests/settings/show-hex-data.spec.js deleted file mode 100644 index 353847a544b4..000000000000 --- a/test/e2e/tests/settings/show-hex-data.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -const { - defaultGanacheOptions, - withFixtures, - logInWithBalanceValidation, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -const selectors = { - accountOptionsMenu: '[data-testid="account-options-menu-button"]', - settingsDiv: '[data-testid="global-menu-settings"]', - portfolioMenuOption: '[data-testid="global-menu-mmi-portfolio"]', - advancedDiv: { text: 'Advanced', tag: 'div' }, - hexDataToggle: '[data-testid="advanced-setting-hex-data"] .toggle-button', - appHeaderLogo: '[data-testid="app-header-logo"]', - ethOverviewSend: '[data-testid="eth-overview-send"]', - ensInput: '[data-testid="ens-input"]', - quantity: 'input[placeholder="0"]', - hexDataInput: '[data-testid="send-hex-textarea"]', - nextPageButton: { text: 'Continue', tag: 'button' }, - hexButton: { text: 'Hex', tag: 'button' }, - detailsTab: { text: 'Details', tag: 'button' }, - containerContent: '.confirm-page-container-content', - confirmButton: { text: 'Confirm', tag: 'button' }, -}; - -const inputData = { - recipientAddress: '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', - hexDataText: '0x0abc', -}; - -// Function to click elements in sequence -async function clickElementsInSequence(driver, clickSelectors) { - for (const selector of clickSelectors) { - if (process.env.MMI && selector === selectors.settingsDiv) { - await driver.waitForSelector(selectors.portfolioMenuOption); - } else { - await driver.waitForSelector(selector); - } - await driver.clickElement(selector); - } -} - -// Function to perform the hex data toggle -async function toggleHexData(driver) { - const sequence = [ - selectors.accountOptionsMenu, - selectors.settingsDiv, - selectors.advancedDiv, - selectors.hexDataToggle, - ]; - - await clickElementsInSequence(driver, sequence); -} - -// Function to click on the app logo -async function clickOnLogo(driver) { - await driver.clickElement(selectors.appHeaderLogo); -} - -// Function to send a transaction and verify hex data text area appears. -async function sendTransactionAndVerifyHexData(driver) { - await driver.clickElement(selectors.ethOverviewSend); - await driver.fill(selectors.ensInput, inputData.recipientAddress); - await driver.fill(selectors.quantity, 1); - await driver.fill(selectors.hexDataInput, inputData.hexDataText); - await driver.clickElement(selectors.nextPageButton); - await driver.clickElement(selectors.hexButton); -} - -// Main test suite -describe('Check the toggle for hex data', function () { - it('Setting the hex data toggle and verify that the textbox appears', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await toggleHexData(driver); - await clickOnLogo(driver); - await sendTransactionAndVerifyHexData(driver); - - // Verify hex data in the container content - await driver.waitForSelector({ - tag: 'p', - text: '0x0abc', - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/signature/personal-sign.spec.js b/test/e2e/tests/signature/personal-sign.spec.js index e4864a84db47..b4c42d3fd982 100644 --- a/test/e2e/tests/signature/personal-sign.spec.js +++ b/test/e2e/tests/signature/personal-sign.spec.js @@ -1,55 +1,16 @@ const { strict: assert } = require('assert'); +const { By } = require('selenium-webdriver'); const { defaultGanacheOptions, withFixtures, openDapp, regularDelayMs, - tempToggleSettingRedesignedConfirmations, unlockWallet, WINDOW_TITLES, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); describe('Personal sign', function () { - it('can initiate and confirm a personal sign', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); - - await openDapp(driver); - await driver.clickElement('#personalSign'); - - await driver.waitUntilXWindowHandles(3); - const windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - const personalMessageRow = await driver.findElement( - '.request-signature__row-value', - ); - const personalMessage = await personalMessageRow.getText(); - assert.equal(personalMessage, 'Example `personal_sign` message'); - - await driver.clickElement('[data-testid="page-container-footer-next"]'); - - await verifyAndAssertPersonalMessage(driver, publicAddress); - }, - ); - }); - it('can queue multiple personal signs and confirm', async function () { await withFixtures( { @@ -64,7 +25,6 @@ describe('Personal sign', function () { const addresses = await ganacheServer.getAccounts(); const publicAddress = addresses[0]; await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); // Create personal sign @@ -84,22 +44,25 @@ describe('Personal sign', function () { windowHandles, ); + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + await driver.waitForSelector({ - text: 'Reject 2 requests', + text: 'Reject all', tag: 'button', }); - const personalMessageRow = await driver.findElement( - '.request-signature__row-value', - ); - const personalMessage = await personalMessageRow.getText(); - assert.equal(personalMessage, 'Example `personal_sign` message'); + await driver.findElement({ + css: 'p', + text: 'Example `personal_sign` message', + }); // Confirm first personal sign - await driver.clickElement('[data-testid="page-container-footer-next"]'); + await driver.clickElement('[data-testid="confirm-footer-button"]'); await driver.delay(regularDelayMs); // Confirm second personal sign - await driver.clickElement('[data-testid="page-container-footer-next"]'); + await driver.clickElement('[data-testid="confirm-footer-button"]'); await verifyAndAssertPersonalMessage(driver, publicAddress); }, diff --git a/test/e2e/tests/signature/signature-request.spec.js b/test/e2e/tests/signature/signature-request.spec.js index 99fcb61c5067..477bcc12bf47 100644 --- a/test/e2e/tests/signature/signature-request.spec.js +++ b/test/e2e/tests/signature/signature-request.spec.js @@ -1,11 +1,10 @@ const { strict: assert } = require('assert'); +const { By } = require('selenium-webdriver'); const { withFixtures, regularDelayMs, openDapp, - DAPP_URL, defaultGanacheOptions, - tempToggleSettingRedesignedConfirmations, unlockWallet, WINDOW_TITLES, } = require('../../helpers'); @@ -63,67 +62,6 @@ const testData = [ ]; describe('Sign Typed Data Signature Request', function () { - testData.forEach((data) => { - it(`can initiate and confirm a Signature Request of ${data.type}`, async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); - - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement(data.buttonId); - - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - await verifyAndAssertSignTypedData( - driver, - data.type, - data.verifyAndAssertMessage.titleClass, - data.verifyAndAssertMessage.originClass, - data.verifyAndAssertMessage.messageClass, - data.expectedMessage, - ); - - // Approve signing typed data - await finalizeSignatureRequest( - driver, - data.type, - '[data-testid="signature-request-scroll-button"]', - 'Sign', - ); - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); - - // switch to the Dapp and verify the signed address - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - await driver.clickElement(data.verifyId); - const recoveredAddress = await driver.findElement( - data.verifyResultId, - ); - - assert.equal(await recoveredAddress.getText(), publicAddress); - }, - ); - }); - }); - testData.forEach((data) => { it(`can queue multiple Signature Requests of ${data.type} and confirm`, async function () { await withFixtures( @@ -139,7 +77,6 @@ describe('Sign Typed Data Signature Request', function () { const addresses = await ganacheServer.getAccounts(); const publicAddress = addresses[0]; await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); @@ -158,35 +95,33 @@ describe('Sign Typed Data Signature Request', function () { windowHandles, ); + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); + await driver.waitForSelector({ - text: 'Reject 2 requests', + text: 'Reject all', tag: 'button', }); - await verifyAndAssertSignTypedData( + await verifyAndAssertRedesignedSignTypedData( driver, - data.type, - data.verifyAndAssertMessage.titleClass, - data.verifyAndAssertMessage.originClass, - data.verifyAndAssertMessage.messageClass, data.expectedMessage, ); - // approve first signature request + // Approve signing typed data await finalizeSignatureRequest( driver, - data.type, - '[data-testid="signature-request-scroll-button"]', - 'Sign', + '.confirm-scroll-to-bottom__button', + 'Confirm', ); await driver.waitUntilXWindowHandles(3); - // approve second signature request + // Approve signing typed data await finalizeSignatureRequest( driver, - data.type, - '[data-testid="signature-request-scroll-button"]', - 'Sign', + '.confirm-scroll-to-bottom__button', + 'Confirm', ); await driver.waitUntilXWindowHandles(2); @@ -215,7 +150,6 @@ describe('Sign Typed Data Signature Request', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); @@ -232,9 +166,8 @@ describe('Sign Typed Data Signature Request', function () { // Reject signing typed data await finalizeSignatureRequest( driver, - data.type, - '[data-testid="signature-request-scroll-button"]', - 'Reject', + '.confirm-scroll-to-bottom__button', + 'Cancel', ); await driver.waitUntilXWindowHandles(2); windowHandles = await driver.getAllWindowHandles(); @@ -269,7 +202,6 @@ describe('Sign Typed Data Signature Request', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); await openDapp(driver); @@ -288,26 +220,28 @@ describe('Sign Typed Data Signature Request', function () { windowHandles, ); + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); + await driver.waitForSelector({ - text: 'Reject 2 requests', + text: 'Reject all', tag: 'button', }); // reject first signature request await finalizeSignatureRequest( driver, - data.type, - '[data-testid="signature-request-scroll-button"]', - 'Reject', + '.confirm-scroll-to-bottom__button', + 'Cancel', ); await driver.waitUntilXWindowHandles(3); // reject second signature request await finalizeSignatureRequest( driver, - data.type, - '[data-testid="signature-request-scroll-button"]', - 'Reject', + '.confirm-scroll-to-bottom__button', + 'Cancel', ); await driver.waitUntilXWindowHandles(2); @@ -328,42 +262,27 @@ describe('Sign Typed Data Signature Request', function () { }); }); -async function verifyAndAssertSignTypedData( - driver, - type, - titleClass, - originClass, - messageClass, - expectedMessage, -) { - const title = await driver.findElement(titleClass); - const origin = await driver.findElement(originClass); - - assert.equal(await title.getText(), 'Signature request'); - assert.equal(await origin.getText(), DAPP_URL); - - const messages = await driver.findElements(messageClass); - if (type !== signatureRequestType.signTypedData) { - const verifyContractDetailsButton = await driver.findElement( - '.signature-request-content__verify-contract-details', - ); - verifyContractDetailsButton.click(); - await driver.findElement({ text: 'Third-party details', tag: 'h5' }); - await driver.findElement('[data-testid="recipient"]'); - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - } - const messageNumber = type === signatureRequestType.signTypedDataV3 ? 4 : 0; - assert.equal(await messages[messageNumber].getText(), expectedMessage); +async function verifyAndAssertRedesignedSignTypedData(driver, expectedMessage) { + await driver.findElement({ + css: 'h2', + text: 'Signature request', + }); + + await driver.findElement({ + css: 'p', + text: '127.0.0.1:8080', + }); + + await driver.findElement({ + css: 'p', + text: expectedMessage, + }); } -async function finalizeSignatureRequest(driver, type, buttonElementId, action) { - if (type !== signatureRequestType.signTypedData) { - await driver.delay(regularDelayMs); - await driver.clickElement(buttonElementId); - } +async function finalizeSignatureRequest(driver, buttonElementId, action) { + await driver.delay(regularDelayMs); + await driver.clickElementSafe(buttonElementId); + await driver.delay(regularDelayMs); await driver.clickElement({ text: action, tag: 'button' }); } diff --git a/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts b/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts index 78d1497dc9cf..5792e4ed4d30 100644 --- a/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts +++ b/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts @@ -11,7 +11,6 @@ const GET_FEES_REQUEST_INCLUDES = { from: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', to: '0x881D40237659C251811CEC9c364ef91dC08D300C', value: '0x1bc16d674ec80000', - gas: '0xf4240', nonce: '0x0', }, ], @@ -60,7 +59,6 @@ const GET_BATCH_STATUS_RESPONSE_PENDING = { minedTx: 'not_mined', wouldRevertMessage: null, minedHash: '', - duplicated: false, timedOut: false, proxied: false, type: 'sentinel', @@ -77,7 +75,6 @@ const GET_BATCH_STATUS_RESPONSE_SUCCESS = { wouldRevertMessage: null, minedHash: '0xec9d6214684d6dc191133ae4a7ec97db3e521fff9cfe5c4f48a84cb6c93a5fa5', - duplicated: true, timedOut: true, proxied: false, type: 'sentinel', diff --git a/test/e2e/tests/smart-transactions/smart-transactions.spec.ts b/test/e2e/tests/smart-transactions/smart-transactions.spec.ts index 36324b9ea797..b2bb04a68b44 100644 --- a/test/e2e/tests/smart-transactions/smart-transactions.spec.ts +++ b/test/e2e/tests/smart-transactions/smart-transactions.spec.ts @@ -64,7 +64,7 @@ export const waitForTransactionToComplete = async ( }; describe('smart transactions @no-mmi', function () { - it('Completes a Swap', async function () { + it.skip('Completes a Swap', async function () { await withFixturesForSmartTransactions( { title: this.test?.fullTitle(), diff --git a/test/e2e/tests/swaps/shared.ts b/test/e2e/tests/swaps/shared.ts index 3f3aff4447e5..fa55b3a7f0a8 100644 --- a/test/e2e/tests/swaps/shared.ts +++ b/test/e2e/tests/swaps/shared.ts @@ -190,6 +190,12 @@ export const checkActivityTransaction = async ( await driver.clickElement('[data-testid="popover-close"]'); }; +export const closeSmartTransactionsMigrationNotification = async ( + driver: Driver, +) => { + await driver.clickElement('[aria-label="Close"]'); +}; + export const checkNotification = async ( driver: Driver, options: { title: string; text: string }, @@ -216,9 +222,21 @@ export const checkNotification = async ( }; export const changeExchangeRate = async (driver: Driver) => { + // Ensure quote view button is present + await driver.waitForSelector('[data-testid="review-quote-view-all-quotes"]'); + + // Scroll button into view before clicking + await driver.executeScript(` + const element = document.querySelector('[data-testid="review-quote-view-all-quotes"]'); + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + `); + + // Add small delay allowing for smooth scroll + await driver.delay(500); + + // Try to click the element await driver.clickElement('[data-testid="review-quote-view-all-quotes"]'); await driver.waitForSelector({ text: 'Quote details', tag: 'h2' }); - const networkFees = await driver.findElements( '[data-testid*="select-quote-popover-row"]', ); diff --git a/test/e2e/tests/swaps/swap-eth.spec.ts b/test/e2e/tests/swaps/swap-eth.spec.ts index 18d049e5de16..376d86fd2852 100644 --- a/test/e2e/tests/swaps/swap-eth.spec.ts +++ b/test/e2e/tests/swaps/swap-eth.spec.ts @@ -7,53 +7,54 @@ import { checkActivityTransaction, changeExchangeRate, mockEthDaiTrade, + closeSmartTransactionsMigrationNotification, } from './shared'; +// TODO: (MM-PENDING) These tests are planned for deprecation as part of swaps testing revamp describe('Swap Eth for another Token @no-mmi', function () { - it('Completes second Swaps while first swap is processing', async function () { - withFixturesOptions.ganacheOptions.miner.blockTime = 10; - + it('Completes a Swap between ETH and DAI after changing initial rate', async function () { await withFixtures( { ...withFixturesOptions, + testSpecificMock: mockEthDaiTrade, title: this.test?.fullTitle(), }, async ({ driver }) => { await unlockWallet(driver); + await buildQuote(driver, { - amount: 0.001, - swapTo: 'USDC', + amount: 2, + swapTo: 'DAI', }); + + // Close the STX notification immediately after buildQuote + // This ensures the UI is clear before we proceed with quote review + await closeSmartTransactionsMigrationNotification(driver); + await reviewQuote(driver, { - amount: 0.001, + amount: 2, swapFrom: 'TESTETH', - swapTo: 'USDC', - }); - await driver.clickElement({ text: 'Swap', tag: 'button' }); - await driver.clickElement({ text: 'View in activity', tag: 'button' }); - await buildQuote(driver, { - amount: 0.003, swapTo: 'DAI', }); + + // The changeExchangeRate function now includes scrolling logic + await changeExchangeRate(driver); + await reviewQuote(driver, { - amount: 0.003, + amount: 2, swapFrom: 'TESTETH', swapTo: 'DAI', + skipCounter: true, }); + await driver.clickElement({ text: 'Swap', tag: 'button' }); await waitForTransactionToComplete(driver, { tokenName: 'DAI' }); await checkActivityTransaction(driver, { index: 0, - amount: '0.003', + amount: '2', swapFrom: 'TESTETH', swapTo: 'DAI', }); - await checkActivityTransaction(driver, { - index: 1, - amount: '0.001', - swapFrom: 'TESTETH', - swapTo: 'USDC', - }); }, ); }); diff --git a/test/e2e/tests/swaps/swaps-notifications.spec.ts b/test/e2e/tests/swaps/swaps-notifications.spec.ts index 134741d3683c..835b86d277e6 100644 --- a/test/e2e/tests/swaps/swaps-notifications.spec.ts +++ b/test/e2e/tests/swaps/swaps-notifications.spec.ts @@ -6,6 +6,7 @@ import { buildQuote, reviewQuote, checkNotification, + closeSmartTransactionsMigrationNotification, } from './shared'; async function mockSwapsTransactionQuote(mockServer: Mockttp) { @@ -80,6 +81,7 @@ describe('Swaps - notifications @no-mmi', function () { amount: 2, swapTo: 'INUINU', }); + await closeSmartTransactionsMigrationNotification(driver); await checkNotification(driver, { title: 'Potentially inauthentic token', text: 'INUINU is only verified on 1 source. Consider verifying it on Etherscan before proceeding.', diff --git a/test/e2e/tests/tokens/add-hide-token.spec.js b/test/e2e/tests/tokens/add-hide-token.spec.js deleted file mode 100644 index 011d52849feb..000000000000 --- a/test/e2e/tests/tokens/add-hide-token.spec.js +++ /dev/null @@ -1,270 +0,0 @@ -const { strict: assert } = require('assert'); -const { toHex } = require('@metamask/controller-utils'); -const { - defaultGanacheOptions, - withFixtures, - unlockWallet, - WINDOW_TITLES, - clickNestedButton, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); -const { CHAIN_IDS } = require('../../../../shared/constants/network'); - -describe('Add hide token', function () { - it('hides the token when clicked', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder() - .withTokensController({ - allTokens: { - [toHex(1337)]: { - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': [ - { - address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', - decimals: 4, - image: null, - isERC721: false, - symbol: 'TST', - }, - ], - }, - }, - tokens: [ - { - address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', - decimals: 4, - image: null, - isERC721: false, - symbol: 'TST', - }, - ], - }) - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await driver.waitForSelector({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '0 TST', - }); - - let assets = await driver.findElements('.multichain-token-list-item'); - assert.equal(assets.length, 2); - - await clickNestedButton(driver, 'Tokens'); - - await driver.clickElement({ text: 'TST', tag: 'span' }); - - await driver.clickElement('[data-testid="asset-options__button"]'); - - await driver.clickElement('[data-testid="asset-options__hide"]'); - // wait for confirm hide modal to be visible - const confirmHideModal = - '[data-testid="hide-token-confirmation-modal"]'; - await driver.findVisibleElement(confirmHideModal); - - await driver.clickElement( - '[data-testid="hide-token-confirmation__hide"]', - ); - - // wait for confirm hide modal to be removed from DOM. - await driver.assertElementNotPresent(confirmHideModal); - - assets = await driver.findElements('.multichain-token-list-item'); - assert.equal(assets.length, 1); - }, - ); - }); -}); - -/* eslint-disable-next-line mocha/max-top-level-suites */ -describe('Add existing token using search', function () { - // Mock call to core to fetch BAT token price - async function mockPriceFetch(mockServer) { - return [ - await mockServer - .forGet('https://price.api.cx.metamask.io/v2/chains/56/spot-prices') - .withQuery({ - tokenAddresses: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - vsCurrency: 'ETH', - }) - .thenCallback(() => { - return { - statusCode: 200, - json: { - '0x0d8775f648430679a709e98d2b0cb6250d2887ef': { - eth: 0.0001, - }, - }, - }; - }), - ]; - } - it('renders the balance for the chosen token', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder({ inputChainId: CHAIN_IDS.BSC }) - .withPreferencesController({ useTokenDetection: true }) - .withTokenListController({ - tokenList: [ - { - name: 'Basic Attention Token', - symbol: 'BAT', - address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', - }, - ], - }) - .withAppStateController({ - [CHAIN_IDS.OPTIMISM]: true, - }) - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - chainId: parseInt(CHAIN_IDS.BSC, 16), - }, - title: this.test.fullTitle(), - testSpecificMock: mockPriceFetch, - }, - async ({ driver }) => { - await unlockWallet(driver); - - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement(`[data-testid="importTokens"]`); - await driver.fill('input[placeholder="Search tokens"]', 'BAT'); - await driver.clickElement({ - text: 'BAT', - tag: 'p', - }); - await driver.clickElement({ text: 'Next', tag: 'button' }); - await driver.clickElementAndWaitToDisappear( - '[data-testid="import-tokens-modal-import-button"]', - ); - await driver.clickElement( - '[data-testid="account-overview__asset-tab"]', - ); - await driver.clickElement({ - tag: 'span', - text: 'Basic Attention Token', - }); - - await driver.waitForSelector({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '0 BAT', - }); - }, - ); - }); -}); - -describe('Add token using wallet_watchAsset', function () { - const smartContract = SMART_CONTRACTS.HST; - - it('opens a notification that adds a token when wallet_watchAsset is executed, then approves', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - await driver.openNewPage('http://127.0.0.1:8080/'); - - await driver.executeScript(` - window.ethereum.request({ - method: 'wallet_watchAsset', - params: { - type: 'ERC20', - options: { - address: '${contractAddress}', - symbol: 'TST', - decimals: 4 - }, - } - }) - `); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - tag: 'button', - text: 'Add token', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.waitForSelector({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '0 TST', - }); - }, - ); - }); - - it('opens a notification that adds a token when wallet_watchAsset is executed, then rejects', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - await driver.openNewPage('http://127.0.0.1:8080/'); - - await driver.executeScript(` - window.ethereum.request({ - method: 'wallet_watchAsset', - params: { - type: 'ERC20', - options: { - address: '${contractAddress}', - symbol: 'TST', - decimals: 4 - }, - } - }) - `); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - tag: 'button', - text: 'Cancel', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - const assetListItems = await driver.findElements( - '.multichain-token-list-item', - ); - - assert.strictEqual(assetListItems.length, 1); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/add-hide-token.spec.ts b/test/e2e/tests/tokens/add-hide-token.spec.ts new file mode 100644 index 000000000000..f6d21430cd18 --- /dev/null +++ b/test/e2e/tests/tokens/add-hide-token.spec.ts @@ -0,0 +1,50 @@ +import { toHex } from '@metamask/controller-utils'; +import { withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Add hide token', function () { + it('hides the token when clicked', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withTokensController({ + allTokens: { + [toHex(1337)]: { + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': [ + { + address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', + decimals: 4, + image: null, + isERC721: false, + symbol: 'TST', + }, + ], + }, + }, + tokens: [ + { + address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', + decimals: 4, + image: null, + isERC721: false, + symbol: 'TST', + }, + ], + }) + .build(), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await loginWithBalanceValidation(driver); + const assetListPage = new AssetListPage(driver); + await assetListPage.check_tokenItemNumber(2); + await assetListPage.check_tokenAmountIsDisplayed('0 TST'); + + await assetListPage.hideToken('TST'); + await assetListPage.check_tokenItemNumber(1); + }, + ); + }); +}); diff --git a/test/e2e/tests/tokens/add-multiple-tokens.spec.js b/test/e2e/tests/tokens/add-multiple-tokens.spec.js deleted file mode 100644 index 8be2a430b622..000000000000 --- a/test/e2e/tests/tokens/add-multiple-tokens.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - defaultGanacheOptions, - openDapp, - switchToNotificationWindow, - WINDOW_TITLES, - DAPP_URL, - unlockWallet, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Multiple ERC20 Watch Asset', function () { - // TODO: This assertion will change once the method is fixed. - it('should show multiple erc20 watchAsset token list, only confirms one bug', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await openDapp(driver, undefined, DAPP_URL); - - // Create Token 1 - const createToken = await driver.findElement({ - text: 'Create Token', - tag: 'button', - }); - await driver.scrollToElement(createToken); - await driver.clickElement({ text: 'Create Token', tag: 'button' }); - await switchToNotificationWindow(driver); - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Wait for token 1 address to populate in dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.wait(async () => { - const tokenAddressesElement = await driver.findElement( - '#erc20TokenAddresses', - ); - const tokenAddresses = await tokenAddressesElement.getText(); - return tokenAddresses !== ''; - }, 10000); - - // Create Token 2 - await driver.clickElement({ text: 'Create Token', tag: 'button' }); - await switchToNotificationWindow(driver); - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Wait for token 2 address to populate in dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.wait(async () => { - const tokenAddressesElement = await driver.findElement( - '#erc20TokenAddresses', - ); - const tokenAddresses = await tokenAddressesElement.getText(); - return tokenAddresses.split(',').length === 2; - }, 10000); - - // Create Token 3 - await driver.clickElement({ text: 'Create Token', tag: 'button' }); - await switchToNotificationWindow(driver); - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Wait for token 3 address to populate in dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.wait(async () => { - const tokenAddressesElement = await driver.findElement( - '#erc20TokenAddresses', - ); - const tokenAddresses = await tokenAddressesElement.getText(); - return tokenAddresses.split(',').length === 3; - }, 10000); - - // Watch all 3 tokens - await driver.clickElement({ - text: 'Add Token(s) to Wallet', - tag: 'button', - }); - - // Switch to watchAsset notification - await switchToNotificationWindow(driver); - const multipleSuggestedtokens = await driver.findElements( - '.confirm-add-suggested-token__token-list-item', - ); - - // Confirm all 3 tokens are present as suggested token list - assert.equal(multipleSuggestedtokens.length, 3); - await driver.findClickableElement({ text: 'Add token', tag: 'button' }); - await driver.clickElement({ text: 'Add token', tag: 'button' }); - - // Switch to fullscreen extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Check all three tokens have been added to the token list. - const addedTokens = await driver.findElements({ - tag: 'span', - text: 'TST', - }); - assert.equal(addedTokens.length, 3); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/add-multiple-tokens.spec.ts b/test/e2e/tests/tokens/add-multiple-tokens.spec.ts new file mode 100644 index 000000000000..78daa2531da1 --- /dev/null +++ b/test/e2e/tests/tokens/add-multiple-tokens.spec.ts @@ -0,0 +1,70 @@ +import AddTokensModal from '../../page-objects/pages/dialog/add-tokens'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { + withFixtures, + defaultGanacheOptions, + openDapp, + WINDOW_TITLES, + DAPP_URL, + unlockWallet, +} from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import CreateContractModal from '../../page-objects/pages/dialog/create-contract'; + +describe('Multiple ERC20 Watch Asset', function () { + it('should show multiple erc20 watchAsset token list, only confirms one bug', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + await openDapp(driver, undefined, DAPP_URL); + const testDapp = new TestDapp(driver); + + // Create multiple tokens + for (let i = 0; i < 3; i++) { + // Create token + await testDapp.findAndClickCreateToken(); + + // Confirm token creation + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const createContractModal = new CreateContractModal(driver); + await createContractModal.check_pageIsLoaded(); + await createContractModal.clickConfirm(); + + // Wait for token address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await testDapp.check_pageIsLoaded(); + await testDapp.check_TokenAddressesCount(i + 1); + } + + // Watch all 3 tokens + // Switch to watchAsset notification + await testDapp.clickAddTokenToWallet(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const addTokensPopupModal = new AddTokensModal(driver); + await addTokensPopupModal.check_pageIsLoaded(); + await addTokensPopupModal.check_SuggestedTokensCount(3); + await addTokensPopupModal.confirmAddTokens(); + + // Switch to fullscreen extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Check all three tokens have been added to the token list. + const tokenList = new AssetListPage(driver); + await tokenList.check_tokenItemNumber(4); // 3 tokens plus ETH + await tokenList.check_tokenIsDisplayed('Ethereum'); + await tokenList.check_tokenIsDisplayed('TST'); + }, + ); + }); +}); diff --git a/test/e2e/tests/tokens/add-token-using-search.ts b/test/e2e/tests/tokens/add-token-using-search.ts new file mode 100644 index 000000000000..c1ae188a39b9 --- /dev/null +++ b/test/e2e/tests/tokens/add-token-using-search.ts @@ -0,0 +1,69 @@ +import { MockedEndpoint, Mockttp } from 'mockttp'; +import { defaultGanacheOptions, withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Add existing token using search', function () { + // Mock call to core to fetch BAT token price + async function mockPriceFetch( + mockServer: Mockttp, + ): Promise { + return [ + await mockServer + .forGet('https://price.api.cx.metamask.io/v2/chains/56/spot-prices') + .withQuery({ + tokenAddresses: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + vsCurrency: 'ETH', + }) + .thenCallback(() => { + return { + statusCode: 200, + json: { + '0x0d8775f648430679a709e98d2b0cb6250d2887ef': { + eth: 0.0001, + }, + }, + }; + }), + ]; + } + it('renders the balance for the chosen token', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder({ inputChainId: CHAIN_IDS.BSC }) + .withPreferencesController({ useTokenDetection: true }) + .withTokenListController({ + tokenList: [ + { + name: 'Basic Attention Token', + symbol: 'BAT', + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + }, + ], + }) + .withAppStateController({ + [CHAIN_IDS.OPTIMISM]: true, + }) + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + chainId: parseInt(CHAIN_IDS.BSC, 16), + }, + title: this.test?.fullTitle(), + testSpecificMock: mockPriceFetch, + }, + async ({ driver }) => { + await loginWithoutBalanceValidation(driver); + const assetListPage = new AssetListPage(driver); + await assetListPage.check_tokenAmountIsDisplayed('25 BNB'); + await assetListPage.importTokenBySearch('BAT'); + await assetListPage.check_tokenAmountInTokenDetailsModal( + 'Basic Attention Token', + '0 BAT', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/tokens/custom-token-add-approve.spec.js b/test/e2e/tests/tokens/custom-token-add-approve.spec.js deleted file mode 100644 index 9226ad1a36f1..000000000000 --- a/test/e2e/tests/tokens/custom-token-add-approve.spec.js +++ /dev/null @@ -1,458 +0,0 @@ -const { strict: assert } = require('assert'); -const { - clickNestedButton, - defaultGanacheOptions, - editGasFeeForm, - logInWithBalanceValidation, - openDapp, - WINDOW_TITLES, - withFixtures, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); - -describe('Create token, approve token and approve token without gas', function () { - const smartContract = SMART_CONTRACTS.HST; - - it('imports and renders the balance for the new token', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver, ganacheServer); - - // imports custom token from extension - await driver.clickElement( - `[data-testid="account-overview__asset-tab"]`, - ); - await clickNestedButton(driver, 'Tokens'); - - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement(`[data-testid="importTokens"]`); - await clickNestedButton(driver, 'Custom token'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-address"]', - contractAddress, - ); - await driver.waitForSelector( - '[data-testid="import-tokens-modal-custom-decimals"]', - ); - - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - await driver.clickElement( - '[data-testid="import-tokens-modal-import-button"]', - ); - - // renders balance for newly created token - await driver.clickElement('.app-header__logo-container'); - - await clickNestedButton(driver, 'Tokens'); - await driver.waitForSelector({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '10 TST', - }); - }, - ); - }); - - it('approves an already created token and displays the token approval data @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // create token - await openDapp(driver, contractAddress); - - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - await driver.findClickableElement('#deployButton'); - // approve token from dapp - await driver.clickElement('#approveTokens'); - - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - await driver.clickElement({ - text: 'Verify third-party details', - css: '.token-allowance-container__verify-link', - }); - - // Verification modal is opened - await driver.waitForSelector({ - text: 'Third-party details', - tag: 'h5', - }); - - await driver.clickElementAndWaitToDisappear({ - text: 'Got it', - tag: 'button', - }); - // back to approval modal - await driver.clickElement({ - text: 'View details', - css: '.token-allowance-container__view-details', - }); - - // Validate elements on approve token popup - await driver.waitForSelector({ - text: 'Function: Approve', - tag: 'h6', - }); - const confirmDataDiv = await driver.findElement( - '.approve-content-card-container__data__data-block', - ); - const confirmDataText = await confirmDataDiv.getText(); - assert( - confirmDataText.match( - /0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef4/u, - ), - ); - - await driver.clickElement({ text: 'Next', tag: 'button' }); - // Spending cap modal is opened - await driver.waitForSelector({ - text: '7 TST', - css: '.mm-box > h6', - }); - - await driver.clickElement({ - text: 'Approve', - tag: 'button', - }); - - // We want to wait until txn is finished, and the dialog is closed - // before checking it in the expanded view of extension - await driver.waitUntilXWindowHandles(2); - - // Moved to expanded window to validate the txn - await driver.switchToWindow(extension); - await clickNestedButton(driver, 'Activity'); - // wait for txn in activity section - await driver.waitForSelector( - '[data-testid="activity-list-item-action"]', - ); - await driver.waitForSelector('.transaction-status-label--confirmed'); - }, - ); - }); - - it('set custom spending cap, customizes gas, edit spending cap and checks transaction in transaction list @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // create token - await openDapp(driver, contractAddress); - - let windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - await driver.findClickableElement('#deployButton'); - - // approve token from dapp - await driver.clickElement({ text: 'Approve Tokens', tag: 'button' }); - - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - // set custom spending cap - let setSpendingCap = await driver.findElement( - '[data-testid="custom-spending-cap-input"]', - ); - await setSpendingCap.fill('5'); - - await driver.clickElement({ - text: 'View details', - css: '.token-allowance-container__view-details', - }); - - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - let spendingCap = await driver.findElement({ - text: '5 TST', - css: '.mm-box > h6', - }); - - assert.equal( - await spendingCap.getText(), - '5 TST', - 'Default value is not correctly set', - ); - - // editing gas fee - const editBtn = await driver.findElement({ - text: 'Edit', - tag: 'h6', - }); - - editBtn.click(); - - await driver.waitForSelector({ - text: 'Edit priority', - tag: 'header', - }); - - await editGasFeeForm(driver, '60001', '10'); - - await driver.waitForSelector( - { - css: '.box--flex-direction-row > h6', - text: '0.0006 ETH', - }, - { timeout: 15000 }, - ); - - // editing spending cap - await driver.clickElement({ - class: '.review-spending-cap__heading-detail__button', - text: 'Edit', - }); - - setSpendingCap = await driver.findElement( - '[data-testid="custom-spending-cap-input"]', - ); - await setSpendingCap.fill('9'); - - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - spendingCap = await driver.findElement({ - text: '9 TST', - css: '.mm-box > h6', - }); - assert.equal( - await spendingCap.getText(), - '9 TST', - 'Default value is not correctly set', - ); - - // submits the transaction - await driver.clickElement({ text: 'Approve', tag: 'button' }); - - // finds the transaction in transaction list - await driver.switchToWindow(extension); - await clickNestedButton(driver, 'Activity'); - - await driver.wait(async () => { - const pendingTxes = await driver.findElements('.activity-list-item'); - return pendingTxes.length === 1; - }, 10000); - const approveTokenTask = await driver.waitForSelector({ - // Select only the heading of the first entry in the transaction list. - css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]', - text: 'Approve TST spending cap', - }); - assert.equal( - await approveTokenTask.getText(), - 'Approve TST spending cap', - ); - }, - ); - }); - - it('set maximum spending cap, submits the transaction and finds the transaction in the transactions list @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // create token - await openDapp(driver, contractAddress); - const windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - await driver.findClickableElement('#deployButton'); - - // approve token from dapp - await driver.clickElement({ text: 'Approve Tokens', tag: 'button' }); - - await driver.switchToWindow(extension); - await clickNestedButton(driver, 'Activity'); - - const pendingTxes = await driver.findElements( - '.transaction-list__pending-transactions .activity-list-item', - ); - pendingTxes[0].click(); - - // set max spending cap - await driver.clickElement({ - css: '[data-testid="custom-spending-cap-max-button"]', - text: 'Max', - }); - - await driver.clickElement({ - tag: 'button', - text: 'Next', - }); - - // checks the balance - const balance = await driver.findElement({ - css: '.box--display-flex > h6', - text: '10 TST', - }); - - const maxSpendingCap = await driver.findElement({ - text: '10 TST', - css: '.mm-box > h6', - }); - - assert.equal( - await maxSpendingCap.getText(), - await balance.getText(), - 'Max spending cap is not set corectly', - ); - - await driver.clickElement({ - tag: 'button', - text: 'Approve', - }); - - const approveTokenTask = await driver.waitForSelector({ - // Select only the heading of the first entry in the transaction list. - css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]', - text: 'Approve TST spending cap', - }); - assert.equal( - await approveTokenTask.getText(), - 'Approve TST spending cap', - ); - }, - ); - }); - - it('approves token without gas, set site suggested spending cap, submits the transaction and finds the transaction in the transactions list @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await openDapp(driver, contractAddress); - const windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - - await driver.findClickableElement('#deployButton'); - // approve token without gas from dapp - await driver.clickElement({ - text: 'Approve Tokens Without Gas', - tag: 'button', - }); - - // switch to extension - await driver.switchToWindow(extension); - await clickNestedButton(driver, 'Activity'); - - const pendingTxes = await driver.findElements('.activity-list-item'); - pendingTxes[0].click(); - - // set custom spending cap - const spendingCap = await driver.findElement( - '[data-testid="custom-spending-cap-input"]', - ); - await spendingCap.fill('5'); - - // set site suggested spending cap - await driver.clickElement({ - text: 'Use site suggestion', - css: '.mm-button-link', - }); - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - await driver.delay(500); - await driver.clickElement({ text: 'Approve', tag: 'button' }); - - // check transaction in Activity tab - const approveTokenTask = await driver.waitForSelector({ - css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]', - text: 'Approve TST spending cap', - }); - assert.equal( - await approveTokenTask.getText(), - 'Approve TST spending cap', - ); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js index 0c5498a82cca..ccb2a821825b 100644 --- a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js +++ b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js @@ -1,4 +1,6 @@ -const { strict: assert } = require('assert'); +const { + mockedSourcifyTokenSend, +} = require('../confirmations/transactions/erc20-token-send-redesign.spec'); const { withFixtures, defaultGanacheOptions, @@ -8,7 +10,6 @@ const { editGasFeeForm, WINDOW_TITLES, clickNestedButton, - tempToggleSettingRedesignedTransactionConfirmations, veryLargeDelayMs, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -18,6 +19,7 @@ const recipientAddress = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; describe('Transfer custom tokens @no-mmi', function () { const smartContract = SMART_CONTRACTS.HST; + it('send custom tokens from extension customizing gas values', async function () { await withFixtures( { @@ -26,12 +28,11 @@ describe('Transfer custom tokens @no-mmi', function () { ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), + testSpecificMock: mocks, }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // go to custom tokens view on extension, perform send tokens await driver.clickElement({ css: '[data-testid="multichain-token-list-item-value"]', @@ -53,35 +54,11 @@ describe('Transfer custom tokens @no-mmi', function () { // check transaction details await driver.waitForSelector({ text: '1 TST', - tag: 'h1', - }); - await driver.waitForSelector({ - text: 'Transfer', - css: '.confirm-page-container-summary__action__name', - }); - const estimatedGasFee = await driver.findElements( - '.currency-display-component__text', - ); - assert.notEqual( - await estimatedGasFee[1].getText(), - '0', - 'Estimated gas fee should not be 0', - ); - - // check function name and hex data details in hex tab - await clickNestedButton(driver, 'Hex'); - await driver.waitForSelector({ - text: 'Transfer', - tag: 'span', - }); - await driver.waitForSelector({ - tag: 'p', - text: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c97', + tag: 'h2', }); // edit gas fee - await clickNestedButton(driver, 'Details'); - await driver.clickElement({ text: 'Edit', tag: 'button' }); + await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); await editGasFeeForm(driver, '60000', '10'); await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -112,6 +89,7 @@ describe('Transfer custom tokens @no-mmi', function () { ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), + testSpecificMock: mocks, }, async ({ driver, contractRegistry }) => { const contractAddress = await contractRegistry.getContractAddress( @@ -119,8 +97,6 @@ describe('Transfer custom tokens @no-mmi', function () { ); await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // transfer token from dapp await openDapp(driver, contractAddress); await driver.delay(veryLargeDelayMs); @@ -128,10 +104,10 @@ describe('Transfer custom tokens @no-mmi', function () { await driver.clickElement({ text: 'Transfer Tokens', tag: 'button' }); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' }); + await driver.waitForSelector({ text: '1.5 TST', tag: 'h2' }); // edit gas fee - await driver.clickElement({ text: 'Edit', tag: 'button' }); + await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); await editGasFeeForm(driver, '60000', '10'); await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -153,14 +129,10 @@ describe('Transfer custom tokens @no-mmi', function () { // check token amount is correct after transaction await clickNestedButton(driver, 'Tokens'); - const tokenAmount = await driver.findElement( - { - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }, - { timeout: 10000 }, - ); - assert.ok(tokenAmount, 'Token amount is not correct'); + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '8.5 TST', + }); }, ); }); @@ -176,6 +148,7 @@ describe('Transfer custom tokens @no-mmi', function () { ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), + testSpecificMock: mocks, }, async ({ driver, contractRegistry }) => { const contractAddress = await contractRegistry.getContractAddress( @@ -183,8 +156,6 @@ describe('Transfer custom tokens @no-mmi', function () { ); await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // transfer token from dapp await openDapp(driver, contractAddress); await driver.delay(veryLargeDelayMs); @@ -193,7 +164,7 @@ describe('Transfer custom tokens @no-mmi', function () { tag: 'button', }); await switchToNotificationWindow(driver); - await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' }); + await driver.waitForSelector({ text: '1.5 TST', tag: 'h2' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); // in extension, check that transaction has completed correctly and is displayed in the activity list @@ -218,15 +189,15 @@ describe('Transfer custom tokens @no-mmi', function () { // check token amount is correct after transaction await clickNestedButton(driver, 'Tokens'); - const tokenAmount = await driver.findElement( - { - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }, - { timeout: 10000 }, - ); - assert.ok(tokenAmount, 'Token amount is not correct'); + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '8.5 TST', + }); }, ); }); + + async function mocks(server) { + return [await mockedSourcifyTokenSend(server)]; + } }); diff --git a/test/e2e/tests/tokens/import-tokens.spec.js b/test/e2e/tests/tokens/import-tokens.spec.ts similarity index 59% rename from test/e2e/tests/tokens/import-tokens.spec.js rename to test/e2e/tests/tokens/import-tokens.spec.ts index 7b1bf60964ab..245381c8d285 100644 --- a/test/e2e/tests/tokens/import-tokens.spec.js +++ b/test/e2e/tests/tokens/import-tokens.spec.ts @@ -1,13 +1,16 @@ -const { strict: assert } = require('assert'); -const { +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import HomePage from '../../page-objects/pages/home/homepage'; + +import { defaultGanacheOptions, withFixtures, unlockWallet, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); +} from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { Mockttp } from '../../mock-e2e'; describe('Import flow', function () { - async function mockPriceFetch(mockServer) { + async function mockPriceFetch(mockServer: Mockttp) { return [ await mockServer .forGet('https://price.api.cx.metamask.io/v2/chains/1/spot-prices') @@ -60,40 +63,28 @@ describe('Import flow', function () { }) .build(), ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), + title: this.test?.fullTitle(), testSpecificMock: mockPriceFetch, }, async ({ driver }) => { await unlockWallet(driver); - await driver.assertElementNotPresent('.loading-overlay'); - - await driver.clickElement('[data-testid="import-token-button"]'); - await driver.clickElement('[data-testid="importTokens"]'); - - await driver.fill('input[placeholder="Search tokens"]', 'cha'); - - await driver.clickElement('.token-list__token_component'); - await driver.clickElement( - '.token-list__token_component:nth-of-type(2)', - ); - await driver.clickElement( - '.token-list__token_component:nth-of-type(3)', - ); - - await driver.clickElement('[data-testid="import-tokens-button-next"]'); - await driver.clickElement( - '[data-testid="import-tokens-modal-import-button"]', - ); - - // Wait for "loading tokens" to be gone - await driver.assertElementNotPresent( - '[data-testid="token-list-loading-message"]', - ); + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + await homePage.check_pageIsLoaded(); + await assetListPage.importMultipleTokensBySearch([ + 'CHAIN', + 'CHANGE', + 'CHAI', + ]); - const expectedTokenListElementsAreFound = - await driver.elementCountBecomesN('.multichain-token-list-item', 4); - assert.equal(expectedTokenListElementsAreFound, true); + const tokenList = new AssetListPage(driver); + await tokenList.check_tokenItemNumber(5); // Linea & Mainnet Eth + await tokenList.check_tokenIsDisplayed('Ethereum'); + await tokenList.check_tokenIsDisplayed('Chain Games'); + // TODO: add back this check once we figure out why tokens name displayed when running the test locally is changex but on CI it is ChangeX + // await tokenList.check_tokenIsDisplayed('Changex'); + await tokenList.check_tokenIsDisplayed('Chai'); }, ); }); diff --git a/test/e2e/tests/tokens/increase-token-allowance.spec.js b/test/e2e/tests/tokens/increase-token-allowance.spec.js deleted file mode 100644 index 9ce8db2cc065..000000000000 --- a/test/e2e/tests/tokens/increase-token-allowance.spec.js +++ /dev/null @@ -1,329 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../../fixture-builder'); -const { - defaultGanacheOptions, - openDapp, - sendTransaction, - unlockWallet, - withFixtures, - ACCOUNT_1, - ACCOUNT_2, - WINDOW_TITLES, - clickNestedButton, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); - -const DEFAULT_TEST_DAPP_INCREASE_ALLOWANCE_SPENDING_CAP = '1'; - -describe('Increase Token Allowance', function () { - const smartContract = SMART_CONTRACTS.HST; - - it('increases token spending cap to allow other accounts to transfer tokens @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const ACCOUNT_1_NAME = 'Account 1'; - const ACCOUNT_2_NAME = '2nd Account'; - - const initialSpendingCap = '1'; - const additionalSpendingCap = '1'; - - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await openDapp(driver, contractAddress); - - await deployTokenContract(driver); - await approveTokenSpendingCapTo(driver, ACCOUNT_2, initialSpendingCap); - - await sendTransaction(driver, ACCOUNT_2, '1'); - await addAccount(driver, ACCOUNT_2_NAME); - - await triggerTransferFromTokens(driver, ACCOUNT_1, ACCOUNT_2); - // 'Transfer From Tokens' on the test dApp attempts to transfer 1.5 TST. - // Since this is higher than the 'initialSpendingCap', it should fail. - await pollForTokenAddressesError( - driver, - 'reverted ERC20: insufficient allowance', - ); - - await switchToAccountWithName(driver, ACCOUNT_1_NAME); - - await increaseTokenAllowance(driver, additionalSpendingCap); - - await switchToAccountWithName(driver, ACCOUNT_2_NAME); - await triggerTransferFromTokens(driver, ACCOUNT_1, ACCOUNT_2); - await confirmTransferFromTokensSuccess(driver); - }, - ); - }); - - async function deployTokenContract(driver) { - await driver.findClickableElement('#deployButton'); - } - - async function approveTokenSpendingCapTo( - driver, - accountToApproveFor, - initialSpendingCap, - ) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const approveToFillEl = await driver.findElement('[id="approveTo"]'); - await approveToFillEl.clear(); - await approveToFillEl.fill(accountToApproveFor); - - await driver.clickElement({ text: 'Approve Tokens', tag: 'button' }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'Activity'); - - const pendingTransactions = await driver.findElements( - '.transaction-list__pending-transactions .activity-list-item', - ); - pendingTransactions[0].click(); - - const setSpendingCap = await driver.findElement( - '[data-testid="custom-spending-cap-input"]', - ); - await setSpendingCap.fill(initialSpendingCap); - - await driver.clickElement({ - tag: 'button', - text: 'Next', - }); - await driver.waitForSelector({ - css: '.box--display-flex > h6', - text: `10 TST`, - }); - await driver.waitForSelector({ - text: `${initialSpendingCap} TST`, - css: '.mm-box > h6', - }); - await driver.clickElement({ - tag: 'button', - text: 'Approve', - }); - - await driver.waitForSelector({ - css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]', - text: 'Approve TST spending cap', - }); - } - - async function addAccount(driver, newAccountName) { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-add-account"]', - ); - - await driver.fill('[placeholder="Account 2"]', newAccountName); - await driver.clickElement({ text: 'Add account', tag: 'button' }); - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: newAccountName, - }); - } - - async function triggerTransferFromTokens( - driver, - senderAccount, - recipientAccount, - ) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const transferFromSenderInputEl = await driver.findElement( - '[id="transferFromSenderInput"]', - ); - await transferFromSenderInputEl.clear(); - await transferFromSenderInputEl.fill(senderAccount); - - const transferFromRecipientInputEl = await driver.findElement( - '[id="transferFromRecipientInput"]', - ); - await transferFromRecipientInputEl.clear(); - await transferFromRecipientInputEl.fill(recipientAccount); - - await driver.clickElement('#transferFromTokens'); - await driver.delay(2000); - } - - async function pollForTokenAddressesError( - driver, - errorMessagePart, - timeout = driver.timeout, - ) { - const pollInterval = 500; - let elapsedTime = 0; - - await new Promise((resolve, reject) => { - const pollInsufficientAllowanceError = setInterval(async () => { - try { - const tokenAddressesElement = await driver.findElement( - '#tokenMethodsResult', - ); - const tokenAddressesMsgText = await tokenAddressesElement.getText(); - const isErrorThrown = - tokenAddressesMsgText.includes(errorMessagePart); - - if (isErrorThrown) { - // Condition satisfied, stopping poll. - clearInterval(pollInsufficientAllowanceError); - resolve(); - } else { - elapsedTime += pollInterval; - if (elapsedTime >= timeout) { - // Timeout reached, stopping poll. - clearInterval(pollInsufficientAllowanceError); - reject( - new Error( - `Did not throw '${errorMessagePart}' error as expected. Timeout reached, stopping poll.`, - ), - ); - } - } - } catch (error) { - clearInterval(pollInsufficientAllowanceError); - reject(error); - } - }, pollInterval); - }); - } - - async function switchToAccountWithName(driver, accountName) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement('[data-testid="account-menu-icon"]'); - - await driver.findElement({ - css: `.multichain-account-list-item .multichain-account-list-item__account-name__button`, - text: accountName, - }); - - await driver.clickElement({ - css: `.multichain-account-list-item .multichain-account-list-item__account-name__button`, - text: accountName, - }); - } - - async function increaseTokenAllowance(driver, finalSpendingCap) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement({ - text: 'Increase Token Allowance', - tag: 'button', - }); - await driver.delay(2000); - - // Windows: MetaMask, Test Dapp and Dialog - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - let spendingCapElement = await driver.findElement( - '[data-testid="custom-spending-cap-input"]', - ); - - let spendingCapValue = await spendingCapElement.getProperty('value'); - assert.equal( - spendingCapValue, - DEFAULT_TEST_DAPP_INCREASE_ALLOWANCE_SPENDING_CAP, - 'Default Test Dapp Increase Allowance Spending Cap is unexpected', - ); - - spendingCapElement = await driver.findElement( - '[data-testid="custom-spending-cap-input"]', - ); - await spendingCapElement.clear(); - - await spendingCapElement.fill('0'); - - await driver.clickElement({ - text: 'Use site suggestion', - tag: 'button', - }); - - spendingCapValue = await spendingCapElement.getProperty('value'); - assert.equal( - spendingCapValue, - DEFAULT_TEST_DAPP_INCREASE_ALLOWANCE_SPENDING_CAP, - 'Test Dapp Suggestion Increase Allowance Spending Cap is unexpected', - ); - - await spendingCapElement.fill(finalSpendingCap); - - await driver.clickElement({ - tag: 'button', - text: 'Next', - }); - await driver.waitForSelector({ - css: '.box--display-flex > h6', - text: `10 TST`, - }); - await driver.assertElementNotPresent( - { - tag: 'h6', - text: '0.000054 ETH', - }, - { - waitAtLeastGuard: 2000, - }, - ); - await driver.waitForSelector({ - tag: 'h6', - text: '0.000062 ETH', - }); - await driver.waitForSelector({ - text: `${finalSpendingCap} TST`, - css: '.mm-box > h6', - }); - await driver.clickElementAndWaitForWindowToClose({ - tag: 'button', - text: 'Approve', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'Activity'); - await driver.waitForSelector({ - css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]', - text: 'Increase TST spending cap', - }); - - await driver.delay(2000); - } - - async function confirmTransferFromTokensSuccess(driver) { - // Windows: MetaMask, Test Dapp and Dialog - await driver.waitUntilXWindowHandles(3, 1000, 10000); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'Activity'); - - await driver.waitForSelector({ - css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]', - text: 'Send TST', - }); - } -}); diff --git a/test/e2e/tests/tokens/nft/auto-detect-nft.spec.ts b/test/e2e/tests/tokens/nft/auto-detect-nft.spec.ts index c70a3860dd63..5856ea7a7a3d 100644 --- a/test/e2e/tests/tokens/nft/auto-detect-nft.spec.ts +++ b/test/e2e/tests/tokens/nft/auto-detect-nft.spec.ts @@ -1,7 +1,8 @@ import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; -import Homepage from '../../../page-objects/pages/homepage'; +import Homepage from '../../../page-objects/pages/home/homepage'; +import NFTListPage from '../../../page-objects/pages/home/nft-list'; import PrivacySettings from '../../../page-objects/pages/settings/privacy-settings'; import SettingsPage from '../../../page-objects/pages/settings/settings-page'; import { loginWithBalanceValidation } from '../../../page-objects/flows/login.flow'; @@ -39,10 +40,11 @@ describe('NFT detection', function () { await homepage.check_pageIsLoaded(); await homepage.check_expectedBalanceIsDisplayed(); await homepage.goToNftTab(); - await homepage.check_nftNameIsDisplayed( - 'ENS: Ethereum Name Service (1)', + const nftListPage = new NFTListPage(driver); + await nftListPage.check_nftNameIsDisplayed( + 'ENS: Ethereum Name Service', ); - await homepage.check_nftImageIsDisplayed(); + await nftListPage.check_nftImageIsDisplayed(); }, ); }); diff --git a/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js b/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js deleted file mode 100644 index 388247bb3fcd..000000000000 --- a/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js +++ /dev/null @@ -1,331 +0,0 @@ -const { strict: assert } = require('assert'); -const { mockNetworkStateOld } = require('../../../../stub/networks'); -const { - withFixtures, - DAPP_URL, - openDapp, - unlockWallet, - WINDOW_TITLES, - defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, - veryLargeDelayMs, -} = require('../../../helpers'); -const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); -const FixtureBuilder = require('../../../fixture-builder'); - -describe('ERC1155 NFTs testdapp interaction', function () { - const smartContract = SMART_CONTRACTS.ERC1155; - - it('should mint ERC1155 token', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withNetworkController( - mockNetworkStateOld({ - chainId: '0x539', - nickname: 'Localhost 8545', - rpcUrl: 'http://localhost:8545', - ticker: 'ETH', - blockExplorerUrl: 'https://etherscan.io/', - }), - ) - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // Mint - await driver.fill('#batchMintTokenIds', '1, 2, 3'); - await driver.fill('#batchMintIdAmounts', '1, 1, 1000000000000000'); - await driver.clickElement('#batchMintButton'); - - // Notification - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Confirm Mint - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Deposit', - }); - await driver.clickElement('[data-testid="activity-list-item-action"]'); - await driver.clickElement({ - text: 'View on block explorer', - tag: 'a', - }); - - // Switch to block explorer - await driver.switchToWindowWithTitle('E2E Test Page'); - await driver.findElement('[data-testid="empty-page-body"]'); - // Verify block explorer - await driver.waitForUrl({ - url: 'https://etherscan.io/tx/0xfe4428397f7913875783c5c0dad182937b596148295bc33c7f08d74fdee8897f', - }); - - // switch to Dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.fill('#watchAssetInput', '1'); - await driver.clickElement('#watchAssetButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="page-container-footer-next"]', - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.clickElementSafe('[data-testid="popover-close"]'); - await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - await driver.clickElement('[data-testid="nft-item"]'); - }, - ); - }); - - it('should batch transfers ERC1155 token', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await openDapp(driver, contract); - - await driver.fill('#batchTransferTokenIds', '1, 2, 3'); - await driver.fill('#batchTransferTokenAmounts', '1, 1, 1000000000000'); - await driver.clickElement('#batchTransferFromButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Confirm Transfer - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Deposit', - }); - }, - ); - }); - - it('should enable approval for a third party address to manage all ERC1155 token', async function () { - // ERC1155 is the name of the test-dapp ERC1155 contract - const expectedMessageTitle = - 'Allow access to and transfer all of your NFTs from ERC1155?'; - const expectedDescription = - 'This allows a third party to access and transfer all of your NFTs from ERC1155 without further notice until you revoke its access.'; - const expectedWarningMessage = 'Your NFT may be at risk'; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Create a set approval for all erc1155 token request in test dapp - await openDapp(driver, contract); - await driver.clickElement('#setApprovalForAllERC1155Button'); - - // Wait for notification popup and check the displayed message - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '[data-testid="confirm-approve-title"]', - text: expectedMessageTitle, - }); - await driver.waitForSelector({ - css: '.confirm-approve-content h6', - text: DAPP_URL, - }); - - await driver.waitForSelector({ - css: '.confirm-approve-content__description', - text: expectedDescription, - }); - - // Check displayed transaction details - await driver.clickElement({ - text: 'View full transaction details', - css: '.confirm-approve-content__small-blue-text', - }); - const [func, params] = await driver.findElements( - '.confirm-approve-content__data .confirm-approve-content__small-text', - ); - assert.equal(await func.getText(), 'Function: SetApprovalForAll'); - assert.equal(await params.getText(), 'Parameters: true'); - - // Check the warning message and confirm set approval for all - await driver.clickElement('[data-testid="page-container-footer-next"]'); - const displayedWarning = await driver.findElement( - '.set-approval-for-all-warning__content__header', - ); - assert.equal(await displayedWarning.getText(), expectedWarningMessage); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Approve', - tag: 'button', - }); - - // Switch to extension and check set approval for all transaction is displayed in activity tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector({ - css: '.transaction-list__completed-transactions', - text: 'Approve Token with no spend limit', - }); - - // Switch back to the dapp and verify that set approval for all action completed message is displayed - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#erc1155Status', - text: 'Set Approval For All completed', - }); - }, - ); - }); - - it('should revoke approval for a third party address to manage all ERC1155 token', async function () { - // ERC1155 is the name of the test-dapp ERC1155 contract - const expectedMessageTitle = - 'Revoke permission to access and transfer all of your NFTs from ERC1155?'; - const expectedDescription = - 'This revokes the permission for a third party to access and transfer all of your NFTs from ERC1155 without further notice.'; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Create a revoke approval for all erc1155 token request in test dapp - await openDapp(driver, contract); - - await driver.delay(veryLargeDelayMs); - - await driver.clickElement('#revokeERC1155Button'); - - // Wait for notification popup and check the displayed message - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector({ - css: '.confirm-approve-content__title', - text: expectedMessageTitle, - }); - - await driver.waitForSelector({ - css: '.confirm-approve-content h6', - text: DAPP_URL, - }); - - await driver.waitForSelector({ - css: '.confirm-approve-content__description', - text: expectedDescription, - }); - - // Check displayed transaction details - await driver.clickElement({ - text: 'View full transaction details', - css: '.confirm-approve-content__small-blue-text', - }); - const [func, params] = await driver.findElements( - '.confirm-approve-content__data .confirm-approve-content__small-text', - ); - assert.equal(await func.getText(), 'Function: SetApprovalForAll'); - assert.equal(await params.getText(), 'Parameters: false'); - - // Click on extension popup to confirm revoke approval for all - await driver.clickElementAndWaitForWindowToClose( - '[data-testid="page-container-footer-next"]', - ); - - // Switch to extension and check revoke approval transaction is displayed in activity tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector({ - css: '.transaction-list__completed-transactions', - text: 'Approve Token with no spend limit', - }); - - // Switch back to the dapp and verify that revoke approval for all message is displayed - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const revokeApprovalStatus = await driver.findElement({ - css: '#erc1155Status', - text: 'Revoke completed', - }); - assert.equal(await revokeApprovalStatus.isDisplayed(), true); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js deleted file mode 100644 index 8b634ffc3ce3..000000000000 --- a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js +++ /dev/null @@ -1,561 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - openDapp, - unlockWallet, - WINDOW_TITLES, - defaultGanacheOptions, - clickNestedButton, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../../helpers'); -const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); -const FixtureBuilder = require('../../../fixture-builder'); - -describe('ERC721 NFTs testdapp interaction', function () { - const smartContract = SMART_CONTRACTS.NFTS; - - it('should add NFTs to state by parsing tx logs without having to click on watch NFT', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // mint NFTs - await driver.fill('#mintAmountInput', '5'); - await driver.clickElement({ text: 'Mint', tag: 'button' }); - - // Notification - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Deposit', - }); - - // verify the mint transaction has finished - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#nftsStatus', - text: 'Mint completed', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'NFTs'); - await driver.findElement({ text: 'TestDappNFTs (5)' }); - const nftsListItemsFirstCheck = await driver.findElements( - '.nft-item__container', - ); - assert.equal(nftsListItemsFirstCheck.length, 5); - }, - ); - }); - - it('should prompt users to add their NFTs to their wallet (one by one) @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // mint NFT - await driver.fill('#mintAmountInput', '5'); - await driver.clickElement({ text: 'Mint', tag: 'button' }); - - // Notification - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Deposit', - }); - - // verify the mint transaction has finished - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const nftsMintStatus = await driver.findElement({ - css: '#nftsStatus', - text: 'Mint completed', - }); - assert.equal(await nftsMintStatus.isDisplayed(), true); - - // watch 3 of the nfts - await driver.fill('#watchNFTInput', '1'); - await driver.clickElement({ text: 'Watch NFT', tag: 'button' }); - await driver.fill('#watchNFTInput', '2'); - await driver.clickElement({ text: 'Watch NFT', tag: 'button' }); - await driver.fill('#watchNFTInput', '3'); - await driver.clickElement({ text: 'Watch NFT', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // avoid race condition - await driver.waitForSelector({ - css: '.confirm-add-suggested-nft__nft-tokenId', - text: '#3', - }); - - // confirm watchNFT - await driver.waitForSelector({ - css: '.mm-text--heading-lg', - text: 'Add suggested NFTs', - }); - await driver.clickElement({ text: 'Add NFTs', tag: 'button' }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'NFTs'); - // Changed this check from 3 to 6, because after mint all nfts has been added to state, - await driver.findElement({ text: 'TestDappNFTs (6)' }); - const nftsListItemsFirstCheck = await driver.findElements( - '.nft-item__container', - ); - assert.equal(nftsListItemsFirstCheck.length, 6); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.fill('#watchNFTInput', '4'); - - await driver.clickElement({ text: 'Watch NFT', tag: 'button' }); - await driver.fill('#watchNFTInput', '5'); - await driver.clickElement({ text: 'Watch NFT', tag: 'button' }); - await driver.fill('#watchNFTInput', '6'); - await driver.clickElement({ text: 'Watch NFT', tag: 'button' }); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // avoid race condition - await driver.waitForSelector({ - css: '.confirm-add-suggested-nft__nft-tokenId', - text: '#6', - }); - - // confirm watchNFT - await driver.waitForSelector({ - css: '.mm-text--heading-lg', - text: 'Add suggested NFTs', - }); - await driver.clickElement({ text: 'Add NFTs', tag: 'button' }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'NFTs'); - await driver.findElement({ text: 'TestDappNFTs (6)' }); - const nftsListItemsSecondCheck = await driver.findElements( - '.nft-item__container', - ); - assert.equal(nftsListItemsSecondCheck.length, 6); - }, - ); - }); - - it('should prompt users to add their NFTs to their wallet (all at once)', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // mint NFT - await driver.fill('#mintAmountInput', '5'); - await driver.clickElement({ text: 'Mint', tag: 'button' }); - - // Notification - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - css: '.confirm-page-container-summary__action__name', - text: 'Deposit', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - - // We need to wait until the transaction is confirmed before looking for the tx - // otherwise the element becomes stale, as it updates from 'pending' to 'confirmed' - await driver.waitForSelector('.transaction-status-label--confirmed'); - - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Deposit', - }); - // verify the mint transaction has finished - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#nftsStatus', - text: 'Mint completed', - }); - - // watch all nfts - await driver.clickElement({ text: 'Watch all NFTs', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // confirm watchNFT - await driver.waitForSelector({ - css: '.mm-text--heading-lg', - text: 'Add suggested NFTs', - }); - - await driver.findElements('.confirm-add-suggested-nft__nft-list-item'); - const suggestedNftListItems = await driver.findElements( - '.confirm-add-suggested-nft__nft-list-item', - ); - // there are 6 nfts to add because one is minted as part of the fixture - assert.equal(suggestedNftListItems.length, 6); - - // remove one nft from the list - const removeButtons = await driver.findElements( - '.confirm-add-suggested-nft__nft-remove', - ); - await removeButtons[0].click(); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Add NFTs', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'NFTs'); - await driver.findElement({ text: 'TestDappNFTs (5)' }); - const nftsListItemsSecondCheck = await driver.findElements( - '.nft-item__container', - ); - - assert.equal(nftsListItemsSecondCheck.length, 5); - }, - ); - }); - - it('should transfer a single ERC721 NFT from one account to another', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // Click Transfer - await driver.fill('#transferTokenInput', '1'); - await driver.clickElement('#transferFromButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Confirm transfer - await driver.waitForSelector({ - css: '.mm-text--heading-md', - text: 'TestDappNFTs', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector( - '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', - ); - - // Verify transaction - await driver.findElement({ text: 'Send TDN' }); - }, - ); - }); - - it('should approve an address to transfer a single ERC721 NFT', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // Click Approve - const approveInput = await driver.findElement('#approveTokenInput'); - await approveInput.clear(); - await approveInput.sendKeys('1'); - await driver.clickElement('#approveButton'); - - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Verify dialog - const title = await driver.findElement( - '[data-testid="confirm-approve-title"]', - ); - await driver.clickElement({ - text: 'View full transaction details', - css: '.confirm-approve-content__small-blue-text', - }); - const [func] = await driver.findElements( - '.confirm-approve-content__data .confirm-approve-content__small-text', - ); - assert.equal( - await title.getText(), - 'Allow access to and transfer of your TestDappNFTs (#1)?', - ); - assert.equal(await func.getText(), 'Function: Approve'); - - // Confirm approval - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector( - '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', - ); - - // Verify transaction - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Approve TDN spending cap', - }); - }, - ); - }); - - it('should enable approval for a third party address to manage all ERC721 NFTs @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // Enable Set approval for all - await driver.clickElement('#setApprovalForAllButton'); - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Verify dialog - const title = await driver.findElement( - '[data-testid="confirm-approve-title"]', - ); - await driver.clickElement({ - text: 'View full transaction details', - css: '.confirm-approve-content__small-blue-text', - }); - const [func, params] = await driver.findElements( - '.confirm-approve-content__data .confirm-approve-content__small-text', - ); - assert.equal( - await title.getText(), - 'Allow access to and transfer of all your TestDappNFTs?', - ); - assert.equal(await func.getText(), 'Function: SetApprovalForAll'); - assert.equal(await params.getText(), 'Parameters: true'); - - // Confirm enabling set approval for all - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement({ text: 'Approve', tag: 'button' }); - - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector( - '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', - ); - - // Verify transaction - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Approve TDN with no spend limit', - }); - }, - ); - }); - - it('should disable approval for a third party address to manage all ERC721 NFTs @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, _, contractRegistry }) => { - const contract = contractRegistry.getContractAddress(smartContract); - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Open Dapp and wait for deployed contract - await openDapp(driver, contract); - await driver.findClickableElement('#deployButton'); - - // Disable Set approval for all - await driver.clickElement('#revokeButton'); - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Verify dialog - const title = await driver.findElement( - '[data-testid="confirm-approve-title"]', - ); - await driver.clickElement({ - text: 'View full transaction details', - css: '.confirm-approve-content__small-blue-text', - }); - const [func, params] = await driver.findElements( - '.confirm-approve-content__data .confirm-approve-content__small-text', - ); - const proceedWithCautionIsDisplayed = await driver.isElementPresent( - '.dialog--error', - ); - assert.equal( - await title.getText(), - 'Revoke permission to access and transfer all of your TestDappNFTs?', - ); - assert.equal(await func.getText(), 'Function: SetApprovalForAll'); - assert.equal(await params.getText(), 'Parameters: false'); - assert.equal(proceedWithCautionIsDisplayed, false); - - // Confirm disabling set approval for all - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.waitUntilXWindowHandles(2); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.waitForSelector( - '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', - ); - - // Verify transaction - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Approve TDN with no spend limit', - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/nft/import-erc1155.spec.js b/test/e2e/tests/tokens/nft/import-erc1155.spec.js index 0eab8a731b7c..2045743f52be 100644 --- a/test/e2e/tests/tokens/nft/import-erc1155.spec.js +++ b/test/e2e/tests/tokens/nft/import-erc1155.spec.js @@ -44,10 +44,9 @@ describe('Import ERC1155 NFT', function () { assert.equal(await newNftNotification.isDisplayed(), true); // Check the imported ERC1155 and its image are displayed in the ERC1155 tab - const importedERC1155 = await driver.waitForSelector({ - css: 'h5', - text: 'Unnamed collection', - }); + const importedERC1155 = await driver.findElement( + '[data-testid="nft-item"]', + ); assert.equal(await importedERC1155.isDisplayed(), true); const importedERC1155Image = await driver.findVisibleElement( diff --git a/test/e2e/tests/tokens/nft/import-nft.spec.ts b/test/e2e/tests/tokens/nft/import-nft.spec.ts index bc709ba463c0..ec3503d1a8a4 100644 --- a/test/e2e/tests/tokens/nft/import-nft.spec.ts +++ b/test/e2e/tests/tokens/nft/import-nft.spec.ts @@ -1,9 +1,11 @@ import { defaultGanacheOptions, withFixtures } from '../../../helpers'; +import { ACCOUNT_TYPE } from '../../../constants'; import { SMART_CONTRACTS } from '../../../seeder/smart-contracts'; import FixtureBuilder from '../../../fixture-builder'; import AccountListPage from '../../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; -import Homepage from '../../../page-objects/pages/homepage'; +import Homepage from '../../../page-objects/pages/home/homepage'; +import NftListPage from '../../../page-objects/pages/home/nft-list'; import { loginWithBalanceValidation } from '../../../page-objects/flows/login.flow'; describe('Import NFT', function () { @@ -27,11 +29,10 @@ describe('Import NFT', function () { const homepage = new Homepage(driver); await homepage.goToNftTab(); - await homepage.importNft(contractAddress, '1'); - await homepage.check_successImportNftMessageIsDisplayed(); - - await homepage.check_nftNameIsDisplayed('TestDappNFTs'); - await homepage.check_nftImageIsDisplayed(); + const nftList = new NftListPage(driver); + await nftList.importNft(contractAddress, '1'); + await nftList.check_successImportNftMessageIsDisplayed(); + await nftList.check_nftImageIsDisplayed(); }, ); }); @@ -55,17 +56,19 @@ describe('Import NFT', function () { // Import a NFT and check that it is displayed in the NFT tab on homepage const homepage = new Homepage(driver); await homepage.goToNftTab(); - await homepage.importNft(contractAddress, '1'); - await homepage.check_successImportNftMessageIsDisplayed(); - await homepage.check_nftNameIsDisplayed('TestDappNFTs'); - await homepage.check_nftImageIsDisplayed(); + const nftList = new NftListPage(driver); + await nftList.importNft(contractAddress, '1'); + await nftList.check_successImportNftMessageIsDisplayed(); + await nftList.check_nftImageIsDisplayed(); // Create new account with default name Account 2 const headerNavbar = new HeaderNavbar(driver); await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel('Account 2'); await homepage.check_expectedBalanceIsDisplayed(); @@ -76,8 +79,7 @@ describe('Import NFT', function () { await accountListPage.switchToAccount('Account 1'); await headerNavbar.check_accountLabel('Account 1'); await homepage.check_localBlockchainBalanceIsDisplayed(ganacheServer); - await homepage.check_nftNameIsDisplayed('TestDappNFTs'); - await homepage.check_nftImageIsDisplayed(); + await nftList.check_nftImageIsDisplayed(); }, ); }); @@ -98,9 +100,8 @@ describe('Import NFT', function () { contractRegistry.getContractAddress(smartContract); await loginWithBalanceValidation(driver, ganacheServer); - const homepage = new Homepage(driver); - await homepage.goToNftTab(); - await homepage.importNft( + await new Homepage(driver).goToNftTab(); + await new NftListPage(driver).importNft( contractAddress, '2', 'NFT can’t be added as the ownership details do not match. Make sure you have entered correct information.', diff --git a/test/e2e/tests/tokens/nft/remove-erc1155.spec.js b/test/e2e/tests/tokens/nft/remove-erc1155.spec.js index b67540f49dfe..0ea7b3fb779b 100644 --- a/test/e2e/tests/tokens/nft/remove-erc1155.spec.js +++ b/test/e2e/tests/tokens/nft/remove-erc1155.spec.js @@ -35,7 +35,7 @@ describe('Remove ERC1155 NFT', function () { // Open the details page and click remove nft button await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - await driver.clickElement('[data-testid="nft-image"]'); + await driver.clickElement('.nft-item__container'); await driver.clickElement('[data-testid="nft-options__button"]'); await driver.clickElement('[data-testid="nft-item-remove"]'); diff --git a/test/e2e/tests/tokens/nft/send-nft.spec.js b/test/e2e/tests/tokens/nft/send-nft.spec.js deleted file mode 100644 index 13cb310f1416..000000000000 --- a/test/e2e/tests/tokens/nft/send-nft.spec.js +++ /dev/null @@ -1,187 +0,0 @@ -const { strict: assert } = require('assert'); -const { - defaultGanacheOptions, - logInWithBalanceValidation, - unlockWallet, - withFixtures, - tempToggleSettingRedesignedTransactionConfirmations, -} = require('../../../helpers'); -const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); -const FixtureBuilder = require('../../../fixture-builder'); - -describe('Send NFT', function () { - const smartContract = SMART_CONTRACTS.NFTS; - const erc1155SmartContract = SMART_CONTRACTS.ERC1155; - - it('should be able to send ERC721 NFT', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withNftControllerERC721().build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - // Fill the send NFT form and confirm the transaction - await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - await driver.clickElement('.nft-item__container'); - // TODO: Update Test when Multichain Send Flow is added - await driver.clickElement({ text: 'Send', tag: 'button' }); - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - '0xc427D562164062a23a5cFf596A4a3208e72Acd28', - ); - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - // Edit the NFT, ensure same address, and move forward - await driver.clickElement( - '[data-testid="confirm-page-back-edit-button"]', - ); - - const recipient = await driver.findElement( - '.ens-input__selected-input__title', - ); - - assert.equal( - await recipient.getText(), - '0xc427D...Acd28\n0xc427D...Acd28', - ); - - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - // Confirm the send - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // When transaction complete, check the send NFT is displayed in activity tab - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - - const sendNftItem = await driver.findElement({ - css: '[data-testid="activity-list-item-action"]', - text: 'Send Test Dapp NFTs', - }); - assert.equal(await sendNftItem.isDisplayed(), true); - - // Go back to NFTs tab and check the imported NFT is shown as previously owned - await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - const previouslyOwnedNft = await driver.findElement({ - css: 'h5', - text: 'Previously Owned', - }); - assert.equal(await previouslyOwnedNft.isDisplayed(), true); - }, - ); - }); - - it('should be able to send ERC1155 NFT', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withNftControllerERC1155().build(), - ganacheOptions: defaultGanacheOptions, - smartContract: erc1155SmartContract, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - - // Fill the send NFT form and confirm the transaction - await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - - await driver.clickElement('[data-testid="nft-network-badge"]'); - - await driver.clickElement({ text: 'Send', tag: 'button' }); - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - '0xc427D562164062a23a5cFf596A4a3208e72Acd28', - ); - - await driver.fill('input[placeholder="0"]', '1'); - - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - // Confirm the send - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // When transaction complete, check the send NFT is displayed in activity tab - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - - const sendNftItem = await driver.findElement({ - css: '[data-testid="activity-list-item-action"]', - text: 'Safe transfer from', - }); - assert.equal(await sendNftItem.isDisplayed(), true); - - // Go back to NFTs tab and check the imported NFT is shown as previously owned - await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - - const previouslyOwnedNft = await driver.findElement({ - css: 'h5', - text: 'Previously Owned', - }); - assert.equal(await previouslyOwnedNft.isDisplayed(), true); - }, - ); - }); - - it('should not be able to send ERC1155 NFT with invalid amount', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withNftControllerERC1155().build(), - ganacheOptions: defaultGanacheOptions, - smartContract: erc1155SmartContract, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - - // Fill the send NFT form and confirm the transaction - await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); - - await driver.clickElement('[data-testid="nft-network-badge"]'); - - await driver.clickElement({ text: 'Send', tag: 'button' }); - - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - '0xc427D562164062a23a5cFf596A4a3208e72Acd28', - ); - - await driver.assertElementNotPresent( - '[data-testid="send-page-amount-error"]', - ); - await driver.fill('input[placeholder="0"]', '0'); - assert.ok( - await driver.findElement({ - text: '1 token. Cannot send negative or zero amounts of asset.', - tag: 'p', - }), - ); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/send-erc20-to-contract.spec.js b/test/e2e/tests/tokens/send-erc20-to-contract.spec.js deleted file mode 100644 index 6e94b6377e67..000000000000 --- a/test/e2e/tests/tokens/send-erc20-to-contract.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -const { strict: assert } = require('assert'); -const { - defaultGanacheOptions, - withFixtures, - unlockWallet, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Send ERC20 token to contract address', function () { - const smartContract = SMART_CONTRACTS.HST; - - it('should display the token contract warning to the user', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withTokensControllerERC20().build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - // Send TST - await driver.clickElement( - '[data-testid="account-overview__asset-tab"]', - ); - await driver.clickElement( - '[data-testid="multichain-token-list-button"]', - ); - await driver.clickElement('[data-testid="coin-overview-send"]'); - - // Type contract address - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - contractAddress, - ); - - // Verify warning - const warningText = - 'Warning: you are about to send to a token contract which could result in a loss of funds. Learn more'; - const warning = await driver.findElement( - '[data-testid="send-warning"] .mm-box--min-width-0 span', - ); - assert.equal(await warning.getText(), warningText); - }, - ); - }); -}); diff --git a/test/e2e/tests/tokens/send-erc20-to-contract.spec.ts b/test/e2e/tests/tokens/send-erc20-to-contract.spec.ts new file mode 100644 index 000000000000..5c7b59cbc215 --- /dev/null +++ b/test/e2e/tests/tokens/send-erc20-to-contract.spec.ts @@ -0,0 +1,52 @@ +import { + defaultGanacheOptions, + withFixtures, + unlockWallet, +} from '../../helpers'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import FixtureBuilder from '../../fixture-builder'; + +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import HomePage from '../../page-objects/pages/home/homepage'; +import SendTokenPage from '../../page-objects/pages/send/send-token-page'; +import TokenOverviewPage from '../../page-objects/pages/token-overview-page'; + +describe('Send ERC20 token to contract address', function () { + const smartContract = SMART_CONTRACTS.HST; + + it('should display the token contract warning to the user', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().withTokensControllerERC20().build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ driver, contractRegistry }) => { + const contractAddress: string = + await contractRegistry.getContractAddress(smartContract); + await unlockWallet(driver); + + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + await homePage.check_pageIsLoaded(); + await assetListPage.clickOnAsset('TST'); + + // Send TST + const tokenOverviewPage = new TokenOverviewPage(driver); + await tokenOverviewPage.check_pageIsLoaded(); + await tokenOverviewPage.clickSend(); + + const sendTokenPage = new SendTokenPage(driver); + await sendTokenPage.check_pageIsLoaded(); + await sendTokenPage.fillRecipient(contractAddress); + + // Verify warning + const warningText = + 'Warning: you are about to send to a token contract which could result in a loss of funds. Learn more'; + await sendTokenPage.check_warningMessage(warningText); + }, + ); + }); +}); diff --git a/test/e2e/tests/tokens/token-details.spec.ts b/test/e2e/tests/tokens/token-details.spec.ts index 2ee84d339ea8..5424f1d02367 100644 --- a/test/e2e/tests/tokens/token-details.spec.ts +++ b/test/e2e/tests/tokens/token-details.spec.ts @@ -47,7 +47,7 @@ describe('Token Details', function () { const openTokenDetails = async (driver: Driver) => { await driver.clickElement('[data-testid="account-overview__asset-tab"]'); - const [, tkn] = await driver.findElements( + const [, , tkn] = await driver.findElements( '[data-testid="multichain-token-list-button"]', ); await tkn.click(); @@ -102,6 +102,9 @@ describe('Token Details', function () { async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); await importToken(driver); + await driver.clickElement( + '.actionable-message__message button[aria-label="Close"]', + ); await openTokenDetails(driver); await verifyToken(driver); }, @@ -157,6 +160,9 @@ describe('Token Details', function () { async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); await importToken(driver); + await driver.clickElement( + '.actionable-message__message button[aria-label="Close"]', + ); await openTokenDetails(driver); await verifyToken(driver); diff --git a/test/e2e/tests/tokens/token-list.spec.ts b/test/e2e/tests/tokens/token-list.spec.ts index f7b032c92a4c..4002142d595e 100644 --- a/test/e2e/tests/tokens/token-list.spec.ts +++ b/test/e2e/tests/tokens/token-list.spec.ts @@ -1,4 +1,3 @@ -import { strict as assert } from 'assert'; import { Mockttp } from 'mockttp'; import { Context } from 'mocha'; import { zeroAddress } from 'ethereumjs-util'; @@ -6,15 +5,17 @@ import { CHAIN_IDS } from '../../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import FixtureBuilder from '../../fixture-builder'; import { - clickNestedButton, defaultGanacheOptions, unlockWallet, withFixtures, } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import HomePage from '../../page-objects/pages/home/homepage'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; describe('Token List', function () { const chainId = CHAIN_IDS.MAINNET; + const lineaChainId = CHAIN_IDS.LINEA_MAINNET; const tokenAddress = '0x2EFA2Cb29C2341d8E5Ba7D3262C9e9d6f1Bf3711'; const symbol = 'foo'; @@ -26,88 +27,118 @@ describe('Token List', function () { }, }; - const importToken = async (driver: Driver) => { - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement(`[data-testid="importTokens"]`); - await clickNestedButton(driver, 'Custom token'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-address"]', - tokenAddress, - ); - await driver.waitForSelector('p.mm-box--color-error-default'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-symbol"]', - symbol, - ); - await driver.clickElement({ text: 'Next', tag: 'button' }); - await driver.clickElement( - '[data-testid="import-tokens-modal-import-button"]', - ); - await driver.findElement({ text: 'Token imported', tag: 'h6' }); + const mockEmptyPrices = async ( + mockServer: Mockttp, + chainIdToMock: string, + ) => { + return mockServer + .forGet( + `https://price.api.cx.metamask.io/v2/chains/${parseInt( + chainIdToMock, + 16, + )}/spot-prices`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: {}, + })); + }; + + const mockEmptyHistoricalPrices = async ( + mockServer: Mockttp, + address: string, + ) => { + return mockServer + .forGet( + `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${address}`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: {}, + })); + }; + + const mockSpotPrices = async ( + mockServer: Mockttp, + chainIdToMock: string, + prices: Record< + string, + { price: number; pricePercentChange1d: number; marketCap: number } + >, + ) => { + return mockServer + .forGet( + `https://price.api.cx.metamask.io/v2/chains/${parseInt( + chainIdToMock, + 16, + )}/spot-prices`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: prices, + })); + }; + + const mockHistoricalPrices = async ( + mockServer: Mockttp, + address: string, + price: number, + ) => { + return mockServer + .forGet( + `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${toChecksumHexAddress( + address, + )}`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: { + prices: [ + [1717566000000, price * 0.9], + [1717566322300, price], + [1717566611338, price * 1.1], + ], + }, + })); }; - it('should not shows percentage increase for an ERC20 token without prices available', async function () { + it('should not show percentage increase for an ERC20 token without prices available', async function () { await withFixtures( { ...fixtures, title: (this as Context).test?.fullTitle(), testSpecificMock: async (mockServer: Mockttp) => [ - // Mock no current price - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v2/chains/${parseInt( - chainId, - 16, - )}/spot-prices`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: {}, - })), - // Mock no historical prices - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${tokenAddress}`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: {}, - })), + await mockEmptyPrices(mockServer, chainId), + await mockEmptyPrices(mockServer, lineaChainId), + await mockEmptyHistoricalPrices(mockServer, tokenAddress), ], }, async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); - await importToken(driver); - // Verify native token increase - const testIdNative = `token-increase-decrease-percentage-${zeroAddress()}`; + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); - // Verify native token increase - const testId = `token-increase-decrease-percentage-${tokenAddress}`; + await homePage.check_pageIsLoaded(); + await assetListPage.importCustomToken(tokenAddress, symbol); - const percentageNative = await ( - await driver.findElement(`[data-testid="${testIdNative}"]`) - ).getText(); - assert.equal(percentageNative, ''); - - const percentage = await ( - await driver.findElement(`[data-testid="${testId}"]`) - ).getText(); - assert.equal(percentage, ''); + await assetListPage.check_tokenGeneralChangePercentageNotPresent( + zeroAddress(), + ); + await assetListPage.check_tokenGeneralChangePercentageNotPresent( + tokenAddress, + ); }, ); }); it('shows percentage increase for an ERC20 token with prices available', async function () { const ethConversionInUsd = 10000; - - // Prices are in ETH const marketData = { price: 0.123, marketCap: 12, pricePercentChange1d: 0.05, }; - const marketDataNative = { price: 0.123, marketCap: 12, @@ -120,91 +151,35 @@ describe('Token List', function () { title: (this as Context).test?.fullTitle(), ethConversionInUsd, testSpecificMock: async (mockServer: Mockttp) => [ - // Mock current price - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v2/chains/${parseInt( - chainId, - 16, - )}/spot-prices`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: { - [zeroAddress()]: marketDataNative, - [tokenAddress.toLowerCase()]: marketData, - }, - })), - // Mock historical prices - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${toChecksumHexAddress( - tokenAddress, - )}`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: { - prices: [ - [1717566000000, marketData.price * 0.9], - [1717566322300, marketData.price], - [1717566611338, marketData.price * 1.1], - ], - }, - })), + await mockSpotPrices(mockServer, chainId, { + [zeroAddress()]: marketDataNative, + [tokenAddress.toLowerCase()]: marketData, + }), + await mockHistoricalPrices( + mockServer, + tokenAddress, + marketData.price, + ), ], }, async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); - await importToken(driver); - // Verify native token increase - const testIdBase = 'token-increase-decrease-percentage'; + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); - const isETHIncreaseDOMPresentAndVisible = - await driver.isElementPresentAndVisible({ - css: `[data-testid="${testIdBase}-${zeroAddress()}"]`, - text: '+0.02%', - }); - assert.equal( - isETHIncreaseDOMPresentAndVisible, - true, - 'Invalid eth increase dom text content', - ); - - const isTokenIncreaseDecreasePercentageDOMPresent = - await driver.isElementPresentAndVisible({ - css: `[data-testid="${testIdBase}-${tokenAddress}"]`, - text: '+0.05%', - }); - assert.equal( - isTokenIncreaseDecreasePercentageDOMPresent, - true, - 'Invalid token increase dom text content', - ); + await homePage.check_pageIsLoaded(); + await assetListPage.importCustomToken(tokenAddress, symbol); - // check increase balance for native token eth - const isExpectedIncreaseDecreaseValueDOMPresentAndVisible = - await driver.isElementPresentAndVisible({ - css: '[data-testid="token-increase-decrease-value"]', - text: '+$50.00', - }); - assert.equal( - isExpectedIncreaseDecreaseValueDOMPresentAndVisible, - true, - 'Invalid increase-decrease-value dom text content', + await assetListPage.check_tokenGeneralChangePercentage( + zeroAddress(), + '+0.02%', ); - - const isExpectedIncreaseDecreasePercentageDOMPresentAndVisible = - await driver.isElementPresentAndVisible({ - css: '[data-testid="token-increase-decrease-percentage"]', - text: '(+0.02%)', - }); - assert.equal( - isExpectedIncreaseDecreasePercentageDOMPresentAndVisible, - true, - 'Invalid increase-decrease-percentage dom text content', + await assetListPage.check_tokenGeneralChangePercentage( + tokenAddress, + '+0.05%', ); + await assetListPage.check_tokenGeneralChangeValue('+$50.00'); }, ); }); diff --git a/test/e2e/tests/tokens/token-sort.spec.ts b/test/e2e/tests/tokens/token-sort.spec.ts index ff2d35a917dc..ddf9c33cceb1 100644 --- a/test/e2e/tests/tokens/token-sort.spec.ts +++ b/test/e2e/tests/tokens/token-sort.spec.ts @@ -3,102 +3,66 @@ import { Context } from 'mocha'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import FixtureBuilder from '../../fixture-builder'; import { - clickNestedButton, defaultGanacheOptions, - regularDelayMs, unlockWallet, withFixtures, + largeDelayMs, } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import HomePage from '../../page-objects/pages/home/homepage'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; -describe('Token List', function () { - const chainId = CHAIN_IDS.MAINNET; - const tokenAddress = '0x2EFA2Cb29C2341d8E5Ba7D3262C9e9d6f1Bf3711'; - const symbol = 'ABC'; +describe('Token List Sorting', function () { + const mainnetChainId = CHAIN_IDS.MAINNET; + const customTokenAddress = '0x2EFA2Cb29C2341d8E5Ba7D3262C9e9d6f1Bf3711'; + const customTokenSymbol = 'ABC'; - const fixtures = { - fixtures: new FixtureBuilder({ inputChainId: chainId }).build(), + const testFixtures = { + fixtures: new FixtureBuilder({ inputChainId: mainnetChainId }).build(), ganacheOptions: { ...defaultGanacheOptions, - chainId: parseInt(chainId, 16), + chainId: parseInt(mainnetChainId, 16), }, }; - const importToken = async (driver: Driver) => { - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement(`[data-testid="importTokens"]`); - await clickNestedButton(driver, 'Custom token'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-address"]', - tokenAddress, - ); - await driver.waitForSelector('p.mm-box--color-error-default'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-symbol"]', - symbol, - ); - await driver.delay(2000); - await driver.clickElement({ text: 'Next', tag: 'button' }); - await driver.clickElement( - '[data-testid="import-tokens-modal-import-button"]', - ); - await driver.findElement({ text: 'Token imported', tag: 'h6' }); - }; - - it('should sort alphabetically and by decreasing balance', async function () { + it('should sort tokens alphabetically and by decreasing balance', async function () { await withFixtures( { - ...fixtures, + ...testFixtures, title: (this as Context).test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); - await importToken(driver); - - const tokenListBeforeSorting = await driver.findElements( - '[data-testid="multichain-token-list-button"]', - ); - const tokenSymbolsBeforeSorting = await Promise.all( - tokenListBeforeSorting.map(async (tokenElement) => { - return tokenElement.getText(); - }), - ); - - assert.ok(tokenSymbolsBeforeSorting[0].includes('Ethereum')); - await driver.clickElement('[data-testid="sort-by-popover-toggle"]'); - await driver.clickElement('[data-testid="sortByAlphabetically"]'); + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); - await driver.delay(regularDelayMs); - const tokenListAfterSortingAlphabetically = await driver.findElements( - '[data-testid="multichain-token-list-button"]', - ); - const tokenListSymbolsAfterSortingAlphabetically = await Promise.all( - tokenListAfterSortingAlphabetically.map(async (tokenElement) => { - return tokenElement.getText(); - }), + await homePage.check_pageIsLoaded(); + await assetListPage.importCustomToken( + customTokenAddress, + customTokenSymbol, ); - assert.ok( - tokenListSymbolsAfterSortingAlphabetically[0].includes('ABC'), - ); + const initialTokenList = await assetListPage.getTokenListNames(); + assert.ok(initialTokenList[0].includes('Ethereum')); + await assetListPage.sortTokenList('alphabetically'); - await driver.clickElement('[data-testid="sort-by-popover-toggle"]'); - await driver.clickElement('[data-testid="sortByDecliningBalance"]'); - - await driver.delay(regularDelayMs); - const tokenListBeforeSortingByDecliningBalance = - await driver.findElements( - '[data-testid="multichain-token-list-button"]', - ); - - const tokenListAfterSortingByDecliningBalance = await Promise.all( - tokenListBeforeSortingByDecliningBalance.map(async (tokenElement) => { - return tokenElement.getText(); - }), + await driver.waitUntil( + async () => { + const sortedTokenList = await assetListPage.getTokenListNames(); + return sortedTokenList[0].includes(customTokenSymbol); + }, + { timeout: largeDelayMs, interval: 100 }, ); - assert.ok( - tokenListAfterSortingByDecliningBalance[0].includes('Ethereum'), + + await assetListPage.sortTokenList('decliningBalance'); + await driver.waitUntil( + async () => { + const sortedTokenListByBalance = + await assetListPage.getTokenListNames(); + return sortedTokenListByBalance[0].includes('Ethereum'); + }, + { timeout: largeDelayMs, interval: 100 }, ); }, ); diff --git a/test/e2e/tests/tokens/watch-asset-call-add-token.ts b/test/e2e/tests/tokens/watch-asset-call-add-token.ts new file mode 100644 index 000000000000..b1dc4ae98746 --- /dev/null +++ b/test/e2e/tests/tokens/watch-asset-call-add-token.ts @@ -0,0 +1,109 @@ +import { + defaultGanacheOptions, + withFixtures, + WINDOW_TITLES, +} from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import AddTokenConfirmation from '../../page-objects/pages/confirmations/redesign/add-token-confirmations'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; + +describe('Add token using wallet_watchAsset', function () { + const smartContract = SMART_CONTRACTS.HST; + + it('opens a notification that adds a token when wallet_watchAsset is executed, then approves', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ driver, ganacheServer, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await loginWithBalanceValidation(driver, ganacheServer); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + + await driver.executeScript(` + window.ethereum.request({ + method: 'wallet_watchAsset', + params: { + type: 'ERC20', + options: { + address: '${contractAddress}', + symbol: 'TST', + decimals: 4 + }, + } + }) + `); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const addTokenConfirmation = new AddTokenConfirmation(driver); + await addTokenConfirmation.check_pageIsLoaded(); + await addTokenConfirmation.confirmAddToken(); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await new AssetListPage(driver).check_tokenAmountIsDisplayed('0 TST'); + }, + ); + }); + + it('opens a notification that adds a token when wallet_watchAsset is executed, then rejects', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ driver, ganacheServer, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await loginWithBalanceValidation(driver, ganacheServer); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + + await driver.executeScript(` + window.ethereum.request({ + method: 'wallet_watchAsset', + params: { + type: 'ERC20', + options: { + address: '${contractAddress}', + symbol: 'TST', + decimals: 4 + }, + } + }) + `); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const addTokenConfirmation = new AddTokenConfirmation(driver); + await addTokenConfirmation.check_pageIsLoaded(); + await addTokenConfirmation.rejectAddToken(); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await new AssetListPage(driver).check_tokenItemNumber(1); + }, + ); + }); +}); diff --git a/test/e2e/tests/transaction/change-assets.spec.js b/test/e2e/tests/transaction/change-assets.spec.js index f6a997c164e9..391d64e28369 100644 --- a/test/e2e/tests/transaction/change-assets.spec.js +++ b/test/e2e/tests/transaction/change-assets.spec.js @@ -3,7 +3,6 @@ const { defaultGanacheOptions, withFixtures, logInWithBalanceValidation, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); @@ -23,8 +22,6 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Wait for balance to load await driver.delay(500); @@ -39,15 +36,9 @@ describe('Change assets', function () { await driver.press('[data-testid="currency-input"]', '2'); await driver.clickElement({ text: 'Continue', css: 'button' }); - // Validate the send amount - await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '2.000042', - }); - // Click edit await driver.clickElement( - '[data-testid="confirm-page-back-edit-button"]', + '[data-testid="wallet-initiated-header-back-button"]', ); // Open the Amount modal @@ -69,10 +60,11 @@ describe('Change assets', function () { await driver.clickElement({ text: 'Continue', css: 'button' }); // Ensure NFT is showing - await driver.waitForSelector( - '.confirm-page-container-summary__title img', - ); - await driver.waitForSelector({ css: 'h3', text: 'Test Dapp NFTs #1' }); + await driver.waitForSelector('[data-testid="nft-default-image"]'); + await driver.waitForSelector({ + css: 'h2', + text: 'Test Dapp NFTs #1', + }); // Send it! await driver.clickElement({ text: 'Confirm', css: 'button' }); @@ -103,11 +95,9 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Click the Send button await driver.clickElement({ - css: '[data-testid="multichain-token-list-button"] span', + css: '[data-testid="multichain-token-list-button"] p', text: 'TST', }); @@ -124,15 +114,9 @@ describe('Change assets', function () { await driver.press('[data-testid="currency-input"]', '0'); await driver.clickElement({ text: 'Continue', css: 'button' }); - // Validate the send amount - await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '0.00008455', - }); - // Click edit await driver.clickElement( - '[data-testid="confirm-page-back-edit-button"]', + '[data-testid="wallet-initiated-header-back-button"]', ); // Open the Amount modal @@ -154,10 +138,11 @@ describe('Change assets', function () { await driver.clickElement({ text: 'Continue', css: 'button' }); // Ensure NFT is showing - await driver.waitForSelector( - '.confirm-page-container-summary__title img', - ); - await driver.waitForSelector({ css: 'h3', text: 'Test Dapp NFTs #1' }); + await driver.waitForSelector('[data-testid="nft-default-image"]'); + await driver.waitForSelector({ + css: 'h2', + text: 'Test Dapp NFTs #1', + }); // Send it! await driver.clickElement({ text: 'Confirm', css: 'button' }); @@ -184,8 +169,6 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Choose the nft await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); await driver.clickElement('[data-testid="nft-default-image"]'); @@ -203,14 +186,15 @@ describe('Change assets', function () { await driver.clickElement({ text: 'Continue', css: 'button' }); // Ensure NFT is showing - await driver.waitForSelector( - '.confirm-page-container-summary__title img', - ); - await driver.waitForSelector({ css: 'h3', text: 'Test Dapp NFTs #1' }); + await driver.waitForSelector('[data-testid="nft-default-image"]'); + await driver.waitForSelector({ + css: 'h2', + text: 'Test Dapp NFTs #1', + }); // Click edit await driver.clickElement( - '[data-testid="confirm-page-back-edit-button"]', + '[data-testid="wallet-initiated-header-back-button"]', ); // Open the Amount modal @@ -236,8 +220,8 @@ describe('Change assets', function () { // Validate the send amount await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '2.000042', + css: 'h2', + text: '2', }); // Send it! @@ -273,8 +257,6 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Create second account await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( @@ -360,8 +342,8 @@ describe('Change assets', function () { // Validate the send amount await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '2.000042', + css: 'h2', + text: '2 ETH', }); }, ); diff --git a/test/e2e/tests/transaction/edit-gas-fee.spec.js b/test/e2e/tests/transaction/edit-gas-fee.spec.js index 3e7655750594..901e78038471 100644 --- a/test/e2e/tests/transaction/edit-gas-fee.spec.js +++ b/test/e2e/tests/transaction/edit-gas-fee.spec.js @@ -9,7 +9,6 @@ const { unlockWallet, generateGanacheOptions, WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -24,13 +23,11 @@ describe('Editing Confirm Transaction', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await createInternalTransaction(driver); await driver.findElement({ - css: '.currency-display-component__text', - text: '1', + css: 'h2', + text: '1 ETH', }); // update estimates to high @@ -66,7 +63,7 @@ describe('Editing Confirm Transaction', function () { await driver.waitForSelector({ text: 'Slow', }); - await driver.waitForSelector('[data-testid="low-gas-fee-alert"]'); + await driver.waitForSelector('[data-testid="inline-alert"]'); // confirms the transaction await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -100,13 +97,11 @@ describe('Editing Confirm Transaction', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await createInternalTransaction(driver); await driver.findElement({ - css: '.currency-display-component__text', - text: '1', + css: 'h2', + text: '1 ETH', }); // update estimates to high @@ -135,12 +130,13 @@ describe('Editing Confirm Transaction', function () { // has correct updated value on the confirm screen the transaction await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '0.00085', + css: '[data-testid="first-gas-field"]', + text: '0.0002 ETH', }); + await driver.waitForSelector({ - css: '.currency-display-component__suffix', - text: 'ETH', + css: '[data-testid="native-currency"]', + text: '$0.30', }); // confirms the transaction @@ -179,8 +175,6 @@ describe('Editing Confirm Transaction', function () { // login to extension await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await createDappTransaction(driver, { maxFeePerGas: '0x2000000000', maxPriorityFeePerGas: '0x1000000000', @@ -201,16 +195,20 @@ describe('Editing Confirm Transaction', function () { '[data-testid="edit-gas-fee-item-dappSuggested"]', ); - const transactionAmounts = await driver.findElements( - '.currency-display-component__text', - ); - const transactionAmount = transactionAmounts[0]; - assert.equal(await transactionAmount.getText(), '0.001'); + await driver.findElements({ + css: 'h2', + text: '0.001 ETH', + }); // has correct updated value on the confirm screen the transaction await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '0.00185144', + css: '[data-testid="first-gas-field"]', + text: '0.0019', + }); + + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$3.15', }); // confirms the transaction diff --git a/test/e2e/tests/transaction/ens.spec.ts b/test/e2e/tests/transaction/ens.spec.ts index 3291287ab5a4..b36765475b62 100644 --- a/test/e2e/tests/transaction/ens.spec.ts +++ b/test/e2e/tests/transaction/ens.spec.ts @@ -4,7 +4,7 @@ import { defaultGanacheOptions, withFixtures } from '../../helpers'; import { Driver } from '../../webdriver/driver'; import FixtureBuilder from '../../fixture-builder'; import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; -import HomePage from '../../page-objects/pages/homepage'; +import HomePage from '../../page-objects/pages/home/homepage'; import SendTokenPage from '../../page-objects/pages/send/send-token-page'; import { mockServerJsonRpc } from '../ppom/mocks/mock-server-json-rpc'; import { mockMultiNetworkBalancePolling } from '../../mock-balance-polling/mock-balance-polling'; diff --git a/test/e2e/tests/transaction/gas-estimates.spec.js b/test/e2e/tests/transaction/gas-estimates.spec.js index f12275ad4d9f..e65ee39dcea6 100644 --- a/test/e2e/tests/transaction/gas-estimates.spec.js +++ b/test/e2e/tests/transaction/gas-estimates.spec.js @@ -3,7 +3,6 @@ const { logInWithBalanceValidation, openActionMenuAndStartSendFlow, generateGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { CHAIN_IDS } = require('../../../../shared/constants/network'); @@ -28,8 +27,6 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openActionMenuAndStartSendFlow(driver); await driver.fill( @@ -43,8 +40,13 @@ describe('Gas estimates generated by MetaMask', function () { // Check that the gas estimation is what we expect await driver.findElement({ - cass: '[data-testid="confirm-gas-display"]', - text: '0.00043983', + css: '[data-testid="first-gas-field"]', + text: '0.0004 ETH', + }); + + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$0.75', }); }, ); @@ -72,8 +74,6 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openActionMenuAndStartSendFlow(driver); await driver.fill( @@ -87,8 +87,12 @@ describe('Gas estimates generated by MetaMask', function () { // Check that the gas estimation is what we expect await driver.findElement({ - cass: '[data-testid="confirm-gas-display"]', - text: '0.00043983', + css: '[data-testid="first-gas-field"]', + text: '0 ETH', + }); + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$0.07', }); }, ); @@ -113,8 +117,6 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openActionMenuAndStartSendFlow(driver); await driver.fill( @@ -128,8 +130,12 @@ describe('Gas estimates generated by MetaMask', function () { // Check that the gas estimation is what we expect await driver.findElement({ - cass: '[data-testid="confirm-gas-display"]', - text: '0.00043983', + css: '[data-testid="first-gas-field"]', + text: '0 ETH', + }); + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$0.07', }); }, ); @@ -150,8 +156,6 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', @@ -164,8 +168,12 @@ describe('Gas estimates generated by MetaMask', function () { // Check that the gas estimation is what we expect await driver.findElement({ - cass: '[data-testid="confirm-gas-display"]', - text: '0.000042', + css: '[data-testid="first-gas-field"]', + text: '0 ETH', + }); + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$0.07', }); }, ); @@ -198,8 +206,6 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', @@ -212,8 +218,12 @@ describe('Gas estimates generated by MetaMask', function () { // Check that the gas estimation is what we expect await driver.findElement({ - cass: '[data-testid="confirm-gas-display"]', - text: '0.000042', + css: '[data-testid="first-gas-field"]', + text: '0 ETH', + }); + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$0.07', }); }, ); @@ -229,8 +239,6 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', @@ -243,8 +251,12 @@ describe('Gas estimates generated by MetaMask', function () { // Check that the gas estimation is what we expect await driver.findElement({ - cass: '[data-testid="confirm-gas-display"]', - text: '0.000042', + css: '[data-testid="first-gas-field"]', + text: '0 ETH', + }); + await driver.waitForSelector({ + css: '[data-testid="native-currency"]', + text: '$0.07', }); }, ); diff --git a/test/e2e/tests/transaction/incoming-transactions.spec.ts b/test/e2e/tests/transaction/incoming-transactions.spec.ts new file mode 100644 index 000000000000..e31d35c12841 --- /dev/null +++ b/test/e2e/tests/transaction/incoming-transactions.spec.ts @@ -0,0 +1,199 @@ +import { Mockttp } from 'mockttp'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; +import { Driver } from '../../webdriver/driver'; +import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; +import { withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { switchToNetworkFlow } from '../../page-objects/flows/network.flow'; +import HomePage from '../../page-objects/pages/home/homepage'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; + +const TIMESTAMP_MOCK = 1234; + +const RESPONSE_STANDARD_MOCK = { + hash: '0x1', + timestamp: new Date(TIMESTAMP_MOCK).toISOString(), + chainId: 1, + blockNumber: 1, + blockHash: '0x2', + gas: 1, + gasUsed: 1, + gasPrice: '1', + effectiveGasPrice: '1', + nonce: 1, + cumulativeGasUsed: 1, + methodId: null, + value: '1230000000000000000', + to: DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), + from: '0x2', + isError: false, + valueTransfers: [], +}; + +const RESPONSE_STANDARD_2_MOCK = { + ...RESPONSE_STANDARD_MOCK, + hash: '0x2', + value: '2340000000000000000', + timestamp: new Date(TIMESTAMP_MOCK - 1).toISOString(), +}; + +const RESPONSE_TOKEN_TRANSFER_MOCK = { + ...RESPONSE_STANDARD_MOCK, + to: '0x2', + valueTransfers: [ + { + contractAddress: '0x123', + decimal: 18, + symbol: 'ABC', + from: '0x2', + to: DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), + amount: '4560000000000000000', + }, + ], +}; + +const RESPONSE_OUTGOING_MOCK = { + ...RESPONSE_STANDARD_MOCK, + from: DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), + to: '0x2', +}; + +async function mockAccountsApi( + mockServer: Mockttp, + { + cursor, + transactions, + }: { cursor?: string; transactions?: Record[] } = {}, +) { + return [ + await mockServer + .forGet( + `https://accounts.api.cx.metamask.io/v1/accounts/${DEFAULT_FIXTURE_ACCOUNT.toLowerCase()}/transactions`, + ) + .withQuery(cursor ? { cursor } : {}) + .thenCallback(() => ({ + statusCode: 200, + json: { + data: transactions ?? [ + RESPONSE_STANDARD_MOCK, + RESPONSE_STANDARD_2_MOCK, + ], + pageInfo: { hasNextPage: false }, + }, + })), + ]; +} + +describe('Incoming Transactions', function () { + it('adds standard incoming transactions', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withIncomingTransactionsPreferences(true) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockAccountsApi, + }, + async ({ driver }: { driver: Driver }) => { + const activityList = await changeNetworkAndGoToActivity(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(2); + + await activityList.check_txAction('Receive', 1); + await activityList.check_txAmountInActivity('1.23 ETH', 1); + + await activityList.check_txAction('Receive', 2); + await activityList.check_txAmountInActivity('2.34 ETH', 2); + }, + ); + }); + + it('ignores token transfer transactions', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withIncomingTransactionsPreferences(true) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: (server: Mockttp) => + mockAccountsApi(server, { + transactions: [ + RESPONSE_STANDARD_MOCK, + RESPONSE_TOKEN_TRANSFER_MOCK, + ], + }), + }, + async ({ driver }: { driver: Driver }) => { + const activityList = await changeNetworkAndGoToActivity(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(1); + }, + ); + }); + + it('ignores outgoing transactions', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withIncomingTransactionsPreferences(true) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: (server: Mockttp) => + mockAccountsApi(server, { + transactions: [RESPONSE_STANDARD_MOCK, RESPONSE_OUTGOING_MOCK], + }), + }, + async ({ driver }: { driver: Driver }) => { + const activityList = await changeNetworkAndGoToActivity(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(1); + }, + ); + }); + + it('does nothing if preference disabled', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withIncomingTransactionsPreferences(false) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockAccountsApi, + }, + async ({ driver }: { driver: Driver }) => { + const activityList = await changeNetworkAndGoToActivity(driver); + await driver.delay(2000); + await activityList.check_noTxInActivity(); + }, + ); + }); + + it('ignores duplicate transactions already in state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withIncomingTransactionsPreferences(true) + .withTransactions([ + { + hash: RESPONSE_STANDARD_MOCK.hash, + txParams: { from: RESPONSE_STANDARD_MOCK.from }, + }, + ]) + .build(), + title: this.test?.fullTitle(), + testSpecificMock: mockAccountsApi, + }, + async ({ driver }: { driver: Driver }) => { + const activityList = await changeNetworkAndGoToActivity(driver); + await activityList.check_confirmedTxNumberDisplayedInActivity(1); + }, + ); + }); +}); + +async function changeNetworkAndGoToActivity(driver: Driver) { + await loginWithoutBalanceValidation(driver); + await switchToNetworkFlow(driver, 'Ethereum Mainnet'); + + const homepage = new HomePage(driver); + await homepage.goToActivityList(); + + return new ActivityListPage(driver); +} diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js index b7ad05b3a93d..16c921fab65f 100644 --- a/test/e2e/tests/transaction/multiple-transactions.spec.js +++ b/test/e2e/tests/transaction/multiple-transactions.spec.js @@ -6,7 +6,6 @@ const { unlockWallet, generateGanacheOptions, WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -24,8 +23,6 @@ describe('Multiple transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // initiates a transaction from the dapp await openDapp(driver); // creates first transaction @@ -40,8 +37,8 @@ describe('Multiple transactions', function () { // confirms second transaction await driver.waitForSelector({ - text: 'Reject 2 transactions', - tag: 'a', + text: 'Reject all', + tag: 'button', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); @@ -88,8 +85,6 @@ describe('Multiple transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // initiates a transaction from the dapp await openDapp(driver); // creates first transaction @@ -104,13 +99,13 @@ describe('Multiple transactions', function () { // rejects second transaction await driver.waitForSelector({ - text: 'Reject 2 transactions', - tag: 'a', + text: 'Reject all', + tag: 'button', }); - await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); await driver.assertElementNotPresent('.loading-overlay__spinner'); // rejects first transaction - await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle( @@ -121,11 +116,6 @@ describe('Multiple transactions', function () { '[data-testid="account-overview__activity-tab"]', ); - const isTransactionListEmpty = await driver.isElementPresentAndVisible( - '.transaction-list__empty-text', - ); - assert.equal(isTransactionListEmpty, true); - // The previous isTransactionListEmpty wait already serves as the guard here for the assertElementNotPresent await driver.assertElementNotPresent( '.transaction-list__completed-transactions .activity-list-item', diff --git a/test/e2e/tests/transaction/navigate-transactions.spec.js b/test/e2e/tests/transaction/navigate-transactions.spec.js index 16e8f9374cb5..0773756c66f0 100644 --- a/test/e2e/tests/transaction/navigate-transactions.spec.js +++ b/test/e2e/tests/transaction/navigate-transactions.spec.js @@ -1,9 +1,9 @@ +const { + default: Confirmation, +} = require('../../page-objects/pages/confirmations/redesign/confirmation'); const { createDappTransaction, } = require('../../page-objects/flows/transaction'); -const { - default: ConfirmationNavigation, -} = require('../../page-objects/pages/confirmations/legacy/navigation'); const { withFixtures, @@ -12,7 +12,6 @@ const { unlockWallet, generateGanacheOptions, WINDOW_TITLES, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -33,11 +32,9 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createRedesignedMultipleTransactions(driver, TRANSACTION_COUNT); - await createMultipleTransactions(driver, TRANSACTION_COUNT); - - const navigation = new ConfirmationNavigation(driver); + const navigation = new Confirmation(driver); await navigation.clickNextPage(); await navigation.check_pageNumbers(2, 4); @@ -48,17 +45,14 @@ describe('Navigate transactions', function () { await navigation.clickNextPage(); await navigation.check_pageNumbers(4, 4); - await navigation.clickFirstPage(); - await navigation.check_pageNumbers(1, 4); - - await navigation.clickLastPage(); - await navigation.check_pageNumbers(4, 4); - await navigation.clickPreviousPage(); await navigation.check_pageNumbers(3, 4); await navigation.clickPreviousPage(); await navigation.check_pageNumbers(2, 4); + + await navigation.clickPreviousPage(); + await navigation.check_pageNumbers(1, 4); }, ); }); @@ -77,11 +71,9 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await createMultipleTransactions(driver, TRANSACTION_COUNT); + await createRedesignedMultipleTransactions(driver, TRANSACTION_COUNT); - const navigation = new ConfirmationNavigation(driver); + const navigation = new Confirmation(driver); await navigation.clickNextPage(); await navigation.check_pageNumbers(2, 4); @@ -114,14 +106,12 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await createMultipleTransactions(driver, TRANSACTION_COUNT); + await createRedesignedMultipleTransactions(driver, TRANSACTION_COUNT); // reject transaction - await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); - const navigation = new ConfirmationNavigation(driver); + const navigation = new Confirmation(driver); await navigation.check_pageNumbers(1, 3); }, ); @@ -141,14 +131,12 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await createMultipleTransactions(driver, TRANSACTION_COUNT); + await createRedesignedMultipleTransactions(driver, TRANSACTION_COUNT); // confirm transaction await driver.clickElement({ text: 'Confirm', tag: 'button' }); - const navigation = new ConfirmationNavigation(driver); + const navigation = new Confirmation(driver); await navigation.check_pageNumbers(1, 3); }, ); @@ -168,12 +156,9 @@ describe('Navigate transactions', function () { async ({ driver, ganacheServer }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await createMultipleTransactions(driver, TRANSACTION_COUNT); + await createRedesignedMultipleTransactions(driver, TRANSACTION_COUNT); // reject transactions - await driver.clickElement({ text: 'Reject 4', tag: 'a' }); await driver.clickElement({ text: 'Reject all', tag: 'button' }); await driver.switchToWindowWithTitle( @@ -185,7 +170,7 @@ describe('Navigate transactions', function () { }); }); -async function createMultipleTransactions(driver, count) { +async function createRedesignedMultipleTransactions(driver, count) { for (let i = 0; i < count; i++) { await createDappTransaction(driver); } @@ -194,7 +179,7 @@ async function createMultipleTransactions(driver, count) { // Wait until total amount is loaded to mitigate flakiness on reject await driver.findElement({ - tag: 'span', - text: '0.001', + tag: 'h2', + text: '0.001 ETH', }); } diff --git a/test/e2e/tests/transaction/send-edit.spec.js b/test/e2e/tests/transaction/send-edit.spec.js index 8d19d6d071b1..31970c0dfaff 100644 --- a/test/e2e/tests/transaction/send-edit.spec.js +++ b/test/e2e/tests/transaction/send-edit.spec.js @@ -8,7 +8,6 @@ const { withFixtures, unlockWallet, generateGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -22,21 +21,26 @@ describe('Editing Confirm Transaction', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createInternalTransaction(driver); await driver.findElement({ - css: '.currency-display-component__text', - text: '1', + css: 'h2', + text: '1 ETH', + }); + + await driver.findElement({ + css: '[data-testid="first-gas-field"]', + text: '0 ETH', }); await driver.findElement({ - css: '.currency-display-component__text', - text: '1.000042', + css: '[data-testid="native-currency"]', + text: '$0.07', }); await driver.clickElement( - '.confirm-page-container-header__back-button', + '[data-testid="wallet-initiated-header-back-button"]', ); const inputAmount = await driver.findElement('input[placeholder="0"]'); @@ -48,7 +52,7 @@ describe('Editing Confirm Transaction', function () { await driver.clickElement({ text: 'Continue', tag: 'button' }); - await driver.clickElement({ text: 'Edit', tag: 'button' }); + await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); const [gasLimitInput, gasPriceInput] = await driver.findElements( 'input[type="number"]', @@ -58,13 +62,14 @@ describe('Editing Confirm Transaction', function () { await driver.clickElement({ text: 'Save', tag: 'button' }); // has correct updated value on the confirm screen the transaction - await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '0.0008', + await driver.findElement({ + css: '[data-testid="first-gas-field"]', + text: '0.0002 ETH', }); - await driver.waitForSelector({ - css: '.currency-display-component__suffix', - text: 'ETH', + + await driver.findElement({ + css: '[data-testid="native-currency"]', + text: '$0.29', }); // confirms the transaction @@ -98,22 +103,26 @@ describe('Editing Confirm Transaction', function () { }, async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); await createInternalTransaction(driver); await driver.findElement({ - css: '.currency-display-component__text', - text: '1', + css: 'h2', + text: '1 ETH', + }); + + await driver.findElement({ + css: '[data-testid="first-gas-field"]', + text: '0.0004 ETH', }); await driver.findElement({ - css: '.currency-display-component__text', - text: '1.00043983', + css: '[data-testid="native-currency"]', + text: '$0.75', }); await driver.clickElement( - '.confirm-page-container-header__back-button', + '[data-testid="wallet-initiated-header-back-button"]', ); const inputAmount = await driver.findElement('input[placeholder="0"]'); @@ -147,13 +156,14 @@ describe('Editing Confirm Transaction', function () { await driver.clickElement({ text: 'Save', tag: 'button' }); // has correct updated value on the confirm screen the transaction - await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '0.0008', + await driver.findElement({ + css: '[data-testid="first-gas-field"]', + text: '0.0002 ETH', }); - await driver.waitForSelector({ - css: '.currency-display-component__suffix', - text: 'ETH', + + await driver.findElement({ + css: '[data-testid="native-currency"]', + text: '$0.29', }); // confirms the transaction diff --git a/test/e2e/tests/transaction/send-eth.spec.js b/test/e2e/tests/transaction/send-eth.spec.js index 9ee1b58dc170..7f5d1c198049 100644 --- a/test/e2e/tests/transaction/send-eth.spec.js +++ b/test/e2e/tests/transaction/send-eth.spec.js @@ -9,7 +9,6 @@ const { editGasFeeForm, WINDOW_TITLES, defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -107,8 +106,6 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await driver.delay(1000); await openActionMenuAndStartSendFlow(driver); @@ -129,11 +126,12 @@ describe('Send ETH', function () { await driver.clickElement({ text: 'Continue', tag: 'button' }); await driver.delay(1000); - const transactionAmounts = await driver.findElements( - '.currency-display-component__text', - ); - const transactionAmount = transactionAmounts[0]; - assert.equal(await transactionAmount.getText(), '1'); + + // Transaction Amount + await driver.findElement({ + css: 'h2', + text: '1 ETH', + }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -164,25 +162,19 @@ describe('Send ETH', function () { smartContract, title: this.test.fullTitle(), }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); + async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); // Wait for balance to load await driver.delay(500); await driver.clickElement('[data-testid="eth-overview-send"]'); - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - contractAddress, - ); + await driver.clickElement({ text: 'Account 1', tag: 'button' }); const inputAmount = await driver.findElement( 'input[placeholder="0"]', ); - await inputAmount.press('1'); + await inputAmount.sendKeys('1'); // Continue to next screen await driver.clickElement({ text: 'Continue', tag: 'button' }); @@ -259,8 +251,6 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // initiates a send from the dapp await openDapp(driver); await driver.clickElement({ text: 'Send', tag: 'button' }); @@ -271,12 +261,7 @@ describe('Send ETH', function () { windowHandles, ); - await driver.assertElementNotPresent( - { text: 'Data', tag: 'li' }, - { findElementGuard: { text: 'Estimated gas fee', tag: 'h6' } }, // make sure the Dialog has loaded - ); - - await driver.clickElement({ text: 'Edit', tag: 'button' }); + await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); await driver.waitForSelector({ text: '0.00021 ETH', }); @@ -289,10 +274,16 @@ describe('Send ETH', function () { tag: 'header', }); await editGasFeeForm(driver, '21000', '100'); - await driver.waitForSelector({ - css: '.transaction-detail-item:nth-of-type(1) h6:nth-of-type(2)', + await driver.findElement({ + css: '[data-testid="first-gas-field"]', text: '0.0021 ETH', }); + + await driver.findElement({ + css: '[data-testid="native-currency"]', + text: '$3.57', + }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.waitUntilXWindowHandles(2); await driver.switchToWindow(extension); @@ -337,11 +328,12 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // initiates a transaction from the dapp await openDapp(driver); - await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await driver.clickElement({ + text: 'Create Token', + tag: 'button', + }); const windowHandles = await driver.waitUntilXWindowHandles(3); const extension = windowHandles[0]; @@ -350,11 +342,6 @@ describe('Send ETH', function () { windowHandles, ); - await driver.assertElementNotPresent( - { text: 'Data', tag: 'li' }, - { findElementGuard: { text: 'Estimated fee' } }, // make sure the Dialog has loaded - ); - await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); await driver.clickElement( '[data-testid="edit-gas-fee-item-custom"]', @@ -371,9 +358,14 @@ describe('Send ETH', function () { await driver.clickElement({ text: 'Save', tag: 'button' }); - await driver.waitForSelector({ - css: '.currency-display-component__text', - text: '0.0550741', + await driver.findElement({ + css: '[data-testid="first-gas-field"]', + text: '0.045 ETH', + }); + + await driver.findElement({ + css: '[data-testid="native-currency"]', + text: '$76.57', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -442,8 +434,6 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - await driver.assertElementNotPresent('.loading-overlay__spinner'); const balance = await driver.findElement( '[data-testid="eth-overview__primary-currency"]', @@ -468,12 +458,7 @@ describe('Send ETH', function () { }); await driver.clickElement({ text: 'Continue', tag: 'button' }); - await driver.findClickableElement( - '[data-testid="sender-to-recipient__name"]', - ); - await driver.clickElement( - '[data-testid="sender-to-recipient__name"]', - ); + await driver.clickElement('[data-testid="recipient-address"]'); const recipientAddress = await driver.findElements({ text: '0xc427D562164062a23a5cFf596A4a3208e72Acd28', diff --git a/test/e2e/tests/transaction/send-hex-address.spec.js b/test/e2e/tests/transaction/send-hex-address.spec.js index b6ad969c6735..f67f840c1688 100644 --- a/test/e2e/tests/transaction/send-hex-address.spec.js +++ b/test/e2e/tests/transaction/send-hex-address.spec.js @@ -3,7 +3,6 @@ const { withFixtures, logInWithBalanceValidation, openActionMenuAndStartSendFlow, - tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); const FixtureBuilder = require('../../fixture-builder'); @@ -57,6 +56,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () { }, ); }); + it('should ensure the address is prefixed with 0x when typed and should send ETH to a valid hexadecimal address', async function () { await withFixtures( { @@ -121,8 +121,6 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Send TST await driver.clickElement( '[data-testid="account-overview__asset-tab"]', @@ -145,8 +143,8 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { // Confirm transaction await driver.findElement({ - css: '.confirm-page-container-summary__title', - text: '0', + css: 'h2', + text: '0 ETH', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement( @@ -170,6 +168,7 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { }, ); }); + it('should ensure the address is prefixed with 0x when typed and should send TST to a valid hexadecimal address', async function () { await withFixtures( { @@ -185,8 +184,6 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); - await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Send TST await driver.clickElement( '[data-testid="account-overview__asset-tab"]', @@ -210,8 +207,8 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { // Confirm transaction await driver.findElement({ - css: '.confirm-page-container-summary__title', - text: '0', + css: 'h2', + text: '0 ETH', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement( diff --git a/test/e2e/tests/transaction/simple-send.spec.ts b/test/e2e/tests/transaction/simple-send.spec.ts deleted file mode 100644 index 7d2f4835cdca..000000000000 --- a/test/e2e/tests/transaction/simple-send.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Suite } from 'mocha'; -import { Driver } from '../../webdriver/driver'; -import { Ganache } from '../../seeder/ganache'; -import { - withFixtures, - defaultGanacheOptions, - tempToggleSettingRedesignedTransactionConfirmations, -} from '../../helpers'; -import FixtureBuilder from '../../fixture-builder'; -import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; -import { sendTransactionToAddress } from '../../page-objects/flows/send-transaction.flow'; -import HomePage from '../../page-objects/pages/homepage'; - -describe('Simple send eth', function (this: Suite) { - it('can send a simple transaction from one account to another', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ - driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - - await tempToggleSettingRedesignedTransactionConfirmations(driver); - - await sendTransactionToAddress({ - driver, - recipientAddress: '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - }); - const homePage = new HomePage(driver); - await homePage.check_pageIsLoaded(); - await homePage.check_confirmedTxNumberDisplayedInActivity(); - await homePage.check_txAmountInActivity(); - }, - ); - }); -}); diff --git a/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts b/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts index 7afedc3187dd..5e2665627910 100644 --- a/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts +++ b/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts @@ -4,7 +4,8 @@ import FixtureBuilder from '../../fixture-builder'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; -import HomePage from '../../page-objects/pages/homepage'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; +import HomePage from '../../page-objects/pages/home/homepage'; describe('Editing Confirm Transaction', function (this: Suite) { it('approves a transaction stuck in approved state on boot', async function () { @@ -25,10 +26,10 @@ describe('Editing Confirm Transaction', function (this: Suite) { }) => { await loginWithBalanceValidation(driver, ganacheServer); - const homePage = new HomePage(driver); - await homePage.goToActivityList(); - await homePage.check_completedTxNumberDisplayedInActivity(); - await homePage.check_txAmountInActivity(); + new HomePage(driver).goToActivityList(); + const activityList = new ActivityListPage(driver); + await activityList.check_completedTxNumberDisplayedInActivity(); + await activityList.check_txAmountInActivity(); }, ); }); diff --git a/test/e2e/vault-decryption-chrome.spec.ts b/test/e2e/vault-decryption-chrome.spec.ts index 939494b00a60..110b7726a614 100644 --- a/test/e2e/vault-decryption-chrome.spec.ts +++ b/test/e2e/vault-decryption-chrome.spec.ts @@ -5,7 +5,7 @@ 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 HomePage from './page-objects/pages/home/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'; diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 42fa0f018f6d..296f41cdbaec 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -355,7 +355,7 @@ class Driver { // bucket that can include the state attribute to wait for elements that // match the selector to be removed from the DOM. let element; - if (!['visible', 'detached'].includes(state)) { + if (!['visible', 'detached', 'enabled'].includes(state)) { throw new Error(`Provided state selector ${state} is not supported`); } if (state === 'visible') { @@ -368,7 +368,13 @@ class Driver { until.stalenessOf(await this.findElement(rawLocator)), timeout, ); + } else if (state === 'enabled') { + element = await this.driver.wait( + until.elementIsEnabled(await this.findElement(rawLocator)), + timeout, + ); } + return wrapElementWithAPI(element, this); } @@ -499,13 +505,14 @@ class Driver { * and returns a reference to the first matching element. * * @param {string | object} rawLocator - Element locator + * @param {number} timeout - Timeout in milliseconds * @returns {Promise} A promise that resolves to the found element. */ - async findElement(rawLocator) { + async findElement(rawLocator, timeout = this.timeout) { const locator = this.buildLocator(rawLocator); const element = await this.driver.wait( until.elementLocated(locator), - this.timeout, + timeout, ); return wrapElementWithAPI(element, this); } @@ -540,13 +547,14 @@ class Driver { * Finds a clickable element on the page using the given locator. * * @param {string | object} rawLocator - Element locator + * @param {number} timeout - Timeout in milliseconds * @returns {Promise} A promise that resolves to the found clickable element. */ - async findClickableElement(rawLocator) { - const element = await this.findElement(rawLocator); + async findClickableElement(rawLocator, timeout = this.timeout) { + const element = await this.findElement(rawLocator, timeout); await Promise.all([ - this.driver.wait(until.elementIsVisible(element), this.timeout), - this.driver.wait(until.elementIsEnabled(element), this.timeout), + this.driver.wait(until.elementIsVisible(element), timeout), + this.driver.wait(until.elementIsEnabled(element), timeout), ]); return wrapElementWithAPI(element, this); } @@ -601,10 +609,18 @@ class Driver { await element.click(); return; } catch (error) { - if ( - error.name === 'StaleElementReferenceError' && - attempt < retries - 1 - ) { + const retryableErrors = [ + 'StaleElementReferenceError', + 'ElementClickInterceptedError', + 'ElementNotInteractableError', + ]; + + if (retryableErrors.includes(error.name) && attempt < retries - 1) { + console.warn( + `Retrying click (attempt ${attempt + 1}/${retries}) due to: ${ + error.name + }`, + ); await this.delay(1000); } else { throw error; @@ -691,12 +707,15 @@ class Driver { async clickElementSafe(rawLocator, timeout = 1000) { try { const locator = this.buildLocator(rawLocator); - const elements = await this.driver.wait( until.elementsLocated(locator), timeout, ); + await Promise.all([ + this.driver.wait(until.elementIsVisible(elements[0]), timeout), + this.driver.wait(until.elementIsEnabled(elements[0]), timeout), + ]); await elements[0].click(); } catch (e) { console.log(`Element ${rawLocator} not found (${e})`); @@ -767,6 +786,29 @@ class Driver { ); } + /** + * Waits for a condition to be met within a given timeout period. + * + * @param {Function} condition - The condition to wait for. This function should return a boolean indicating whether the condition is met. + * @param {object} options - Options for the wait. + * @param {number} options.timeout - The maximum amount of time (in milliseconds) to wait for the condition to be met. + * @param {number} options.interval - The interval (in milliseconds) between checks for the condition. + * @returns {Promise} A promise that resolves when the condition is met or the timeout is reached. + * @throws {Error} Throws an error if the condition is not met within the timeout period. + */ + async waitUntil(condition, options) { + const { timeout, interval } = options; + const endTime = Date.now() + timeout; + + while (Date.now() < endTime) { + if (await condition()) { + return true; + } + await new Promise((resolve) => setTimeout(resolve, interval)); + } + throw new Error('Condition not met within timeout'); + } + /** * Checks if an element that matches the given locator is present on the page. * @@ -805,15 +847,13 @@ class Driver { * @returns {Promise} promise that resolves to the WebElement */ async pasteIntoField(rawLocator, contentToPaste) { - // Throw if double-quote is present in content to paste - // so that we don't have to worry about escaping double-quotes - if (contentToPaste.includes('"')) { - throw new Error('Cannot paste content with double-quote'); - } // Click to focus the field await this.clickElement(rawLocator); await this.executeScript( - `navigator.clipboard.writeText("${contentToPaste}")`, + `navigator.clipboard.writeText("${contentToPaste.replace( + /"/gu, + '\\"', + )}")`, ); await this.fill(rawLocator, Key.chord(this.Key.MODIFIER, 'v')); } diff --git a/test/integration/confirmations/signatures/permit-batch.test.tsx b/test/integration/confirmations/signatures/permit-batch.test.tsx new file mode 100644 index 000000000000..84c050d8e022 --- /dev/null +++ b/test/integration/confirmations/signatures/permit-batch.test.tsx @@ -0,0 +1,190 @@ +import { act, fireEvent, screen } from '@testing-library/react'; +import nock from 'nock'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; +import * as backgroundConnection from '../../../../ui/store/background-connection'; +import { tEn } from '../../../lib/i18n-helpers'; +import { integrationTestRender } from '../../../lib/render-helpers'; +import mockMetaMaskState from '../../data/integration-init-state.json'; +import { createMockImplementation } from '../../helpers'; +import { + getMetaMaskStateWithUnapprovedPermitSign, + verifyDetails, +} from './signature-helpers'; + +jest.mock('../../../../ui/store/background-connection', () => ({ + ...jest.requireActual('../../../../ui/store/background-connection'), + submitRequestToBackground: jest.fn(), +})); + +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + +const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const backgroundConnectionMocked = { + onNotification: jest.fn(), +}; +const mockedAssetDetails = jest.mocked(useAssetDetails); + +const renderPermitBatchSignature = async () => { + const account = + mockMetaMaskState.internalAccounts.accounts[ + mockMetaMaskState.internalAccounts + .selectedAccount as keyof typeof mockMetaMaskState.internalAccounts.accounts + ]; + + const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( + account.address, + 'PermitBatch', + ); + + await act(async () => { + await integrationTestRender({ + preloadedState: { + ...mockedMetaMaskState, + selectedNetworkClientId: 'testNetworkConfigurationId', + providerConfig: { + type: 'rpc', + nickname: 'test mainnet', + chainId: '0x1', + ticker: 'ETH', + id: 'chain1', + }, + }, + backgroundConnection: backgroundConnectionMocked, + }); + }); +}; + +describe('Permit Batch Signature Tests', () => { + beforeEach(() => { + jest.resetAllMocks(); + mockedBackgroundConnection.submitRequestToBackground.mockImplementation( + createMockImplementation({ + getTokenStandardAndDetails: { decimals: '2' }, + }), + ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('renders the permit batch signature with correct titles', async () => { + await renderPermitBatchSignature(); + + expect( + await screen.findByText(tEn('confirmTitlePermitTokens') as string), + ).toBeInTheDocument(); + expect( + await screen.findByText(tEn('confirmTitleDescPermitSignature') as string), + ).toBeInTheDocument(); + }); + + it('displays the correct details in the simulation section', async () => { + await renderPermitBatchSignature(); + + const simulationSection = await screen.findByTestId( + 'confirmation__simulation_section', + ); + expect(simulationSection).toBeInTheDocument(); + + const simulationDetails = [ + 'Estimated changes', + "You're giving the spender permission to spend this many tokens from your account.", + 'Spending cap', + '0xA0b86...6eB48', + 'Unlimited', + '0xb0B86...6EB48', + 'Unlimited', + ]; + + verifyDetails(simulationSection, simulationDetails); + }); + + it('displays correct request and message details', async () => { + await renderPermitBatchSignature(); + + const requestDetailsSection = await screen.findByTestId( + 'confirmation_request-section', + ); + const requestDetails = [ + 'Spender', + '0x3fC91...b7FAD', + 'Request from', + 'metamask.github.io', + 'Interacting with', + '0x00000...78BA3', + ]; + expect(requestDetailsSection).toBeInTheDocument(); + verifyDetails(requestDetailsSection, requestDetails); + + await act(async () => { + fireEvent.click(await screen.findByTestId('sectionCollapseButton')); + }); + + const messageDetailsSection = await screen.findByTestId( + 'confirmation_message-section', + ); + expect(messageDetailsSection).toBeInTheDocument(); + expect(messageDetailsSection).toHaveTextContent('Message'); + expect(messageDetailsSection).toHaveTextContent('Primary type:'); + expect(messageDetailsSection).toHaveTextContent('PermitBatch'); + + const messageData0 = await screen.findByTestId( + 'confirmation_data-0-index-0', + ); + const messageData1 = await screen.findByTestId( + 'confirmation_data-1-index-1', + ); + expect(messageDetailsSection).toContainElement(messageData0); + expect(messageDetailsSection).toContainElement(messageData1); + + const messageDetails = [ + { + element: messageData0, + content: [ + 'Token', + 'USDC', + 'Amount', + '1,461,501,637,3...', + 'Expiration', + '05 August 2024, 19:52', + 'Nonce', + '5', + ], + }, + { + element: messageData1, + content: [ + 'Token', + '0xb0B86...6EB48', + 'Amount', + '2,461,501,637,3...', + 'Expiration', + '05 August 2024, 19:54', + 'Nonce', + '6', + ], + }, + ]; + + messageDetails.forEach(({ element, content }) => { + verifyDetails(element, content); + }); + + expect(messageDetailsSection).toHaveTextContent('Spender'); + expect(messageDetailsSection).toHaveTextContent('0x3fC91...b7FAD'); + expect(messageDetailsSection).toHaveTextContent('SigDeadline'); + expect(messageDetailsSection).toHaveTextContent('06 July 2024, 20:22'); + }); +}); diff --git a/test/integration/confirmations/signatures/permit-seaport.test.tsx b/test/integration/confirmations/signatures/permit-seaport.test.tsx new file mode 100644 index 000000000000..dc32671da245 --- /dev/null +++ b/test/integration/confirmations/signatures/permit-seaport.test.tsx @@ -0,0 +1,209 @@ +import { act, fireEvent, screen } from '@testing-library/react'; +import nock from 'nock'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; +import * as backgroundConnection from '../../../../ui/store/background-connection'; +import { tEn } from '../../../lib/i18n-helpers'; +import { integrationTestRender } from '../../../lib/render-helpers'; +import mockMetaMaskState from '../../data/integration-init-state.json'; +import { createMockImplementation } from '../../helpers'; +import { + getMetaMaskStateWithUnapprovedPermitSign, + verifyDetails, +} from './signature-helpers'; + +jest.mock('../../../../ui/store/background-connection', () => ({ + ...jest.requireActual('../../../../ui/store/background-connection'), + submitRequestToBackground: jest.fn(), +})); + +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + +const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const backgroundConnectionMocked = { + onNotification: jest.fn(), +}; +const mockedAssetDetails = jest.mocked(useAssetDetails); + +const renderSeaportSignature = async () => { + const account = + mockMetaMaskState.internalAccounts.accounts[ + mockMetaMaskState.internalAccounts + .selectedAccount as keyof typeof mockMetaMaskState.internalAccounts.accounts + ]; + + const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( + account.address, + 'PermitSeaport', + ); + + await act(async () => { + await integrationTestRender({ + preloadedState: { + ...mockedMetaMaskState, + selectedNetworkClientId: 'testNetworkConfigurationId', + providerConfig: { + type: 'rpc', + nickname: 'test mainnet', + chainId: '0x1', + ticker: 'ETH', + id: 'chain1', + }, + }, + backgroundConnection: backgroundConnectionMocked, + }); + }); +}; + +describe('Permit Seaport Tests', () => { + beforeEach(() => { + jest.resetAllMocks(); + mockedBackgroundConnection.submitRequestToBackground.mockImplementation( + createMockImplementation({ + getTokenStandardAndDetails: { decimals: '2' }, + }), + ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('renders seaport signature', async () => { + await renderSeaportSignature(); + + expect( + await screen.findByText(tEn('confirmTitleSignature') as string), + ).toBeInTheDocument(); + expect( + await screen.findByText(tEn('confirmTitleDescSign') as string), + ).toBeInTheDocument(); + }); + + it('renders request details section', async () => { + await renderSeaportSignature(); + + const requestDetailsSection = await screen.findByTestId( + 'confirmation_request-section', + ); + + expect(requestDetailsSection).toBeInTheDocument(); + expect(requestDetailsSection).toHaveTextContent('Request from'); + expect(requestDetailsSection).toHaveTextContent('metamask.github.io'); + expect(requestDetailsSection).toHaveTextContent('Interacting with'); + expect(requestDetailsSection).toHaveTextContent('0x00000...78BA3'); + }); + + it('renders message details section', async () => { + await renderSeaportSignature(); + + fireEvent.click(screen.getByTestId('sectionCollapseButton')); + + const messageDetailsSection = await screen.findByTestId( + 'confirmation_message-section', + ); + expect(messageDetailsSection).toBeInTheDocument(); + const messageDetailsContent = [ + 'Message', + 'Primary type:', + 'OrderComponents', + 'Offerer', + '0x5a6f5...Ac994', + 'Zone', + '0x004C0...60C00', + 'Offer', + ]; + verifyDetails(messageDetailsSection, messageDetailsContent); + }); + + it('renders offer and consideration details', async () => { + await renderSeaportSignature(); + + fireEvent.click(screen.getByTestId('sectionCollapseButton')); + + const offers = await screen.findByTestId('confirmation_data-offer-index-2'); + const offerDetails0 = offers.querySelector( + '[data-testid="confirmation_data-0-index-0"]', + ); + const offerDetails1 = offers.querySelector( + '[data-testid="confirmation_data-1-index-1"]', + ); + const considerations = await screen.findByTestId( + 'confirmation_data-consideration-index-3', + ); + const considerationDetails0 = considerations.querySelector( + '[data-testid="confirmation_data-0-index-0"]', + ); + + expect(offerDetails0).toBeInTheDocument(); + expect(offerDetails1).toBeInTheDocument(); + expect(considerations).toBeInTheDocument(); + expect(considerationDetails0).toBeInTheDocument(); + + const details = [ + { + element: offerDetails0 as HTMLElement, + content: [ + 'ItemType', + '2', + 'Token', + 'MutantApeYachtClub', + 'IdentifierOrCriteria', + '26464', + 'StartAmount', + '1', + 'EndAmount', + '1', + ], + }, + { + element: offerDetails1 as HTMLElement, + content: [ + 'ItemType', + '2', + 'Token', + 'MutantApeYachtClub', + 'IdentifierOrCriteria', + '7779', + 'StartAmount', + '1', + 'EndAmount', + '1', + ], + }, + { + element: considerationDetails0 as HTMLElement, + content: [ + 'ItemType', + '2', + 'Token', + 'MutantApeYachtClub', + 'IdentifierOrCriteria', + '26464', + 'StartAmount', + '1', + 'EndAmount', + '1', + 'Recipient', + '0xDFdc0...25Cc1', + ], + }, + ]; + + details.forEach(({ element, content }) => { + if (element) { + verifyDetails(element, content); + } + }); + }); +}); diff --git a/test/integration/confirmations/signatures/permit-single.test.tsx b/test/integration/confirmations/signatures/permit-single.test.tsx new file mode 100644 index 000000000000..f9d3276e31ed --- /dev/null +++ b/test/integration/confirmations/signatures/permit-single.test.tsx @@ -0,0 +1,164 @@ +import { act, fireEvent, screen } from '@testing-library/react'; +import nock from 'nock'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; +import * as backgroundConnection from '../../../../ui/store/background-connection'; +import { tEn } from '../../../lib/i18n-helpers'; +import { integrationTestRender } from '../../../lib/render-helpers'; +import mockMetaMaskState from '../../data/integration-init-state.json'; +import { createMockImplementation } from '../../helpers'; +import { + getMetaMaskStateWithUnapprovedPermitSign, + verifyDetails, +} from './signature-helpers'; + +jest.mock('../../../../ui/store/background-connection', () => ({ + ...jest.requireActual('../../../../ui/store/background-connection'), + submitRequestToBackground: jest.fn(), +})); + +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + +const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const backgroundConnectionMocked = { + onNotification: jest.fn(), +}; +const mockedAssetDetails = jest.mocked(useAssetDetails); + +const renderSingleBatchSignature = async () => { + const account = + mockMetaMaskState.internalAccounts.accounts[ + mockMetaMaskState.internalAccounts + .selectedAccount as keyof typeof mockMetaMaskState.internalAccounts.accounts + ]; + + const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( + account.address, + 'PermitSingle', + ); + + await act(async () => { + await integrationTestRender({ + preloadedState: { + ...mockedMetaMaskState, + selectedNetworkClientId: 'testNetworkConfigurationId', + providerConfig: { + type: 'rpc', + nickname: 'test mainnet', + chainId: '0x1', + ticker: 'ETH', + id: 'chain1', + }, + }, + backgroundConnection: backgroundConnectionMocked, + }); + }); +}; + +describe('Permit Single Signature Tests', () => { + beforeEach(() => { + jest.resetAllMocks(); + mockedBackgroundConnection.submitRequestToBackground.mockImplementation( + createMockImplementation({ + getTokenStandardAndDetails: { decimals: '2' }, + }), + ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('renders permit single signature with correct titles', async () => { + await renderSingleBatchSignature(); + + expect( + await screen.findByText(tEn('confirmTitlePermitTokens') as string), + ).toBeInTheDocument(); + expect( + await screen.findByText(tEn('confirmTitleDescPermitSignature') as string), + ).toBeInTheDocument(); + }); + + it('displays correct details in simulation section', async () => { + await renderSingleBatchSignature(); + + const simulationSection = await screen.findByTestId( + 'confirmation__simulation_section', + ); + const simulationDetails = [ + 'Estimated changes', + "You're giving the spender permission to spend this many tokens from your account.", + 'Spending cap', + '0xA0b86...6eB48', + 'Unlimited', + ]; + + expect(simulationSection).toBeInTheDocument(); + verifyDetails(simulationSection, simulationDetails); + }); + + it('displays correct details in request section', async () => { + await renderSingleBatchSignature(); + + const requestDetailsSection = await screen.findByTestId( + 'confirmation_request-section', + ); + const requestDetails = [ + 'Spender', + '0x3fC91...b7FAD', + 'Request from', + 'metamask.github.io', + 'Interacting with', + '0x00000...78BA3', + ]; + + expect(requestDetailsSection).toBeInTheDocument(); + verifyDetails(requestDetailsSection, requestDetails); + }); + + it('displays correct details in message section', async () => { + await renderSingleBatchSignature(); + fireEvent.click(await screen.findByTestId('sectionCollapseButton')); + + const messageDetailsSection = await screen.findByTestId( + 'confirmation_message-section', + ); + const messageDetails = ['Message', 'Primary type:', 'PermitSingle']; + + expect(messageDetailsSection).toBeInTheDocument(); + verifyDetails(messageDetailsSection, messageDetails); + + const messageData0 = await screen.findByTestId( + 'confirmation_data-details-index-0', + ); + const messageData0Details = [ + 'Token', + 'USDC', + 'Amount', + '1,461,501,637,3...', + 'Expiration', + '05 August 2024, 19:52', + 'Nonce', + '5', + ]; + + expect(messageDetailsSection).toContainElement(messageData0); + verifyDetails(messageData0, messageData0Details); + + expect(messageDetailsSection).toHaveTextContent('Spender'); + expect(messageDetailsSection).toHaveTextContent('0x3fC91...b7FAD'); + expect(messageDetailsSection).toHaveTextContent('SigDeadline'); + expect(messageDetailsSection).toHaveTextContent('06 July 2024, 20:22'); + }); +}); diff --git a/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx b/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx new file mode 100644 index 000000000000..3f915967dc00 --- /dev/null +++ b/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx @@ -0,0 +1,144 @@ +import { act, screen } from '@testing-library/react'; +import nock from 'nock'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; +import * as backgroundConnection from '../../../../ui/store/background-connection'; +import { tEn } from '../../../lib/i18n-helpers'; +import { integrationTestRender } from '../../../lib/render-helpers'; +import mockMetaMaskState from '../../data/integration-init-state.json'; +import { createMockImplementation } from '../../helpers'; +import { + getMetaMaskStateWithUnapprovedPermitSign, + verifyDetails, +} from './signature-helpers'; + +jest.mock('../../../../ui/store/background-connection', () => ({ + ...jest.requireActual('../../../../ui/store/background-connection'), + submitRequestToBackground: jest.fn(), +})); + +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + +const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const backgroundConnectionMocked = { + onNotification: jest.fn(), +}; +const mockedAssetDetails = jest.mocked(useAssetDetails); + +const renderTradeOrderSignature = async () => { + const account = + mockMetaMaskState.internalAccounts.accounts[ + mockMetaMaskState.internalAccounts + .selectedAccount as keyof typeof mockMetaMaskState.internalAccounts.accounts + ]; + + const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( + account.address, + 'TradeOrder', + ); + + await act(async () => { + await integrationTestRender({ + preloadedState: { + ...mockedMetaMaskState, + selectedNetworkClientId: 'testNetworkConfigurationId', + providerConfig: { + type: 'rpc', + nickname: 'test mainnet', + chainId: '0x1', + ticker: 'ETH', + id: 'chain1', + }, + }, + backgroundConnection: backgroundConnectionMocked, + }); + }); +}; + +describe('Permit Trade Order Tests', () => { + beforeEach(() => { + jest.resetAllMocks(); + mockedBackgroundConnection.submitRequestToBackground.mockImplementation( + createMockImplementation({ + getTokenStandardAndDetails: { decimals: '2' }, + }), + ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('renders trade order signature with correct titles', async () => { + await renderTradeOrderSignature(); + + expect( + await screen.findByText(tEn('confirmTitleSignature') as string), + ).toBeInTheDocument(); + expect( + await screen.findByText(tEn('confirmTitleDescSign') as string), + ).toBeInTheDocument(); + }); + + it('displays correct details in request section', async () => { + await renderTradeOrderSignature(); + + const requestDetailsSection = await screen.findByTestId( + 'confirmation_request-section', + ); + const requestDetails = [ + 'Request from', + 'metamask.github.io', + 'Interacting with', + '0xDef1C...25EfF', + ]; + + expect(requestDetailsSection).toBeInTheDocument(); + verifyDetails(requestDetailsSection, requestDetails); + }); + + it('displays correct details in message section', async () => { + await renderTradeOrderSignature(); + const messageDetailsSection = await screen.findByTestId( + 'confirmation_message-section', + ); + const messageDetails = [ + 'Message', + 'Primary type:', + 'ERC721Order', + 'Direction', + '0', + 'Maker', + '0x8Eeee...73D12', + 'Taker', + '0xCD2a3...DD826', + 'Expiry', + '2524604400', + 'Nonce', + '100131415900000000000000000000000000000083840314483690155566137712510085002484', + 'Erc20Token', + 'Wrapped Ether', + 'Erc20TokenAmount', + '42000000000000', + 'Fees', + 'Erc721Token', + 'Doodles', + 'Erc721TokenId', + '2516', + 'Erc721TokenProperties', + ]; + + expect(messageDetailsSection).toBeInTheDocument(); + verifyDetails(messageDetailsSection, messageDetails); + }); +}); diff --git a/test/integration/confirmations/signatures/permit.test.tsx b/test/integration/confirmations/signatures/permit.test.tsx index 7af3be743f5f..332cebc3a6b2 100644 --- a/test/integration/confirmations/signatures/permit.test.tsx +++ b/test/integration/confirmations/signatures/permit.test.tsx @@ -1,7 +1,5 @@ -import { ApprovalType } from '@metamask/controller-utils'; import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import nock from 'nock'; -import { CHAIN_IDS } from '@metamask/transaction-controller'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { MetaMetricsEventCategory, @@ -9,66 +7,32 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { shortenAddress } from '../../../../ui/helpers/utils/util'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { integrationTestRender } from '../../../lib/render-helpers'; import mockMetaMaskState from '../../data/integration-init-state.json'; import { createMockImplementation } from '../../helpers'; +import { getMetaMaskStateWithUnapprovedPermitSign } from './signature-helpers'; jest.mock('../../../../ui/store/background-connection', () => ({ ...jest.requireActual('../../../../ui/store/background-connection'), submitRequestToBackground: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); const backgroundConnectionMocked = { onNotification: jest.fn(), }; - -const getMetaMaskStateWithUnapprovedPermitSign = (accountAddress: string) => { - const pendingPermitId = 'eae47d40-42a3-11ef-9253-b105fa7dfc9c'; - const pendingPermitTime = new Date().getTime(); - const messageParams = { - from: accountAddress, - version: 'v4', - data: `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"${accountAddress}","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}`, - origin: 'https://metamask.github.io', - signatureMethod: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, - }; - return { - ...mockMetaMaskState, - preferences: { - ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, - }, - unapprovedTypedMessages: { - [pendingPermitId]: { - id: pendingPermitId, - chainId: CHAIN_IDS.SEPOLIA, - status: 'unapproved', - time: pendingPermitTime, - type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA, - securityProviderResponse: null, - msgParams: messageParams, - }, - }, - unapprovedTypedMessagesCount: 1, - pendingApprovals: { - [pendingPermitId]: { - id: pendingPermitId, - origin: 'origin', - time: pendingPermitTime, - type: ApprovalType.EthSignTypedData, - requestData: { - ...messageParams, - metamaskId: pendingPermitId, - }, - requestState: null, - expectsResult: false, - }, - }, - pendingApprovalCount: 1, - }; -}; +const mockedAssetDetails = jest.mocked(useAssetDetails); describe('Permit Confirmation', () => { beforeEach(() => { @@ -78,6 +42,10 @@ describe('Permit Confirmation', () => { getTokenStandardAndDetails: { decimals: '2', standard: 'ERC20' }, }), ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { @@ -94,6 +62,7 @@ describe('Permit Confirmation', () => { const accountName = account.metadata.name; const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( account.address, + 'Permit', ); await act(async () => { @@ -181,6 +150,7 @@ describe('Permit Confirmation', () => { const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( account.address, + 'Permit', ); await act(async () => { @@ -239,6 +209,7 @@ describe('Permit Confirmation', () => { const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( account.address, + 'Permit', ); await act(async () => { @@ -294,6 +265,7 @@ describe('Permit Confirmation', () => { const mockedMetaMaskState = getMetaMaskStateWithUnapprovedPermitSign( account.address, + 'Permit', ); await act(async () => { diff --git a/test/integration/confirmations/signatures/personalSign.test.tsx b/test/integration/confirmations/signatures/personalSign.test.tsx index 03685f46ab7b..2e614d26ddad 100644 --- a/test/integration/confirmations/signatures/personalSign.test.tsx +++ b/test/integration/confirmations/signatures/personalSign.test.tsx @@ -1,6 +1,6 @@ import { ApprovalType } from '@metamask/controller-utils'; -import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import { CHAIN_IDS } from '@metamask/transaction-controller'; +import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { MetaMetricsEventCategory, @@ -8,6 +8,7 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { shortenAddress } from '../../../../ui/helpers/utils/util'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { integrationTestRender } from '../../../lib/render-helpers'; import mockMetaMaskState from '../../data/integration-init-state.json'; @@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ submitRequestToBackground: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -30,7 +41,6 @@ const getMetaMaskStateWithUnapprovedPersonalSign = (accountAddress: string) => { ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, }, unapprovedPersonalMsgs: { [pendingPersonalSignId]: { @@ -68,6 +78,10 @@ const getMetaMaskStateWithUnapprovedPersonalSign = (accountAddress: string) => { describe('PersonalSign Confirmation', () => { beforeEach(() => { jest.resetAllMocks(); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); it('displays the header account modal with correct data', async () => { @@ -82,35 +96,42 @@ describe('PersonalSign Confirmation', () => { account.address, ); - const { findByTestId, getByTestId, queryByTestId } = + await act(async () => { await integrationTestRender({ preloadedState: mockedMetaMaskState, backgroundConnection: backgroundConnectionMocked, }); + }); - expect(await findByTestId('header-account-name')).toHaveTextContent( + expect(await screen.findByTestId('header-account-name')).toHaveTextContent( accountName, ); - expect(await findByTestId('header-network-display-name')).toHaveTextContent( - 'Sepolia', - ); + expect( + await screen.findByTestId('header-network-display-name'), + ).toHaveTextContent('Sepolia'); - fireEvent.click(await findByTestId('header-info__account-details-button')); + fireEvent.click( + await screen.findByTestId('header-info__account-details-button'), + ); - await waitFor(() => { - expect( - getByTestId('confirmation-account-details-modal__account-name'), - ).toBeInTheDocument(); - }); + expect( + await screen.findByTestId( + 'confirmation-account-details-modal__account-name', + ), + ).toBeInTheDocument(); expect( - await findByTestId('confirmation-account-details-modal__account-name'), + await screen.findByTestId( + 'confirmation-account-details-modal__account-name', + ), ).toHaveTextContent(accountName); - expect(await findByTestId('address-copy-button-text')).toHaveTextContent( - '0x0DCD5...3E7bc', - ); expect( - await findByTestId('confirmation-account-details-modal__account-balance'), + await screen.findByTestId('address-copy-button-text'), + ).toHaveTextContent('0x0DCD5...3E7bc'); + expect( + await screen.findByTestId( + 'confirmation-account-details-modal__account-balance', + ), ).toHaveTextContent('1.582717SepoliaETH'); let confirmAccountDetailsModalMetricsEvent; @@ -140,12 +161,16 @@ describe('PersonalSign Confirmation', () => { ); fireEvent.click( - await findByTestId('confirmation-account-details-modal__close-button'), + await screen.findByTestId( + 'confirmation-account-details-modal__close-button', + ), ); await waitFor(() => { expect( - queryByTestId('confirmation-account-details-modal__account-name'), + screen.queryByTestId( + 'confirmation-account-details-modal__account-name', + ), ).not.toBeInTheDocument(); }); }); @@ -189,9 +214,11 @@ describe('PersonalSign Confirmation', () => { account.address, ); - const { findByText } = await integrationTestRender({ - preloadedState: mockedMetaMaskState, - backgroundConnection: backgroundConnectionMocked, + await act(async () => { + await integrationTestRender({ + preloadedState: mockedMetaMaskState, + backgroundConnection: backgroundConnectionMocked, + }); }); const mismatchAccountText = `Your selected account (${shortenAddress( @@ -200,6 +227,6 @@ describe('PersonalSign Confirmation', () => { account.address, )})`; - expect(await findByText(mismatchAccountText)).toBeInTheDocument(); + expect(await screen.findByText(mismatchAccountText)).toBeInTheDocument(); }); }); diff --git a/test/integration/confirmations/signatures/signature-helpers.ts b/test/integration/confirmations/signatures/signature-helpers.ts new file mode 100644 index 000000000000..1ae575ff14c3 --- /dev/null +++ b/test/integration/confirmations/signatures/signature-helpers.ts @@ -0,0 +1,98 @@ +import { ApprovalType } from '@metamask/controller-utils'; +import { MESSAGE_TYPE } from '../../../../shared/constants/app'; +import mockMetaMaskState from '../../data/integration-init-state.json'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; + +const PERMIT_DATA = `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"{ownerAddress}","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}`; + +const PERMIT_BATCH_DATA = `{"types":{"PermitBatch":[{"name":"details","type":"PermitDetails[]"},{"name":"spender","type":"address"},{"name":"sigDeadline","type":"uint256"}],"PermitDetails":[{"name":"token","type":"address"},{"name":"amount","type":"uint160"},{"name":"expiration","type":"uint48"},{"name":"nonce","type":"uint48"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Permit2","chainId":"1","verifyingContract":"0x000000000022d473030f116ddee9f6b43ac78ba3"},"primaryType":"PermitBatch","message":{"details":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"1461501637330902918203684832716283019655932542975","expiration":"1722887542","nonce":"5"},{"token":"0xb0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"2461501637330902918203684832716283019655932542975","expiration":"1722887642","nonce":"6"}],"spender":"0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad","sigDeadline":"1720297342"}}`; + +const PERMIT_SINGLE_DATA = `{"types":{"PermitSingle":[{"name":"details","type":"PermitDetails"},{"name":"spender","type":"address"},{"name":"sigDeadline","type":"uint256"}],"PermitDetails":[{"name":"token","type":"address"},{"name":"amount","type":"uint160"},{"name":"expiration","type":"uint48"},{"name":"nonce","type":"uint48"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Permit2","chainId":"1","verifyingContract":"0x000000000022d473030f116ddee9f6b43ac78ba3"},"primaryType":"PermitSingle","message":{"details":{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"1461501637330902918203684832716283019655932542975","expiration":"1722887542","nonce":"5"},"spender":"0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad","sigDeadline":"1720297342"}}`; + +const SEAPORT_DATA = `{"types":{"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Seaport","version":"1.1","chainId":1,"verifyingContract":"0x000000000022d473030f116ddee9f6b43ac78ba3"},"primaryType":"OrderComponents","message":{"offerer":"0x5a6f5477bdeb7801ba137a9f0dc39c0599bac994","zone":"0x004c00500000ad104d7dbd00e3ae0a5c00560c00","offer":[{"itemType":"2","token":"0x60e4d786628fea6478f785a6d7e704777c86a7c6","identifierOrCriteria":"26464","startAmount":"1","endAmount":"1"},{"itemType":"2","token":"0x60e4d786628fea6478f785a6d7e704777c86a7c6","identifierOrCriteria":"7779","startAmount":"1","endAmount":"1"},{"itemType":"2","token":"0x60e4d786628fea6478f785a6d7e704777c86a7c6","identifierOrCriteria":"4770","startAmount":"1","endAmount":"1"},{"itemType":"2","token":"0xba30e5f9bb24caa003e9f2f0497ad287fdf95623","identifierOrCriteria":"9594","startAmount":"1","endAmount":"1"},{"itemType":"2","token":"0xba30e5f9bb24caa003e9f2f0497ad287fdf95623","identifierOrCriteria":"2118","startAmount":"1","endAmount":"1"},{"itemType":"2","token":"0xba30e5f9bb24caa003e9f2f0497ad287fdf95623","identifierOrCriteria":"1753","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"2","token":"0x60e4d786628fea6478f785a6d7e704777c86a7c6","identifierOrCriteria":"26464","startAmount":"1","endAmount":"1","recipient":"0xdfdc0b1cf8e9950d6a860af6501c4fecf7825cc1"},{"itemType":"2","token":"0x60e4d786628fea6478f785a6d7e704777c86a7c6","identifierOrCriteria":"7779","startAmount":"1","endAmount":"1","recipient":"0xdfdc0b1cf8e9950d6a860af6501c4fecf7825cc1"},{"itemType":"2","token":"0x60e4d786628fea6478f785a6d7e704777c86a7c6","identifierOrCriteria":"4770","startAmount":"1","endAmount":"1","recipient":"0xdfdc0b1cf8e9950d6a860af6501c4fecf7825cc1"},{"itemType":"2","token":"0xba30e5f9bb24caa003e9f2f0497ad287fdf95623","identifierOrCriteria":"9594","startAmount":"1","endAmount":"1","recipient":"0xdfdc0b1cf8e9950d6a860af6501c4fecf7825cc1"},{"itemType":"2","token":"0xba30e5f9bb24caa003e9f2f0497ad287fdf95623","identifierOrCriteria":"2118","startAmount":"1","endAmount":"1","recipient":"0xdfdc0b1cf8e9950d6a860af6501c4fecf7825cc1"},{"itemType":"2","token":"0xba30e5f9bb24caa003e9f2f0497ad287fdf95623","identifierOrCriteria":"1753","startAmount":"1","endAmount":"1","recipient":"0xdfdc0b1cf8e9950d6a860af6501c4fecf7825cc1"}],"orderType":"2","startTime":"1681810415","endTime":"1681983215","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"1550213294656772168494388599483486699884316127427085531712538817979596","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","counter":"0"}}`; + +const TRADE_ORDER_DATA = `{"types":{"ERC721Order":[{"type":"uint8","name":"direction"},{"type":"address","name":"maker"},{"type":"address","name":"taker"},{"type":"uint256","name":"expiry"},{"type":"uint256","name":"nonce"},{"type":"address","name":"erc20Token"},{"type":"uint256","name":"erc20TokenAmount"},{"type":"Fee[]","name":"fees"},{"type":"address","name":"erc721Token"},{"type":"uint256","name":"erc721TokenId"},{"type":"Property[]","name":"erc721TokenProperties"}],"Fee":[{"type":"address","name":"recipient"},{"type":"uint256","name":"amount"},{"type":"bytes","name":"feeData"}],"Property":[{"type":"address","name":"propertyValidator"},{"type":"bytes","name":"propertyData"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"ZeroEx","version":"1.0.0","chainId":"0x1","verifyingContract":"0xdef1c0ded9bec7f1a1670819833240f027b25eff"},"primaryType":"ERC721Order","message":{"direction":"0","maker":"0x8eeee1781fd885ff5ddef7789486676961873d12","taker":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826","expiry":"2524604400","nonce":"100131415900000000000000000000000000000083840314483690155566137712510085002484","erc20Token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","erc20TokenAmount":"42000000000000","fees":[],"erc721Token":"0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e","erc721TokenId":"2516","erc721TokenProperties":[]}}`; + +const getPermitData = (permitType: string, accountAddress: string) => { + switch (permitType) { + case 'Permit': + return PERMIT_DATA.replace('{ownerAddress}', accountAddress); + case 'PermitBatch': + return PERMIT_BATCH_DATA; + case 'PermitSingle': + return PERMIT_SINGLE_DATA; + case 'PermitSeaport': + return SEAPORT_DATA; + case 'TradeOrder': + return TRADE_ORDER_DATA; + default: + throw new Error(`Unknown permit type: ${permitType}`); + } +}; + +const getMessageParams = (accountAddress: string, data: string) => ({ + from: accountAddress, + version: 'v4', + data, + origin: 'https://metamask.github.io', + signatureMethod: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, +}); + +export const getMetaMaskStateWithUnapprovedPermitSign = ( + accountAddress: string, + permitType: + | 'Permit' + | 'PermitBatch' + | 'PermitSingle' + | 'PermitSeaport' + | 'TradeOrder', +) => { + const data = getPermitData(permitType, accountAddress); + const pendingPermitId = '48a75190-45ca-11ef-9001-f3886ec2397c'; + const pendingPermitTime = new Date().getTime(); + const messageParams = getMessageParams(accountAddress, data); + + const unapprovedTypedMessage = { + id: pendingPermitId, + status: 'unapproved', + time: pendingPermitTime, + type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA, + securityProviderResponse: null, + msgParams: messageParams, + chainId: CHAIN_IDS.SEPOLIA, + }; + + const pendingApproval = { + id: pendingPermitId, + origin: 'origin', + time: pendingPermitTime, + type: ApprovalType.EthSignTypedData, + requestData: { + ...messageParams, + metamaskId: pendingPermitId, + }, + requestState: null, + expectsResult: false, + }; + + return { + ...mockMetaMaskState, + preferences: { + ...mockMetaMaskState.preferences, + }, + unapprovedTypedMessages: { + [pendingPermitId]: unapprovedTypedMessage, + }, + unapprovedTypedMessagesCount: 1, + pendingApprovals: { + [pendingPermitId]: pendingApproval, + }, + pendingApprovalCount: 1, + }; +}; + +export const verifyDetails = (element: Element, expectedValues: string[]) => { + expectedValues.forEach((value) => { + expect(element).toHaveTextContent(value); + }); +}; diff --git a/test/integration/confirmations/transactions/alerts.test.tsx b/test/integration/confirmations/transactions/alerts.test.tsx index 1bbf9d1fd2c5..6010deb6f841 100644 --- a/test/integration/confirmations/transactions/alerts.test.tsx +++ b/test/integration/confirmations/transactions/alerts.test.tsx @@ -1,12 +1,13 @@ import { randomUUID } from 'crypto'; -import { act, fireEvent, screen } from '@testing-library/react'; import { ApprovalType } from '@metamask/controller-utils'; +import { act, fireEvent, screen } from '@testing-library/react'; import nock from 'nock'; -import mockMetaMaskState from '../../data/integration-init-state.json'; -import { integrationTestRender } from '../../../lib/render-helpers'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; -import { createMockImplementation, mock4byte } from '../../helpers'; +import { integrationTestRender } from '../../../lib/render-helpers'; import { createTestProviderTools } from '../../../stub/provider'; +import mockMetaMaskState from '../../data/integration-init-state.json'; +import { createMockImplementation, mock4byte } from '../../helpers'; import { getUnapprovedApproveTransaction } from './transactionDataHelpers'; jest.mock('../../../../ui/store/background-connection', () => ({ @@ -15,7 +16,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -30,7 +41,6 @@ const getMetaMaskStateWithUnapprovedApproveTransaction = ( ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, }, pendingApprovals: { [pendingTransactionId]: { @@ -92,6 +102,10 @@ describe('Contract Interaction Confirmation Alerts', () => { setupSubmitRequestToBackgroundMocks(); const APPROVE_NFT_HEX_SIG = '0x095ea7b3'; mock4byte(APPROVE_NFT_HEX_SIG); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { @@ -362,7 +376,7 @@ describe('Contract Interaction Confirmation Alerts', () => { expect( await screen.findByTestId('alert-modal__selected-alert'), ).toHaveTextContent( - 'This transaction won’t go through until a previous transaction is complete. Learn how to cancel or speed up a transaction.', + "This transaction won't go through until a previous transaction is complete. Learn how to cancel or speed up a transaction.", ); }); @@ -411,4 +425,73 @@ describe('Contract Interaction Confirmation Alerts', () => { await screen.findByTestId('alert-modal-action-showGasFeeModal'), ).toHaveTextContent('Update gas options'); }); + + it('displays the alert for signing and submitting alerts', async () => { + const account = + mockMetaMaskState.internalAccounts.accounts[ + mockMetaMaskState.internalAccounts + .selectedAccount as keyof typeof mockMetaMaskState.internalAccounts.accounts + ]; + + const mockedMetaMaskState = + getMetaMaskStateWithUnapprovedApproveTransaction(account.address); + const unapprovedTransaction = mockedMetaMaskState.transactions[0]; + const signedTransaction = getUnapprovedApproveTransaction( + account.address, + randomUUID(), + pendingTransactionTime - 1000, + ); + signedTransaction.status = 'signed'; + + await act(async () => { + await integrationTestRender({ + preloadedState: { + ...mockedMetaMaskState, + gasEstimateType: 'none', + pendingApprovalCount: 2, + pendingApprovals: { + [pendingTransactionId]: { + id: pendingTransactionId, + origin: 'origin', + time: pendingTransactionTime, + type: ApprovalType.Transaction, + requestData: { + txId: pendingTransactionId, + }, + requestState: null, + expectsResult: false, + }, + [signedTransaction.id]: { + id: signedTransaction.id, + origin: 'origin', + time: pendingTransactionTime - 1000, + type: ApprovalType.Transaction, + requestData: { + txId: signedTransaction.id, + }, + requestState: null, + expectsResult: false, + }, + }, + transactions: [unapprovedTransaction, signedTransaction], + }, + backgroundConnection: backgroundConnectionMocked, + }); + }); + + const alerts = await screen.findAllByTestId('confirm-banner-alert'); + + expect( + alerts.some((alert) => + alert.textContent?.includes( + 'A previous transaction is still being signed or submitted', + ), + ), + ).toBe(true); + + expect( + await screen.findByTestId('confirm-footer-button'), + ).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-footer-button')).toBeDisabled(); + }); }); diff --git a/test/integration/confirmations/transactions/contract-deployment.test.tsx b/test/integration/confirmations/transactions/contract-deployment.test.tsx index 7698ce3ef8b2..ea2ab8c09206 100644 --- a/test/integration/confirmations/transactions/contract-deployment.test.tsx +++ b/test/integration/confirmations/transactions/contract-deployment.test.tsx @@ -13,6 +13,7 @@ import { MetaMetricsEventLocation, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { tEn } from '../../../lib/i18n-helpers'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -26,7 +27,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -45,7 +56,6 @@ const getMetaMaskStateWithUnapprovedContractDeployment = ({ ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, showConfirmationAdvancedDetails, }, nextNonce: '8', @@ -136,6 +146,10 @@ describe('Contract Deployment Confirmation', () => { setupSubmitRequestToBackgroundMocks(); const DEPOSIT_HEX_SIG = '0xd0e30db0'; mock4byte(DEPOSIT_HEX_SIG); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { diff --git a/test/integration/confirmations/transactions/contract-interaction.test.tsx b/test/integration/confirmations/transactions/contract-interaction.test.tsx index 5db121bc9fda..c208ae153ab4 100644 --- a/test/integration/confirmations/transactions/contract-interaction.test.tsx +++ b/test/integration/confirmations/transactions/contract-interaction.test.tsx @@ -13,6 +13,7 @@ import { MetaMetricsEventLocation, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { tEn } from '../../../lib/i18n-helpers'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -31,7 +32,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -50,7 +61,6 @@ const getMetaMaskStateWithUnapprovedContractInteraction = ({ ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, showConfirmationAdvancedDetails, }, nextNonce: '8', @@ -156,6 +166,10 @@ describe('Contract Interaction Confirmation', () => { setupSubmitRequestToBackgroundMocks(); const MINT_NFT_HEX_SIG = '0x3b4b1381'; mock4byte(MINT_NFT_HEX_SIG); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { diff --git a/test/integration/confirmations/transactions/erc20-approve.test.tsx b/test/integration/confirmations/transactions/erc20-approve.test.tsx index c25b2ee3627d..5a22666b6718 100644 --- a/test/integration/confirmations/transactions/erc20-approve.test.tsx +++ b/test/integration/confirmations/transactions/erc20-approve.test.tsx @@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import nock from 'nock'; import { TokenStandard } from '../../../../shared/constants/transaction'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { tEn } from '../../../lib/i18n-helpers'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockImplementation(() => ({ + decimals: '4', + })), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -38,7 +49,6 @@ const getMetaMaskStateWithUnapprovedApproveTransaction = (opts?: { ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, showConfirmationAdvancedDetails: opts?.showAdvanceDetails ?? false, }, pendingApprovals: { @@ -140,6 +150,10 @@ describe('ERC20 Approve Confirmation', () => { const APPROVE_ERC20_HEX_SIG = '0x095ea7b3'; const APPROVE_ERC20_TEXT_SIG = 'approve(address,uint256)'; mock4byte(APPROVE_ERC20_HEX_SIG, APPROVE_ERC20_TEXT_SIG); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { diff --git a/test/integration/confirmations/transactions/erc721-approve.test.tsx b/test/integration/confirmations/transactions/erc721-approve.test.tsx index c158717cc9c9..a4a38d134d9d 100644 --- a/test/integration/confirmations/transactions/erc721-approve.test.tsx +++ b/test/integration/confirmations/transactions/erc721-approve.test.tsx @@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import nock from 'nock'; import { TokenStandard } from '../../../../shared/constants/transaction'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { tEn } from '../../../lib/i18n-helpers'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -38,7 +49,6 @@ const getMetaMaskStateWithUnapprovedApproveTransaction = (opts?: { ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, showConfirmationAdvancedDetails: opts?.showAdvanceDetails ?? false, }, pendingApprovals: { @@ -140,6 +150,10 @@ describe('ERC721 Approve Confirmation', () => { const APPROVE_NFT_HEX_SIG = '0x095ea7b3'; const APPROVE_NFT_TEXT_SIG = 'approve(address,uint256)'; mock4byte(APPROVE_NFT_HEX_SIG, APPROVE_NFT_TEXT_SIG); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { @@ -163,7 +177,9 @@ describe('ERC721 Approve Confirmation', () => { }); expect( - await screen.findByText(tEn('confirmTitleApproveTransaction') as string), + await screen.findByText( + tEn('confirmTitleApproveTransactionNFT') as string, + ), ).toBeInTheDocument(); expect( await screen.findByText( @@ -218,6 +234,7 @@ describe('ERC721 Approve Confirmation', () => { const approveDetails = await screen.findByTestId( 'confirmation__approve-details', ); + expect(approveDetails).toBeInTheDocument(); const approveDetailsSpender = await screen.findByTestId( 'confirmation__approve-spender', diff --git a/test/integration/confirmations/transactions/increase-allowance.test.tsx b/test/integration/confirmations/transactions/increase-allowance.test.tsx index 810477d3a3a5..4d6f552dca8a 100644 --- a/test/integration/confirmations/transactions/increase-allowance.test.tsx +++ b/test/integration/confirmations/transactions/increase-allowance.test.tsx @@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import nock from 'nock'; import { TokenStandard } from '../../../../shared/constants/transaction'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { tEn } from '../../../lib/i18n-helpers'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -38,7 +49,6 @@ const getMetaMaskStateWithUnapprovedIncreaseAllowanceTransaction = (opts?: { ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, showConfirmationAdvancedDetails: opts?.showAdvanceDetails ?? false, }, pendingApprovals: { @@ -144,6 +154,10 @@ describe('ERC20 increaseAllowance Confirmation', () => { INCREASE_ALLOWANCE_ERC20_HEX_SIG, INCREASE_ALLOWANCE_ERC20_TEXT_SIG, ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { @@ -198,7 +212,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { 'simulation-token-value', ); expect(simulationSection).toContainElement(spendingCapValue); - expect(spendingCapValue).toHaveTextContent('1'); + expect(spendingCapValue).toHaveTextContent('3'); expect(simulationSection).toHaveTextContent('0x07614...3ad68'); }); @@ -225,7 +239,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { expect(approveDetails).toContainElement(approveDetailsSpender); expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string); - expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); + expect(approveDetailsSpender).toHaveTextContent('0x9bc5b...AfEF4'); const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); @@ -286,7 +300,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { ); expect(spendingCapSection).toContainElement(spendingCapGroup); expect(spendingCapGroup).toHaveTextContent(tEn('spendingCap') as string); - expect(spendingCapGroup).toHaveTextContent('1'); + expect(spendingCapGroup).toHaveTextContent('3'); const spendingCapGroupTooltip = await screen.findByTestId( 'confirmation__approve-spending-cap-group-tooltip', diff --git a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx index ebe680983a6c..cd4f22e4b7a5 100644 --- a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx +++ b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx @@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import nock from 'nock'; import { TokenStandard } from '../../../../shared/constants/transaction'; +import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails'; import * as backgroundConnection from '../../../../ui/store/background-connection'; import { tEn } from '../../../lib/i18n-helpers'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({ callBackgroundMethod: jest.fn(), })); +jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({ + ...jest.requireActual( + '../../../../ui/pages/confirmations/hooks/useAssetDetails', + ), + useAssetDetails: jest.fn().mockResolvedValue({ + decimals: '4', + }), +})); + const mockedBackgroundConnection = jest.mocked(backgroundConnection); +const mockedAssetDetails = jest.mocked(useAssetDetails); const backgroundConnectionMocked = { onNotification: jest.fn(), @@ -38,7 +49,6 @@ const getMetaMaskStateWithUnapprovedSetApprovalForAllTransaction = (opts?: { ...mockMetaMaskState, preferences: { ...mockMetaMaskState.preferences, - redesignedConfirmationsEnabled: true, showConfirmationAdvancedDetails: opts?.showAdvanceDetails ?? false, }, pendingApprovals: { @@ -144,6 +154,10 @@ describe('ERC721 setApprovalForAll Confirmation', () => { INCREASE_SET_APPROVAL_FOR_ALL_HEX_SIG, INCREASE_SET_APPROVAL_FOR_ALL_TEXT_SIG, ); + mockedAssetDetails.mockImplementation(() => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + decimals: '4' as any, + })); }); afterEach(() => { @@ -231,7 +245,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { expect(approveDetailsSpender).toHaveTextContent( tEn('permissionFor') as string, ); - expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); + expect(approveDetailsSpender).toHaveTextContent('0x9bc5b...AfEF4'); const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); diff --git a/test/integration/confirmations/transactions/transactionDataHelpers.tsx b/test/integration/confirmations/transactions/transactionDataHelpers.tsx index e9bcd7b818f2..58c8d40bffea 100644 --- a/test/integration/confirmations/transactions/transactionDataHelpers.tsx +++ b/test/integration/confirmations/transactions/transactionDataHelpers.tsx @@ -25,6 +25,7 @@ export const getUnapprovedContractInteractionTransaction = ( maxFeePerGas: '0x5b06b0c0d', maxPriorityFeePerGas: '0x59682f00', }, + gasLimitNoBuffer: '0x16a92', userEditedGasLimit: false, verifiedOnBlockchain: false, type: TransactionType.contractInteraction, diff --git a/test/integration/data/integration-init-state.json b/test/integration/data/integration-init-state.json index ed42111c00e1..abc3676ba477 100644 --- a/test/integration/data/integration-init-state.json +++ b/test/integration/data/integration-init-state.json @@ -784,8 +784,15 @@ "smartTransactionsOptInStatus": true, "petnamesEnabled": false, "showConfirmationAdvancedDetails": false, + "featureNotificationsEnabled": false, "showMultiRpcModal": false, - "tokenNetworkFilter": {} + "privacyMode": false, + "tokenNetworkFilter": {}, + "tokenSortConfig": { + "key": "token-sort-key", + "order": "dsc", + "sortCallback": "stringNumeric" + } }, "preventPollingOnNetworkRestart": true, "previousAppVersion": "11.14.4", @@ -801,6 +808,7 @@ "showProductTour": false, "showTestnetMessageInDropdown": true, "signatureSecurityAlertResponses": {}, + "slides": [], "smartTransactionsState": { "fees": {}, "liveness": true, @@ -1165,7 +1173,7 @@ "smartTransactions": { "expectedDeadline": 45, "maxDeadline": 150, - "returnTxHashAsap": false + "extensionReturnTxHashAsap": false } }, "linea": { @@ -1227,6 +1235,14 @@ "switchedNetworkNeverShowMessage": false, "termsOfUseLastAgreed": 1692109813199, "theme": "os", + "tokenBalances": { + "0x03cf1158b58ccdfc04dd518f11f85c3ee7fa0189": { + "0x1": { + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": "0xbdbd", + "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e": "0x501b4176a64d6" + } + } + }, "tokenList": { "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", @@ -2048,19 +2064,9 @@ "useNftDetection": false, "useNonceField": false, "usePhishDetect": true, - "useRequestQueue": false, "useSafeChainsListValidation": true, "useTokenDetection": false, "useTransactionSimulations": true, - "usedNetworks": { - "0xaa36a7": { - "rpcUrl": "https://sepolia.infura.io/v3/dummy_key", - "chainId": "0xaa36a7", - "nickname": "Sepolia Test Network", - "ticker": "ETH", - "blockExplorerUrl": "https://sepolia.etherscan.io" - } - }, "userOperations": {}, "versionFileETag": "W/\"598946a37f16c5b882a0bebbadf4509f\"", "versionInfo": [], diff --git a/test/integration/data/onboarding-completion-route.json b/test/integration/data/onboarding-completion-route.json index b2c19536a138..159de09122be 100644 --- a/test/integration/data/onboarding-completion-route.json +++ b/test/integration/data/onboarding-completion-route.json @@ -59,7 +59,9 @@ "approvalFlows": [], "bridgeState": { "bridgeFeatureFlags": { - "extensionSupport": false + "extensionConfig": { + "support": false + } } }, "browserEnvironment": { "os": "mac", "browser": "firefox" }, @@ -220,15 +222,22 @@ "pinnedAccountList": [], "popupGasPollTokens": [], "preferences": { + "hideZeroBalanceTokens": false, "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, "smartTransactionsOptInStatus": true, - "hideZeroBalanceTokens": false, "petnamesEnabled": true, - "redesignedConfirmationsEnabled": true, + "showConfirmationAdvancedDetails": false, "featureNotificationsEnabled": false, - "privacyMode": false + "showMultiRpcModal": false, + "privacyMode": false, + "tokenNetworkFilter": {}, + "tokenSortConfig": { + "key": "token-sort-key", + "order": "dsc", + "sortCallback": "stringNumeric" + } }, "preventPollingOnNetworkRestart": false, "previousAppVersion": "", @@ -285,6 +294,7 @@ "showPermissionsTour": true, "showTestnetMessageInDropdown": true, "signatureSecurityAlertResponses": {}, + "slides": [], "smartTransactionsState": { "smartTransactions": { "0x1": [] }, "fees": {}, @@ -433,6 +443,14 @@ "switchedNetworkNeverShowMessage": false, "termsOfUseLastAgreed": 1715944006161, "theme": "os", + "tokenBalances": { + "0x03cf1158b58ccdfc04dd518f11f85c3ee7fa0189": { + "0x1": { + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": "0xbdbd", + "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e": "0x501b4176a64d6" + } + } + }, "tokenList": {}, "tokens": [], "tokensChainsCache": {}, @@ -459,11 +477,9 @@ "useNftDetection": false, "useNonceField": false, "usePhishDetect": true, - "useRequestQueue": true, "useSafeChainsListValidation": true, "useTokenDetection": true, "useTransactionSimulations": true, - "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, "userOperations": {}, "versionFileETag": "", "versionInfo": [], diff --git a/test/integration/data/transaction-helpers.tsx b/test/integration/data/transaction-helpers.tsx index 7ee19922014d..4a8ffc1439c1 100644 --- a/test/integration/data/transaction-helpers.tsx +++ b/test/integration/data/transaction-helpers.tsx @@ -25,6 +25,7 @@ export const getUnapprovedTransaction = ( maxFeePerGas: '0x5b06b0c0d', maxPriorityFeePerGas: '0x59682f00', }, + gasLimitNoBuffer: '0x16a92', userEditedGasLimit: false, verifiedOnBlockchain: false, type: TransactionType.contractInteraction, diff --git a/test/integration/notifications&auth/data/notification-state.ts b/test/integration/notifications&auth/data/notification-state.ts index 61d74d161671..d91c1e7f0d2e 100644 --- a/test/integration/notifications&auth/data/notification-state.ts +++ b/test/integration/notifications&auth/data/notification-state.ts @@ -40,6 +40,7 @@ export const getMockedNotificationsState = () => { isProfileSyncingUpdateLoading: false, hasAccountSyncingSyncedAtLeastOnce: false, isAccountSyncingReadyToBeDispatched: false, + isAccountSyncingInProgress: false, isMetamaskNotificationsFeatureSeen: true, isNotificationServicesEnabled: true, isFeatureAnnouncementsEnabled: true, diff --git a/test/integration/notifications&auth/notifications-toggle.test.tsx b/test/integration/notifications&auth/notifications-toggle.test.tsx index fd4c11ec4494..9104729cb380 100644 --- a/test/integration/notifications&auth/notifications-toggle.test.tsx +++ b/test/integration/notifications&auth/notifications-toggle.test.tsx @@ -53,9 +53,7 @@ describe('Notifications Toggle', () => { }; const waitForElement = async (testId: string) => { - await waitFor(async () => { - expect(await screen.findByTestId(testId)).toBeInTheDocument(); - }); + expect(await screen.findByTestId(testId)).toBeInTheDocument(); }; it('disabling notifications from settings', async () => { diff --git a/test/integration/onboarding/wallet-created.test.tsx b/test/integration/onboarding/wallet-created.test.tsx index c1ddb1f1886a..ab87882653f5 100644 --- a/test/integration/onboarding/wallet-created.test.tsx +++ b/test/integration/onboarding/wallet-created.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, waitFor } from '@testing-library/react'; +import nock from 'nock'; import mockMetaMaskState from '../data/onboarding-completion-route.json'; import { integrationTestRender } from '../../lib/render-helpers'; import * as backgroundConnection from '../../../ui/store/background-connection'; @@ -6,6 +7,8 @@ import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../../shared/constants/metametrics'; +import { createMockImplementation } from '../helpers'; +import { BridgeBackgroundAction } from '../../../shared/types/bridge'; jest.mock('../../../ui/store/background-connection', () => ({ ...jest.requireActual('../../../ui/store/background-connection'), @@ -15,7 +18,6 @@ jest.mock('../../../ui/store/background-connection', () => ({ jest.mock('../../../ui/ducks/bridge/actions', () => ({ ...jest.requireActual('../../../ui/ducks/bridge/actions'), - setBridgeFeatureFlags: jest.fn().mockResolvedValueOnce(undefined), })); const mockedBackgroundConnection = jest.mocked(backgroundConnection); @@ -25,9 +27,38 @@ const backgroundConnectionMocked = { callBackgroundMethod: jest.fn(), }; +const setupSubmitRequestToBackgroundMocks = ( + mockRequests?: Record, +) => { + mockedBackgroundConnection.submitRequestToBackground.mockImplementation( + createMockImplementation({ + [BridgeBackgroundAction.SET_FEATURE_FLAGS]: undefined, + ...mockRequests, + }), + ); +}; + +export function mockSurveyLink() { + const mockEndpoint = nock('https://accounts.api.cx.metamask.io') + .persist() + .get( + '/v1/users/0x4d6d78a255217af6411a5bbd39e31b5e46e0e920bdf7e979470f316cbe8c00eb/surveys', + ) + .reply(200, { + surveys: {}, + }); + return mockEndpoint; +} + describe('Wallet Created Events', () => { beforeEach(() => { jest.resetAllMocks(); + mockSurveyLink(); + setupSubmitRequestToBackgroundMocks(); + }); + + afterEach(() => { + nock.cleanAll(); }); it('are sent when onboarding user who chooses to opt in metrics', async () => { diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index a3543e485bb7..f22f088e64ec 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -4,7 +4,9 @@ import { KeyringType } from '../../shared/constants/keyring'; import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods'; import { mockNetworkState } from '../stub/networks'; import { DEFAULT_BRIDGE_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge/constants'; +import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge-status/constants'; import { BRIDGE_PREFERRED_GAS_ESTIMATE } from '../../shared/constants/bridge'; +import { mockTokenData } from '../data/bridge/mock-token-data'; export const createGetSmartTransactionFeesApiResponse = () => { return { @@ -393,7 +395,7 @@ export const createSwapsMockStore = () => { smartTransactions: { expectedDeadline: 45, maxDeadline: 150, - returnTxHashAsap: false, + extensionReturnTxHashAsap: false, }, }, smartTransactions: { @@ -702,17 +704,31 @@ export const createSwapsMockStore = () => { }; export const createBridgeMockStore = ( - featureFlagOverrides = {}, - bridgeSliceOverrides = {}, - bridgeStateOverrides = {}, - metamaskStateOverrides = {}, + { + featureFlagOverrides = {}, + bridgeSliceOverrides = {}, + bridgeStateOverrides = {}, + bridgeStatusStateOverrides = {}, + metamaskStateOverrides = {}, + } = { + featureFlagOverrides: {}, + bridgeSliceOverrides: {}, + bridgeStateOverrides: {}, + bridgeStatusStateOverrides: {}, + metamaskStateOverrides: {}, + }, ) => { const swapsStore = createSwapsMockStore(); return { ...swapsStore, + // For initial state of dest asset picker + swaps: { + ...swapsStore.swaps, + topAssets: [], + }, bridge: { toChainId: null, - sortOrder: 0, + sortOrder: 'cost_ascending', ...bridgeSliceOverrides, }, metamask: { @@ -730,20 +746,37 @@ export const createBridgeMockStore = ( }, currencyRates: { ETH: { conversionRate: 2524.25 }, + usd: { conversionRate: 1 }, + }, + marketData: { + '0x1': { + '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': { + currency: 'usd', + price: 2.3, + }, + }, }, + ...mockTokenData, ...metamaskStateOverrides, bridgeState: { - ...(swapsStore.metamask.bridgeState ?? {}), + ...DEFAULT_BRIDGE_CONTROLLER_STATE, bridgeFeatureFlags: { - extensionSupport: false, - srcNetworkAllowlist: [], - destNetworkAllowlist: [], ...featureFlagOverrides, + extensionConfig: { + support: false, + chains: {}, + ...featureFlagOverrides.extensionConfig, + }, }, - quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, - quoteRequest: DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest, ...bridgeStateOverrides, }, + bridgeStatusState: { + ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, + ...bridgeStatusStateOverrides, + }, + }, + send: { + swapsBlockedTokens: [], }, }; }; diff --git a/test/jest/mocks.ts b/test/jest/mocks.ts index 8822b96315b6..5bbb9caf4727 100644 --- a/test/jest/mocks.ts +++ b/test/jest/mocks.ts @@ -3,9 +3,14 @@ import { EthMethod, BtcMethod, BtcAccountType, - InternalAccount, isEvmAccountType, + EthScopes, + BtcScopes, + SolAccountType, + SolScopes, + SolMethod, } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { KeyringTypes } from '@metamask/keyring-controller'; import { v4 as uuidv4 } from 'uuid'; import { keyringTypeToName } from '@metamask/accounts-controller'; @@ -17,6 +22,7 @@ import { } from '../../ui/ducks/send'; import { MetaMaskReduxState } from '../../ui/store/store'; import mockState from '../data/mock-state.json'; +import { isBtcMainnetAddress } from '../../shared/lib/multichain'; export type MockState = typeof mockState; @@ -186,7 +192,11 @@ export function createMockInternalAccount({ type = EthAccountType.Eoa, keyringType = KeyringTypes.hd, lastSelected = 0, - snapOptions = undefined, + snapOptions = { + enabled: true, + id: 'npm:snap-id', + name: 'snap-name', + }, options = undefined, }: { name?: string; @@ -201,10 +211,12 @@ export function createMockInternalAccount({ }; options?: Record; } = {}) { + let scopes; let methods; switch (type) { case EthAccountType.Eoa: + scopes = [EthScopes.Namespace]; methods = [ EthMethod.PersonalSign, EthMethod.SignTransaction, @@ -214,15 +226,27 @@ export function createMockInternalAccount({ ]; break; case EthAccountType.Erc4337: + // NOTE: This is not really valid here, cause a SC account might not be deployed on + // every EVM chains, but for testing purposes we enable everything. + scopes = [EthScopes.Namespace]; methods = [ EthMethod.PatchUserOperation, EthMethod.PrepareUserOperation, EthMethod.SignUserOperation, ]; break; - case BtcAccountType.P2wpkh: + case BtcAccountType.P2wpkh: { + // If no address is given, we fallback to testnet + const isMainnet = Boolean(address) && isBtcMainnetAddress(address); + + scopes = [isMainnet ? BtcScopes.Mainnet : BtcScopes.Testnet]; methods = [BtcMethod.SendBitcoin]; break; + } + case SolAccountType.DataAccount: + scopes = [SolScopes.Mainnet, SolScopes.Testnet, SolScopes.Devnet]; + methods = [SolMethod.SendAndConfirmTransaction]; + break; default: throw new Error(`Unknown account type: ${type}`); } @@ -236,10 +260,11 @@ export function createMockInternalAccount({ keyring: { type: keyringType, }, - snap: snapOptions, + snap: keyringType === KeyringTypes.snap ? snapOptions : undefined, lastSelected, }, options: options ?? {}, + scopes, methods, type, }; diff --git a/test/lib/render-helpers.js b/test/lib/render-helpers.js index 574415f2f3c6..d2161fe1cf2e 100644 --- a/test/lib/render-helpers.js +++ b/test/lib/render-helpers.js @@ -69,10 +69,15 @@ const createProviderWrapper = (store, pathname = '/') => { }; }; -export function renderWithProvider(component, store, pathname = '/') { +export function renderWithProvider( + component, + store, + pathname = '/', + renderer = render, +) { const { history, Wrapper } = createProviderWrapper(store, pathname); return { - ...render(component, { wrapper: Wrapper }), + ...renderer(component, { wrapper: Wrapper }), history, }; } diff --git a/test/manual-scenarios/upgrade-testing/upgrade-testing.md b/test/manual-scenarios/upgrade-testing/upgrade-testing.md index df5ca3d1fc50..13880f13694b 100644 --- a/test/manual-scenarios/upgrade-testing/upgrade-testing.md +++ b/test/manual-scenarios/upgrade-testing/upgrade-testing.md @@ -8,7 +8,7 @@ To ensure MetaMask extension's upgrade process is seamless and retains user data ### Pre-Upgrade Actions on Master Branch -- **Given** the user checks out the master branch, runs `yarn` and `yarn start` to build locally, and has loaded the MetaMask extension. For instructions on how to load extension on Chrome and Firefox, check the guidelines [here for Chrome](https://github.com/MetaMask/metamask-extension/blob/develop/docs/add-to-chrome.md) and [here for Firefox](https://github.com/MetaMask/metamask-extension/blob/develop/docs/add-to-firefox.md). +- **Given** the user checks out the master branch, runs `yarn` and `yarn start` to build locally, and has loaded the MetaMask extension. For instructions on how to load extension on Chrome and Firefox, check the guidelines [here for Chrome](https://github.com/MetaMask/metamask-extension/blob/main/docs/add-to-chrome.md) and [here for Firefox](https://github.com/MetaMask/metamask-extension/blob/main/docs/add-to-firefox.md). - **And** the user has successfully onboarded. - **And** the user creates two accounts. - **And** the user sends a transaction between these accounts. diff --git a/test/stub/provider.js b/test/stub/provider.js index ff5e09944be5..c9ced3099daf 100644 --- a/test/stub/provider.js +++ b/test/stub/provider.js @@ -53,6 +53,12 @@ export function createTestProviderTools(opts = {}) { network_id: opts.networkId ?? 1, chain: { chainId: opts.chainId ?? CHAIN_IDS.MAINNET }, hardfork: 'muirGlacier', + logging: { + logger: { + // eslint-disable-next-line no-empty-function + log: () => {}, // don't do anything + }, + }, }), ), ); diff --git a/types/eth-query.d.ts b/types/eth-query.d.ts deleted file mode 100644 index 726300f68176..000000000000 --- a/types/eth-query.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -declare module 'eth-query' { - // What it says on the tin. We omit `null` because confusingly, this is used - // for a successful response to indicate a lack of an error. - type EverythingButNull = - | string - | number - | boolean - | object - | symbol - | undefined; - - type ProviderSendAsyncResponse = { - error?: { message: string }; - result?: Result; - }; - - type ProviderSendAsyncCallback = ( - error: unknown, - response: ProviderSendAsyncResponse, - ) => void; - - type Provider = { - sendAsync( - payload: SendAsyncPayload, - callback: ProviderSendAsyncCallback, - ): void; - }; - - type SendAsyncPayload = { - id: number; - jsonrpc: '2.0'; - method: string; - params: Params; - }; - - type SendAsyncCallback = ( - ...args: - | [error: EverythingButNull, result: undefined] - | [error: null, result: Result] - ) => void; - - export default class EthQuery { - constructor(provider: Provider); - - sendAsync( - opts: Partial>, - callback: SendAsyncCallback, - ): void; - } -} diff --git a/types/global.d.ts b/types/global.d.ts index 8078a3998bde..8dbd3a02e314 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -21,6 +21,7 @@ import { OffscreenCommunicationTarget, TrezorAction, } from 'shared/constants/offscreen-communication'; +import type { Provider } from '@metamask/network-controller'; import type { Preferences } from '../app/scripts/controllers/preferences-controller'; declare class Platform { @@ -233,11 +234,6 @@ type SentryObject = Sentry & { getMetaMetricsEnabled: () => Promise; }; -type HttpProvider = { - host: string; - timeout: number; -}; - type StateHooks = { getCustomTraces?: () => { [name: string]: number }; getCleanAppState?: () => Promise; @@ -263,7 +259,7 @@ export declare global { var chrome: Chrome; - var ethereumProvider: HttpProvider; + var ethereumProvider: Provider; var stateHooks: StateHooks; diff --git a/ui/components/app/alert-system/alert-modal/alert-modal.tsx b/ui/components/app/alert-system/alert-modal/alert-modal.tsx index 10f5d90c3e77..2eb78fba44c1 100644 --- a/ui/components/app/alert-system/alert-modal/alert-modal.tsx +++ b/ui/components/app/alert-system/alert-modal/alert-modal.tsx @@ -164,12 +164,15 @@ function AlertDetails({ > {customDetails ?? ( - - {selectedAlert.message} - + {Boolean(selectedAlert.content) && selectedAlert.content} + {Boolean(selectedAlert.message) && ( + + {selectedAlert.message} + + )} {selectedAlert.alertDetails?.length ? ( {t('alertModalDetails')} diff --git a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx index f84c8113ae1e..c0089b6d31ed 100644 --- a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx +++ b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx @@ -87,7 +87,7 @@ function ConfirmDetails({ <> - {t('confirmationAlertModalDetails')} + {t('confirmationAlertDetails')} void; provider?: SecurityProvider; reportUrl?: string; severity: AlertSeverity; title?: string; + children?: React.ReactNode; }; function ReportLink({ @@ -119,6 +120,7 @@ function GeneralAlert({ description={description} {...props} > + {props.children} { subjects: { 'https://test.dapp': { permissions: { - eth_accounts: { + 'endowment:caip25': { caveats: [ { - type: 'restrictReturnedAccounts', - value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'], + type: 'authorizedScopes', + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: [ + 'eip155:1:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ], + }, + }, + isMultichainOrigin: false, + }, }, ], invoker: 'https://test.dapp', - parentCapability: 'eth_accounts', + parentCapability: 'endowment:caip25', }, }, }, diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 9eefd48028ac..a6ed9ec7420b 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -7,8 +7,6 @@ @import 'alert-system/alert-modal/index'; @import 'alert-system/inline-alert/index'; @import 'import-token/index'; -@import 'assets/nfts/nfts-items/index'; -@import 'assets/nfts/nfts-tab/index'; @import 'assets/nfts/nft-details/index'; @import 'assets/nfts/nft-default-image/index'; @import 'assets/nfts/nft-options/index'; @@ -57,6 +55,7 @@ @import 'assets/asset-list/asset-list-control-bar/index'; @import 'assets/asset-list/sort-control/index'; @import 'assets/token-cell/token-cell'; +@import 'assets/nfts/nft-grid/index'; @import 'transaction-activity-log/index'; @import 'transaction-breakdown/index'; @import 'transaction-icon/transaction-icon'; diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.test.tsx b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.test.tsx new file mode 100644 index 000000000000..7c6ba579943c --- /dev/null +++ b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import thunk from 'redux-thunk'; +import configureMockStore from 'redux-mock-store'; +import { renderWithProvider } from '../../../../../../test/jest'; +import { MetaMetricsContext } from '../../../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../../../shared/constants/metametrics'; +import AssetListControlBar from './asset-list-control-bar'; + +describe('AssetListControlBar', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should fire metrics event when refresh button is clicked', async () => { + const store = configureMockStore([thunk])({ + metamask: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + chainId: '0x1', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + }, + internalAccounts: { + selectedAccount: 'selectedAccount', + accounts: { + selectedAccount: {}, + }, + }, + }, + }); + + const mockTrackEvent = jest.fn(); + + const { findByTestId } = renderWithProvider( + + + , + store, + ); + + const importButton = await findByTestId('import-token-button'); + importButton.click(); + + const refreshListItem = await findByTestId('refreshList__button'); + refreshListItem.click(); + + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + expect(mockTrackEvent).toHaveBeenCalledWith({ + category: MetaMetricsEventCategory.Tokens, + event: MetaMetricsEventName.TokenListRefreshed, + }); + }); +}); diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx index 8e6abb940d1c..d335f6f41820 100644 --- a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx +++ b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx @@ -1,6 +1,11 @@ import React, { useEffect, useRef, useState, useContext, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { getCurrentNetwork, getPreferences } from '../../../../../selectors'; +import { + getCurrentNetwork, + getIsTokenNetworkFilterEqualCurrentNetwork, + getSelectedInternalAccount, + getTokenNetworkFilter, +} from '../../../../../selectors'; import { getNetworkConfigurationsByChainId } from '../../../../../../shared/modules/selectors/networks'; import { Box, @@ -23,7 +28,10 @@ import { import ImportControl from '../import-control'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { MetaMetricsContext } from '../../../../../contexts/metametrics'; -import { TEST_CHAINS } from '../../../../../../shared/constants/network'; +import { + FEATURED_NETWORK_CHAIN_IDS, + TEST_CHAINS, +} from '../../../../../../shared/constants/network'; import { MetaMetricsEventCategory, MetaMetricsEventName, @@ -42,6 +50,8 @@ import { showImportTokensModal, } from '../../../../../store/actions'; import Tooltip from '../../../../ui/tooltip'; +import { useMultichainSelector } from '../../../../../hooks/useMultichainSelector'; +import { getMultichainNetwork } from '../../../../../selectors/multichain'; type AssetListControlBarProps = { showTokensLinks?: boolean; @@ -54,14 +64,20 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { const popoverRef = useRef(null); const currentNetwork = useSelector(getCurrentNetwork); const allNetworks = useSelector(getNetworkConfigurationsByChainId); + const isTokenNetworkFilterEqualCurrentNetwork = useSelector( + getIsTokenNetworkFilterEqualCurrentNetwork, + ); - const { tokenNetworkFilter } = useSelector(getPreferences); + const tokenNetworkFilter = useSelector(getTokenNetworkFilter); const [isTokenSortPopoverOpen, setIsTokenSortPopoverOpen] = useState(false); const [isImportTokensPopoverOpen, setIsImportTokensPopoverOpen] = useState(false); const [isNetworkFilterPopoverOpen, setIsNetworkFilterPopoverOpen] = useState(false); + const account = useSelector(getSelectedInternalAccount); + const { isEvmNetwork } = useMultichainSelector(getMultichainNetwork, account); + const isTestNetwork = useMemo(() => { return (TEST_CHAINS as string[]).includes(currentNetwork.chainId); }, [currentNetwork.chainId, TEST_CHAINS]); @@ -71,10 +87,6 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { allOpts[chainId] = true; }); - const allNetworksFilterShown = - Object.keys(tokenNetworkFilter || {}).length !== - Object.keys(allOpts || {}).length; - useEffect(() => { if (isTestNetwork) { const testnetFilter = { [currentNetwork.chainId]: true }; @@ -88,7 +100,7 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { useEffect(() => { if ( process.env.PORTFOLIO_VIEW && - Object.keys(tokenNetworkFilter || {}).length === 0 + Object.keys(tokenNetworkFilter).length === 0 ) { dispatch(setTokenNetworkFilter(allOpts)); } else { @@ -148,6 +160,10 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { const handleRefresh = () => { dispatch(detectTokens()); closePopover(); + trackEvent({ + category: MetaMetricsEventCategory.Tokens, + event: MetaMetricsEventName.TokenListRefreshed, + }); }; return ( @@ -160,19 +176,23 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { - {process.env.PORTFOLIO_VIEW && ( + {/* TODO: Remove isEvmNetwork check when we are ready to show the network filter in all networks including non-EVM */} + {process.env.PORTFOLIO_VIEW && isEvmNetwork ? ( { marginRight={isFullScreen ? 2 : null} ellipsis > - {allNetworksFilterShown + {isTokenNetworkFilterEqualCurrentNetwork ? currentNetwork?.nickname ?? t('currentNetwork') - : t('allNetworks')} + : t('popularNetworks')} - )} + ) : null} { })), tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), tokenBalancesStopPollingByPollingToken: jest.fn(), + addImportedTokens: jest.fn(), + }; +}); + +// Mock the dispatch function +const mockDispatch = jest.fn(); + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: () => mockDispatch, }; }); diff --git a/ui/components/app/assets/asset-list/asset-list.tsx b/ui/components/app/assets/asset-list/asset-list.tsx index 6a3036f88764..5ac7a84e0b29 100644 --- a/ui/components/app/assets/asset-list/asset-list.tsx +++ b/ui/components/app/assets/asset-list/asset-list.tsx @@ -1,16 +1,18 @@ -import React, { useContext, useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useContext, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Token } from '@metamask/assets-controllers'; +import { NetworkConfiguration } from '@metamask/network-controller'; import TokenList from '../token-list'; import { PRIMARY } from '../../../../helpers/constants/common'; import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency'; import { getAllDetectedTokensForSelectedAddress, getDetectedTokensInCurrentNetwork, - getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, - getPreferences, + getIsTokenNetworkFilterEqualCurrentNetwork, getSelectedAccount, + getSelectedAddress, + getUseTokenDetection, } from '../../../../selectors'; -import { getNetworkConfigurationsByChainId } from '../../../../../shared/modules/selectors/networks'; import { getMultichainIsEvm, getMultichainSelectedAccountCachedBalance, @@ -24,9 +26,10 @@ import { MetaMetricsContext } from '../../../../contexts/metametrics'; import { MetaMetricsEventCategory, MetaMetricsEventName, + MetaMetricsTokenEventSource, } from '../../../../../shared/constants/metametrics'; import DetectedToken from '../../detected-token/detected-token'; -import { DetectedTokensBanner, ReceiveModal } from '../../../multichain'; +import { ReceiveModal } from '../../../multichain'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { FundingMethodModal } from '../../../multichain/funding-method-modal/funding-method-modal'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) @@ -36,6 +39,16 @@ import { } from '../../../multichain/ramps-card/ramps-card'; import { getIsNativeTokenBuyable } from '../../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF +import { + getCurrentChainId, + getNetworkConfigurationsByChainId, + getSelectedNetworkClientId, +} from '../../../../../shared/modules/selectors/networks'; +import { addImportedTokens } from '../../../../store/actions'; +import { + AssetType, + TokenStandard, +} from '../../../../../shared/constants/transaction'; import AssetListControlBar from './asset-list-control-bar'; import NativeToken from './native-token'; @@ -55,6 +68,7 @@ export type AssetListProps = { }; const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { + const dispatch = useDispatch(); const [showDetectedTokens, setShowDetectedTokens] = useState(false); const selectedAccount = useSelector(getSelectedAccount); const t = useI18nContext(); @@ -75,20 +89,18 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { }); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork) || []; - const isTokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( - getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, - ); - const allNetworks = useSelector(getNetworkConfigurationsByChainId); - const { tokenNetworkFilter } = useSelector(getPreferences); - const allOpts: Record = {}; - Object.keys(allNetworks || {}).forEach((chainId) => { - allOpts[chainId] = true; - }); + const isTokenNetworkFilterEqualCurrentNetwork = useSelector( + getIsTokenNetworkFilterEqualCurrentNetwork, + ); - const allNetworksFilterShown = - Object.keys(tokenNetworkFilter || {}).length !== - Object.keys(allOpts || {}).length; + const allNetworks: Record<`0x${string}`, NetworkConfiguration> = useSelector( + getNetworkConfigurationsByChainId, + ); + const networkClientId = useSelector(getSelectedNetworkClientId); + const selectedAddress = useSelector(getSelectedAddress); + const useTokenDetection = useSelector(getUseTokenDetection); + const currentChainId = useSelector(getCurrentChainId); const [showFundingMethodModal, setShowFundingMethodModal] = useState(false); const [showReceiveModal, setShowReceiveModal] = useState(false); @@ -112,30 +124,88 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { // for EVM assets const shouldShowTokensLinks = showTokensLinks ?? isEvm; - const detectedTokensMultichain = useSelector( - getAllDetectedTokensForSelectedAddress, - ); + const detectedTokensMultichain: { + [key: `0x${string}`]: Token[]; + } = useSelector(getAllDetectedTokensForSelectedAddress); - const totalTokens = - process.env.PORTFOLIO_VIEW && !allNetworksFilterShown - ? (Object.values(detectedTokensMultichain).reduce( - // @ts-expect-error TS18046: 'tokenArray' is of type 'unknown' - (count, tokenArray) => count + tokenArray.length, - 0, - ) as number) - : detectedTokens.length; + const multichainDetectedTokensLength = Object.values( + detectedTokensMultichain || {}, + ).reduce((acc, tokens) => acc + tokens.length, 0); + + // Add detected tokens to sate + useEffect(() => { + const importAllDetectedTokens = async () => { + // If autodetect tokens toggle is OFF, return + if (!useTokenDetection) { + return; + } + // TODO add event for MetaMetricsEventName.TokenAdded + + if ( + process.env.PORTFOLIO_VIEW && + !isTokenNetworkFilterEqualCurrentNetwork + ) { + const importPromises = Object.entries(detectedTokensMultichain).map( + async ([networkId, tokens]) => { + const chainConfig = allNetworks[networkId as `0x${string}`]; + const { defaultRpcEndpointIndex } = chainConfig; + const { networkClientId: networkInstanceId } = + chainConfig.rpcEndpoints[defaultRpcEndpointIndex]; + + await dispatch( + addImportedTokens(tokens as Token[], networkInstanceId), + ); + tokens.forEach((importedToken) => { + trackEvent({ + event: MetaMetricsEventName.TokenAdded, + category: MetaMetricsEventCategory.Wallet, + sensitiveProperties: { + token_symbol: importedToken.symbol, + token_contract_address: importedToken.address, + token_decimal_precision: importedToken.decimals, + source: MetaMetricsTokenEventSource.Detected, + token_standard: TokenStandard.ERC20, + asset_type: AssetType.token, + token_added_type: 'detected', + chain_id: chainConfig.chainId, + }, + }); + }); + }, + ); + + await Promise.all(importPromises); + } else if (detectedTokens.length > 0) { + await dispatch(addImportedTokens(detectedTokens, networkClientId)); + detectedTokens.forEach((importedToken: Token) => { + trackEvent({ + event: MetaMetricsEventName.TokenAdded, + category: MetaMetricsEventCategory.Wallet, + sensitiveProperties: { + token_symbol: importedToken.symbol, + token_contract_address: importedToken.address, + token_decimal_precision: importedToken.decimals, + source: MetaMetricsTokenEventSource.Detected, + token_standard: TokenStandard.ERC20, + asset_type: AssetType.token, + token_added_type: 'detected', + chain_id: currentChainId, + }, + }); + }); + } + }; + importAllDetectedTokens(); + }, [ + isTokenNetworkFilterEqualCurrentNetwork, + selectedAddress, + networkClientId, + detectedTokens.length, + multichainDetectedTokensLength, + ]); return ( <> - {totalTokens && - totalTokens > 0 && - !isTokenDetectionInactiveOnNonMainnetSupportedNetwork ? ( - setShowDetectedTokens(true)} - margin={4} - /> - ) : null} { const showFiat = useSelector(getMultichainShouldShowFiat); + const account = useSelector(getSelectedInternalAccount); const primaryTokenImage = useSelector(getMultichainCurrencyImage); const { showNativeTokenAsMainBalance } = useSelector(getPreferences); const { chainId, ticker, type, rpcUrl } = useSelector( @@ -46,12 +51,14 @@ export const useNativeTokenBalance = () => { const [primaryCurrencyDisplay, primaryCurrencyProperties] = useCurrencyDisplay(balance, { + account, numberOfDecimals: primaryNumberOfDecimals, currency: primaryCurrency, }); const [secondaryCurrencyDisplay, secondaryCurrencyProperties] = useCurrencyDisplay(balance, { + account, numberOfDecimals: secondaryNumberOfDecimals, currency: secondaryCurrency, }); diff --git a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx index 9b5cb3797e7c..a63aef334d11 100644 --- a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx +++ b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx @@ -1,15 +1,18 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { setTokenNetworkFilter } from '../../../../../store/actions'; import { - getCurrentChainId, getCurrentNetwork, - getPreferences, - getChainIdsToPoll, getShouldHideZeroBalanceTokens, getSelectedAccount, + getAllChainsToPoll, + getTokenNetworkFilter, + getIsTokenNetworkFilterEqualCurrentNetwork, } from '../../../../../selectors'; -import { getNetworkConfigurationsByChainId } from '../../../../../../shared/modules/selectors/networks'; +import { + getCurrentChainId, + getNetworkConfigurationsByChainId, +} from '../../../../../../shared/modules/selectors/networks'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { SelectableListItem } from '../sort-control/sort-control'; import { Text } from '../../../../component-library/text/text'; @@ -29,10 +32,11 @@ import { import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'; import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, - TEST_CHAINS, + FEATURED_NETWORK_CHAIN_IDS, } from '../../../../../../shared/constants/network'; import { useGetFormattedTokensPerChain } from '../../../../../hooks/useGetFormattedTokensPerChain'; import { useAccountTotalCrossChainFiatBalance } from '../../../../../hooks/useAccountTotalCrossChainFiatBalance'; +import InfoTooltip from '../../../../ui/info-tooltip'; type SortControlProps = { handleClose: () => void; @@ -45,12 +49,15 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { const currentNetwork = useSelector(getCurrentNetwork); const selectedAccount = useSelector(getSelectedAccount); const allNetworks = useSelector(getNetworkConfigurationsByChainId); - const [chainsToShow, setChainsToShow] = useState([]); - const { tokenNetworkFilter } = useSelector(getPreferences); + const tokenNetworkFilter = useSelector(getTokenNetworkFilter); + const isTokenNetworkFilterEqualCurrentNetwork = useSelector( + getIsTokenNetworkFilterEqualCurrentNetwork, + ); + const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); - const allChainIDs = useSelector(getChainIdsToPoll); + const allChainIDs = useSelector(getAllChainsToPoll); const { formattedTokensWithBalancesPerChain } = useGetFormattedTokensPerChain( selectedAccount, shouldHideZeroBalanceTokens, @@ -83,26 +90,21 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { handleClose(); }; - useEffect(() => { - const testnetChains: string[] = TEST_CHAINS; - const mainnetChainIds = Object.keys(allNetworks || {}).filter( - (chain) => !testnetChains.includes(chain), - ); - setChainsToShow(mainnetChainIds); - }, []); - const allOpts: Record = {}; Object.keys(allNetworks || {}).forEach((chain) => { allOpts[chain] = true; }); + const allAddedPopularNetworks = FEATURED_NETWORK_CHAIN_IDS.filter( + (chain) => allOpts[chain], + ).map((chain) => { + return allNetworks[chain].name; + }); + return ( <> handleFilter(allOpts)} testId="network-filter-all" > @@ -117,7 +119,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { variant={TextVariant.bodyMdMedium} color={TextColor.textDefault} > - {t('allNetworks')} + {t('popularNetworks')} { - {chainsToShow - .slice(0, 5) // only show a max of 5 icons overlapping - .map((chain, index) => { + + {FEATURED_NETWORK_CHAIN_IDS.filter((chain) => allOpts[chain]).map( + (chain, index) => { const networkImageUrl = CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ chain as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP ]; return ( { }} /> ); - })} + }, + )} handleFilter({ [chainId]: true })} testId="network-filter-current" @@ -197,7 +203,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { diff --git a/ui/components/app/assets/asset-list/sort-control/sort-control.test.tsx b/ui/components/app/assets/asset-list/sort-control/sort-control.test.tsx index 4aac598bd838..a74adca65ce9 100644 --- a/ui/components/app/assets/asset-list/sort-control/sort-control.test.tsx +++ b/ui/components/app/assets/asset-list/sort-control/sort-control.test.tsx @@ -4,7 +4,8 @@ import { useSelector } from 'react-redux'; import { setTokenSortConfig } from '../../../../../store/actions'; import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; import { MetaMetricsContext } from '../../../../../contexts/metametrics'; -import { getCurrentCurrency, getPreferences } from '../../../../../selectors'; +import { getPreferences } from '../../../../../selectors'; +import { getCurrentCurrency } from '../../../../../ducks/metamask/metamask'; import SortControl from './sort-control'; // Mock the sortAssets utility diff --git a/ui/components/app/assets/asset-list/sort-control/sort-control.tsx b/ui/components/app/assets/asset-list/sort-control/sort-control.tsx index cf7285c9115b..ddded6c1c027 100644 --- a/ui/components/app/assets/asset-list/sort-control/sort-control.tsx +++ b/ui/components/app/assets/asset-list/sort-control/sort-control.tsx @@ -18,7 +18,8 @@ import { MetaMetricsEventName, MetaMetricsUserTrait, } from '../../../../../../shared/constants/metametrics'; -import { getCurrentCurrency, getPreferences } from '../../../../../selectors'; +import { getPreferences } from '../../../../../selectors'; +import { getCurrentCurrency } from '../../../../../ducks/metamask/metamask'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { getCurrencySymbol } from '../../../../../helpers/utils/common.util'; diff --git a/ui/components/app/assets/nfts/nft-default-image/__snapshots__/nft-default-image.test.js.snap b/ui/components/app/assets/nfts/nft-default-image/__snapshots__/nft-default-image.test.js.snap index ad5821e56969..fbe005e021cc 100644 --- a/ui/components/app/assets/nfts/nft-default-image/__snapshots__/nft-default-image.test.js.snap +++ b/ui/components/app/assets/nfts/nft-default-image/__snapshots__/nft-default-image.test.js.snap @@ -3,7 +3,7 @@ exports[`NFT Default Image should match snapshot with all provided props 1`] = `
    @@ -13,7 +13,7 @@ exports[`NFT Default Image should match snapshot with all provided props 1`] = ` exports[`NFT Default Image should match snapshot with missing clickable prop 1`] = `
    @@ -23,7 +23,7 @@ exports[`NFT Default Image should match snapshot with missing clickable prop 1`] exports[`NFT Default Image should render with no props 1`] = `
    diff --git a/ui/components/app/assets/nfts/nft-default-image/nft-default-image.js b/ui/components/app/assets/nfts/nft-default-image/nft-default-image.tsx similarity index 63% rename from ui/components/app/assets/nfts/nft-default-image/nft-default-image.js rename to ui/components/app/assets/nfts/nft-default-image/nft-default-image.tsx index 37c31e1bf35e..4fd6676507ec 100644 --- a/ui/components/app/assets/nfts/nft-default-image/nft-default-image.js +++ b/ui/components/app/assets/nfts/nft-default-image/nft-default-image.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useDispatch } from 'react-redux'; import { @@ -12,7 +11,15 @@ import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { ButtonLink, Box } from '../../../../component-library'; import { showIpfsModal } from '../../../../../store/actions'; -export default function NftDefaultImage({ className, clickable }) { +type NftDefaultImageProps = { + className: string; + clickable?: boolean; +}; + +export default function NftDefaultImage({ + className, + clickable, +}: NftDefaultImageProps) { const t = useI18nContext(); const dispatch = useDispatch(); @@ -21,18 +28,18 @@ export default function NftDefaultImage({ className, clickable }) { tabIndex={0} data-testid="nft-default-image" className={classnames(className, 'nft-default', { - 'nft-default--clickable': clickable, + 'nft-default--clickable': Boolean(clickable), })} display={Display.Flex} - alignItems={AlignItems.Center} - justifyContent={JustifyContent.Center} + alignItems={AlignItems.center} + justifyContent={JustifyContent.center} borderRadius={BorderRadius.LG} > {clickable && ( { + onClick={(e: { stopPropagation: () => void }) => { e.stopPropagation(); dispatch(showIpfsModal()); }} @@ -43,15 +50,3 @@ export default function NftDefaultImage({ className, clickable }) { ); } - -NftDefaultImage.propTypes = { - /** - * Controls the css class for the cursor hover - * It determines if we need to show the button on default image or not - */ - clickable: PropTypes.bool, - /** - * An additional className to apply to the NFT default image - */ - className: PropTypes.string, -}; diff --git a/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap b/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap index d6b0de0c043e..380e39f95f44 100644 --- a/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap +++ b/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap @@ -24,10 +24,12 @@ exports[`NFT Details should match minimal props and state snapshot 1`] = ` style="mask-image: url('./images/icons/arrow-left.svg');" /> -
    +
    -
    -
    + +
    - G +
    + G +
    -
    - + +

    +

    +

    - + +

    +

    +

diff --git a/ui/components/app/assets/nfts/nft-details/index.scss b/ui/components/app/assets/nfts/nft-details/index.scss index 5d8cda879a57..a10fec90d753 100644 --- a/ui/components/app/assets/nfts/nft-details/index.scss +++ b/ui/components/app/assets/nfts/nft-details/index.scss @@ -108,7 +108,7 @@ $spacer-break-small: 16px; padding-left: 16px; padding-right: 16px; border-radius: var(--Spacing-sm, 8px); - border: 1px solid var(--border-muted, #d6d9dc); + border: 1px solid var(--color-border-muted); } &__nft-attribute-frame { diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.test.js b/ui/components/app/assets/nfts/nft-details/nft-details.test.js index 350dc4813c6a..e378fb4bb2bf 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-details.test.js +++ b/ui/components/app/assets/nfts/nft-details/nft-details.test.js @@ -109,7 +109,8 @@ describe('NFT Details', () => { const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const removeNftButton = queryByTestId('nft-item-remove'); + const removeNftButton = queryByTestId('nft-item-remove')?.firstChild; + expect(removeNftButton).toBeInTheDocument(); fireEvent.click(removeNftButton); await expect(removeAndIgnoreNft).toHaveBeenCalledWith( @@ -132,7 +133,7 @@ describe('NFT Details', () => { const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const removeNftButton = queryByTestId('nft-item-remove'); + const removeNftButton = queryByTestId('nft-item-remove')?.firstChild; fireEvent.click(removeNftButton); await expect(removeAndIgnoreNft).toHaveBeenCalledWith( @@ -217,14 +218,16 @@ describe('NFT Details', () => { mockStore, ); + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const openOpenSea = queryByTestId('nft-options__view-on-opensea'); + const openOpenSea = queryByTestId('nft-options__view-on-opensea__button'); fireEvent.click(openOpenSea); await waitFor(() => { - expect(global.platform.openTab).toHaveBeenCalledWith({ + expect(openTabSpy).toHaveBeenCalledWith({ url: `https://testnets.opensea.io/assets/goerli/${nfts[5].address}/${nfts[5].tokenId}`, }); }); @@ -242,6 +245,8 @@ describe('NFT Details', () => { }; const mainnetMockStore = configureMockStore([thunk])(mainnetState); + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + const { queryByTestId } = renderWithProvider( , mainnetMockStore, @@ -250,11 +255,11 @@ describe('NFT Details', () => { const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const openOpenSea = queryByTestId('nft-options__view-on-opensea'); + const openOpenSea = queryByTestId('nft-options__view-on-opensea__button'); fireEvent.click(openOpenSea); await waitFor(() => { - expect(global.platform.openTab).toHaveBeenCalledWith({ + expect(openTabSpy).toHaveBeenCalledWith({ url: `https://opensea.io/assets/ethereum/${nfts[5].address}/${nfts[5].tokenId}`, }); }); @@ -270,6 +275,8 @@ describe('NFT Details', () => { }; const polygonMockStore = configureMockStore([thunk])(polygonState); + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + const { queryByTestId } = renderWithProvider( , polygonMockStore, @@ -278,11 +285,11 @@ describe('NFT Details', () => { const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const openOpenSea = queryByTestId('nft-options__view-on-opensea'); + const openOpenSea = queryByTestId('nft-options__view-on-opensea__button'); fireEvent.click(openOpenSea); await waitFor(() => { - expect(global.platform.openTab).toHaveBeenCalledWith({ + expect(openTabSpy).toHaveBeenCalledWith({ url: `https://opensea.io/assets/matic/${nfts[5].address}/${nfts[5].tokenId}`, }); }); @@ -298,6 +305,8 @@ describe('NFT Details', () => { }; const sepoliaMockStore = configureMockStore([thunk])(sepoliaState); + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + const { queryByTestId } = renderWithProvider( , sepoliaMockStore, @@ -306,11 +315,11 @@ describe('NFT Details', () => { const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const openOpenSea = queryByTestId('nft-options__view-on-opensea'); + const openOpenSea = queryByTestId('nft-options__view-on-opensea__button'); fireEvent.click(openOpenSea); await waitFor(() => { - expect(global.platform.openTab).toHaveBeenCalledWith({ + expect(openTabSpy).toHaveBeenCalledWith({ url: `https://testnets.opensea.io/assets/sepolia/${nfts[5].address}/${nfts[5].tokenId}`, }); }); @@ -336,7 +345,7 @@ describe('NFT Details', () => { const openOptionMenuButton = queryByTestId('nft-options__button'); fireEvent.click(openOptionMenuButton); - const openOpenSea = queryByTestId('nft-options__view-on-opensea'); + const openOpenSea = queryByTestId('nft-options__view-on-opensea__button'); await waitFor(() => { expect(openOpenSea).not.toBeInTheDocument(); }); diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.tsx b/ui/components/app/assets/nfts/nft-details/nft-details.tsx index 0dc9ec05250c..8a857e5f0ce1 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-details.tsx +++ b/ui/components/app/assets/nfts/nft-details/nft-details.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useContext } from 'react'; -import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { isEqual } from 'lodash'; @@ -20,12 +19,8 @@ import { import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { shortenAddress } from '../../../../../helpers/utils/util'; import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; -import { - getCurrentChainId, - getCurrentCurrency, - getCurrentNetwork, - getIpfsGateway, -} from '../../../../../selectors'; +import { getCurrentChainId } from '../../../../../../shared/modules/selectors/networks'; +import { getCurrentNetwork, getIpfsGateway } from '../../../../../selectors'; import { ASSET_ROUTE, DEFAULT_ROUTE, @@ -67,7 +62,10 @@ import { Content, Footer, Page } from '../../../../multichain/pages/page'; import { formatCurrency } from '../../../../../helpers/utils/confirm-tx.util'; import { getShortDateFormatterV2 } from '../../../../../pages/asset/util'; import { CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../../shared/constants/common'; -import { getConversionRate } from '../../../../../ducks/metamask/metamask'; +import { + getConversionRate, + getCurrentCurrency, +} from '../../../../../ducks/metamask/metamask'; import { Numeric } from '../../../../../../shared/modules/Numeric'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths @@ -113,7 +111,10 @@ export default function NftDetails({ nft }: { nft: Nft }) { const isIpfsURL = nftSrcUrl?.startsWith('ipfs:'); const isImageHosted = image?.startsWith('https:') || image?.startsWith('http:'); - const nftImageURL = useGetAssetImageUrl(imageOriginal ?? image, ipfsGateway); + const nftImageURL = useGetAssetImageUrl( + imageOriginal ?? image ?? undefined, + ipfsGateway, + ); const hasFloorAskPrice = Boolean( collection?.floorAsk?.price?.amount?.usd && @@ -311,6 +312,8 @@ export default function NftDetails({ nft }: { nft: Nft }) { return `${text.slice(0, chars)}...${text.slice(-chars)}`; }; + const nftItemSrc = isImageHosted ? image : nftImageURL; + return ( @@ -327,11 +330,13 @@ export default function NftDetails({ nft }: { nft: Nft }) { data-testid="nft__back" /> global.platform.openTab({ url: openSeaLink }) - : null - } + showOpenSeaLink={Boolean(openSeaLink)} + onViewOnOpensea={() => { + if (!openSeaLink) { + return null; + } + return global.platform.openTab({ url: openSeaLink }); + }} onRemove={onRemove} /> @@ -343,14 +348,13 @@ export default function NftDetails({ nft }: { nft: Nft }) { > @@ -847,106 +851,3 @@ export default function NftDetails({ nft }: { nft: Nft }) { ); } - -NftDetails.propTypes = { - nft: PropTypes.shape({ - address: PropTypes.string.isRequired, - tokenId: PropTypes.string.isRequired, - isCurrentlyOwned: PropTypes.bool, - name: PropTypes.string, - description: PropTypes.string, - image: PropTypes.string, - standard: PropTypes.string, - imageThumbnail: PropTypes.string, - imagePreview: PropTypes.string, - imageOriginal: PropTypes.string, - rarityRank: PropTypes.string, - - creator: PropTypes.shape({ - address: PropTypes.string, - config: PropTypes.string, - profile_img_url: PropTypes.string, - }), - attributes: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string, - value: PropTypes.string, - }), - ), - lastSale: PropTypes.shape({ - timestamp: PropTypes.string, - orderSource: PropTypes.string, - price: PropTypes.shape({ - amount: PropTypes.shape({ - native: PropTypes.string, - decimal: PropTypes.string, - usd: PropTypes.string, - }), - currency: PropTypes.shape({ - symbol: PropTypes.string, - }), - }), - }), - topBid: PropTypes.shape({ - source: PropTypes.shape({ - id: PropTypes.string, - domain: PropTypes.string, - name: PropTypes.string, - icon: PropTypes.string, - url: PropTypes.string, - }), - price: PropTypes.shape({ - amount: PropTypes.shape({ - native: PropTypes.string, - decimal: PropTypes.string, - usd: PropTypes.string, - }), - currency: PropTypes.shape({ - symbol: PropTypes.string, - }), - }), - }), - collection: PropTypes.shape({ - openseaVerificationStatus: PropTypes.string, - tokenCount: PropTypes.string, - name: PropTypes.string, - ownerCount: PropTypes.string, - creator: PropTypes.string, - symbol: PropTypes.string, - contractDeployedAt: PropTypes.string, - floorAsk: PropTypes.shape({ - sourceDomain: PropTypes.string, - source: PropTypes.shape({ - id: PropTypes.string, - domain: PropTypes.string, - name: PropTypes.string, - icon: PropTypes.string, - url: PropTypes.string, - }), - price: PropTypes.shape({ - amount: PropTypes.shape({ - native: PropTypes.string, - decimal: PropTypes.string, - usd: PropTypes.string, - }), - currency: PropTypes.shape({ - symbol: PropTypes.string, - }), - }), - }), - topBid: PropTypes.shape({ - sourceDomain: PropTypes.string, - price: PropTypes.shape({ - amount: PropTypes.shape({ - native: PropTypes.string, - decimal: PropTypes.string, - usd: PropTypes.string, - }), - currency: PropTypes.shape({ - symbol: PropTypes.string, - }), - }), - }), - }), - }), -}; diff --git a/ui/components/app/assets/nfts/nft-grid/index.scss b/ui/components/app/assets/nfts/nft-grid/index.scss new file mode 100644 index 000000000000..9744586cab21 --- /dev/null +++ b/ui/components/app/assets/nfts/nft-grid/index.scss @@ -0,0 +1,19 @@ +@use "design-system"; + +.nft-items { + &__wrapper { + grid-template-columns: repeat(4, minmax(120px, 1fr)); + } + + @include design-system.screen-md-max { + &__wrapper { + grid-template-columns: repeat(3, minmax(100px, 1fr)); + } + } + + @include design-system.screen-sm-max { + &__wrapper { + grid-template-columns: repeat(3, minmax(85px, 1fr)); + } + } +} diff --git a/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx b/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx new file mode 100644 index 000000000000..2b5639e95274 --- /dev/null +++ b/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; +import { Display } from '../../../../../helpers/constants/design-system'; +import { Box } from '../../../../component-library'; +import Spinner from '../../../../ui/spinner'; +import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; +import { NftItem } from '../../../../multichain/nft-item'; +import { NFT } from '../../../../multichain/asset-picker-amount/asset-picker-modal/types'; +import { + getCurrentNetwork, + getNftIsStillFetchingIndication, +} from '../../../../../selectors'; + +export default function NftGrid({ + nfts, + handleNftClick, + privacyMode, +}: { + nfts: NFT[]; + handleNftClick: (nft: NFT) => void; + privacyMode?: boolean; +}) { + const currentChain = useSelector(getCurrentNetwork) as { + chainId: Hex; + nickname: string; + rpcPrefs?: { imageUrl: string }; + }; + const nftsStillFetchingIndication = useSelector( + getNftIsStillFetchingIndication, + ); + + return ( + + + {nfts.map((nft: NFT) => { + const { image, imageOriginal, tokenURI } = nft; + const nftImageAlt = getNftImageAlt(nft); + const isIpfsURL = (imageOriginal ?? image ?? tokenURI)?.startsWith( + 'ipfs:', + ); + return ( + + handleNftClick(nft)} + isIpfsURL={isIpfsURL} + privacyMode={privacyMode} + clickable + /> + + ); + })} + {nftsStillFetchingIndication ? ( + + + + ) : null} + + + ); +} diff --git a/ui/components/app/assets/nfts/nft-options/nft-options.js b/ui/components/app/assets/nfts/nft-options/nft-options.js deleted file mode 100644 index 520d3a32c116..000000000000 --- a/ui/components/app/assets/nfts/nft-options/nft-options.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useContext, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; - -import { I18nContext } from '../../../../../contexts/i18n'; -import { Menu, MenuItem } from '../../../../ui/menu'; -import { - ButtonIcon, - ButtonIconSize, - IconName, -} from '../../../../component-library'; -import { Color } from '../../../../../helpers/constants/design-system'; - -const NftOptions = ({ onRemove, onViewOnOpensea }) => { - const t = useContext(I18nContext); - const [nftOptionsOpen, setNftOptionsOpen] = useState(false); - const ref = useRef(false); - - return ( -
- setNftOptionsOpen(true)} - color={Color.textDefault} - size={ButtonIconSize.Sm} - ariaLabel={t('nftOptions')} - /> - - {nftOptionsOpen ? ( - setNftOptionsOpen(false)} - > - {onViewOnOpensea ? ( - { - setNftOptionsOpen(false); - onViewOnOpensea(); - }} - > - {t('viewOnOpensea')} - - ) : null} - { - setNftOptionsOpen(false); - onRemove(); - }} - > - {t('removeNFT')} - - - ) : null} -
- ); -}; - -NftOptions.propTypes = { - onRemove: PropTypes.func.isRequired, - onViewOnOpensea: PropTypes.func, -}; - -export default NftOptions; diff --git a/ui/components/app/assets/nfts/nft-options/nft-options.test.js b/ui/components/app/assets/nfts/nft-options/nft-options.test.js index 450206df7714..7d6652f3b9c7 100644 --- a/ui/components/app/assets/nfts/nft-options/nft-options.test.js +++ b/ui/components/app/assets/nfts/nft-options/nft-options.test.js @@ -7,6 +7,7 @@ describe('NFT Options Component', () => { const props = { onRemove: jest.fn(), onViewOnOpensea: jest.fn(), + showOpenSeaLink: true, }; it('should expand NFT options menu`', async () => { @@ -26,15 +27,14 @@ describe('NFT Options Component', () => { it('should expand and close menu options when clicked`', async () => { const { queryByTestId } = renderWithProvider(); - const openOptionMenuButton = queryByTestId('nft-options__button'); - - fireEvent.click(openOptionMenuButton); + const optionMenuToggle = queryByTestId('nft-options__button'); - const closeOptionMenuButton = queryByTestId('close-nft-options-menu'); + fireEvent.click(optionMenuToggle); + fireEvent.click(optionMenuToggle); - fireEvent.click(closeOptionMenuButton); + const nftItemRemove = queryByTestId('nft-item-remove'); - expect(closeOptionMenuButton).not.toBeInTheDocument(); + expect(nftItemRemove).not.toBeInTheDocument(); }); it('should click onRemove handler and close option menu', () => { @@ -44,8 +44,7 @@ describe('NFT Options Component', () => { fireEvent.click(openOptionMenuButton); - const removeNftButton = queryByTestId('nft-item-remove'); - + const removeNftButton = queryByTestId('nft-item-remove__button'); fireEvent.click(removeNftButton); expect(props.onRemove).toHaveBeenCalled(); @@ -60,10 +59,9 @@ describe('NFT Options Component', () => { fireEvent.click(openOptionMenuButton); - const openOpenSea = queryByTestId('nft-options__view-on-opensea'); + const openOpenSea = queryByTestId('nft-options__view-on-opensea__button'); fireEvent.click(openOpenSea); - expect(props.onViewOnOpensea).toHaveBeenCalled(); expect(removeNftButton).not.toBeInTheDocument(); }); diff --git a/ui/components/app/assets/nfts/nft-options/nft-options.tsx b/ui/components/app/assets/nfts/nft-options/nft-options.tsx new file mode 100644 index 000000000000..998ba90a7e42 --- /dev/null +++ b/ui/components/app/assets/nfts/nft-options/nft-options.tsx @@ -0,0 +1,89 @@ +import React, { useContext, useRef, useState } from 'react'; +import { I18nContext } from '../../../../../contexts/i18n'; +import { + Box, + ButtonIcon, + ButtonIconSize, + Icon, + IconName, + IconSize, + Popover, + PopoverPosition, +} from '../../../../component-library'; +import { IconColor } from '../../../../../helpers/constants/design-system'; +import { SelectableListItem } from '../../asset-list/sort-control/sort-control'; + +type NftOptionsProps = { + onRemove: () => void; + onViewOnOpensea?: () => void; + showOpenSeaLink: boolean; +}; + +const NftOptions = ({ + onRemove, + onViewOnOpensea, + showOpenSeaLink, +}: NftOptionsProps) => { + const t = useContext(I18nContext); + const [nftOptionsOpen, setNftOptionsOpen] = useState(false); + const ref = useRef(null); + + const closePopover = () => { + setNftOptionsOpen(false); + }; + + return ( + + setNftOptionsOpen(true)} + color={IconColor.iconDefault} + size={ButtonIconSize.Sm} + ariaLabel={t('nftOptions')} + /> + + {showOpenSeaLink ? ( + { + closePopover(); + onViewOnOpensea?.(); + }} + > + + {t('viewOnOpensea')} + + ) : null} + { + closePopover(); + onRemove?.(); + }} + > + + {t('removeNFT')} + + + + ); +}; + +export default NftOptions; diff --git a/ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/index.js b/ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/index.ts similarity index 100% rename from ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/index.js rename to ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/index.ts diff --git a/ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/nfts-detection-notice-import-nfts.js b/ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/nfts-detection-notice-import-nfts.tsx similarity index 83% rename from ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/nfts-detection-notice-import-nfts.js rename to ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/nfts-detection-notice-import-nfts.tsx index 90697c1535b9..1653e67b06e5 100644 --- a/ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/nfts-detection-notice-import-nfts.js +++ b/ui/components/app/assets/nfts/nfts-detection-notice-import-nfts/nfts-detection-notice-import-nfts.tsx @@ -5,7 +5,13 @@ import { BannerAlert } from '../../../../component-library'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { SECURITY_ROUTE } from '../../../../../helpers/constants/routes'; -export default function NftsDetectionNoticeImportNFTs({ onActionButtonClick }) { +type NftsDetectionNoticeImportNFTsProps = { + onActionButtonClick: () => void; +}; + +export default function NftsDetectionNoticeImportNFTs({ + onActionButtonClick, +}: NftsDetectionNoticeImportNFTsProps) { const t = useI18nContext(); const history = useHistory(); diff --git a/ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/index.js b/ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/index.ts similarity index 100% rename from ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/index.js rename to ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/index.ts diff --git a/ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.js b/ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.tsx similarity index 100% rename from ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.js rename to ui/components/app/assets/nfts/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.tsx diff --git a/ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx b/ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx deleted file mode 100644 index 726ca26508b9..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { screen } from '@testing-library/react'; -import mockState from '../../../../../../test/data/mock-state.json'; -import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; -import { getIpfsGateway, getOpenSeaEnabled } from '../../../../../selectors'; -import { CollectionImageComponent } from './collection-image.component'; - -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useSelector: jest.fn(), -})); - -jest.mock('../../../../../selectors', () => ({ - ...jest.requireActual('../../../../../selectors'), - getIpfsGateway: jest.fn(), - getOpenSeaEnabled: jest.fn(), -})); -const mockStore = configureMockStore([thunk])(mockState); -describe('CollectionImageComponent', () => { - const useSelectorMock = useSelector as jest.Mock; - beforeEach(() => { - jest.resetAllMocks(); - }); - it('should show collection first letter when ipfs is not enabled', async () => { - useSelectorMock.mockImplementation((selector) => { - if (selector === getIpfsGateway) { - return undefined; - } - return undefined; - }); - - const props = { - collectionName: 'NFT Collection', - collectionImage: 'ipfs://', - }; - - const { getByText } = renderWithProvider( - , - mockStore, - ); - - expect(getByText('N')).toBeInTheDocument(); - }); - - it('should show collection first letter when opensea is not enabled', async () => { - useSelectorMock.mockImplementation((selector) => { - if (selector === getOpenSeaEnabled) { - return false; - } - return undefined; - }); - - const props = { - collectionName: 'Test NFT Collection', - collectionImage: 'https://image.png', - }; - - const { getByText } = renderWithProvider( - , - mockStore, - ); - - expect(getByText('T')).toBeInTheDocument(); - }); - - it('should show collection image', async () => { - useSelectorMock.mockImplementation((selector) => { - if (selector === getOpenSeaEnabled) { - return true; - } - return undefined; - }); - - const props = { - collectionName: 'Test NFT Collection', - collectionImage: 'https://image.png', - }; - - renderWithProvider(, mockStore); - - expect(screen.getAllByRole('img')).toHaveLength(1); - }); -}); diff --git a/ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx b/ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx deleted file mode 100644 index 24f34714dd95..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; - -import { useSelector } from 'react-redux'; - -import { getIpfsGateway, getOpenSeaEnabled } from '../../../../../selectors'; -import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl'; -import { Box } from '../../../../component-library'; - -export const CollectionImageComponent = ({ - collectionImage, - collectionName, -}: { - collectionImage: string; - collectionName: string; -}) => { - const ipfsGateway = useSelector(getIpfsGateway); - const openSeaEnabled = useSelector(getOpenSeaEnabled); - const nftImageURL = useGetAssetImageUrl(collectionImage, ipfsGateway); - - const renderCollectionImage = () => { - if (collectionImage?.startsWith('ipfs') && !ipfsGateway) { - return ( -
- {collectionName?.[0]?.toUpperCase() ?? null} -
- ); - } - if (!openSeaEnabled && !collectionImage?.startsWith('ipfs')) { - return ( -
- {collectionName?.[0]?.toUpperCase() ?? null} -
- ); - } - - if (collectionImage) { - return ( - {collectionName} - ); - } - return ( -
- {collectionName?.[0]?.toUpperCase() ?? null} -
- ); - }; - - return {renderCollectionImage()}; -}; diff --git a/ui/components/app/assets/nfts/nfts-items/index.js b/ui/components/app/assets/nfts/nfts-items/index.js deleted file mode 100644 index e0428c09b332..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './nfts-items'; diff --git a/ui/components/app/assets/nfts/nfts-items/index.scss b/ui/components/app/assets/nfts/nfts-items/index.scss deleted file mode 100644 index f8d7839fb200..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/index.scss +++ /dev/null @@ -1,58 +0,0 @@ -.nfts-items { - &__collection { - margin-bottom: 24px; - - &:last-child { - margin-bottom: 0; - } - - &-accordion-title { - cursor: pointer; - } - - &-wrapper { - background-color: transparent; - border: 0; - width: 100%; - } - - &-image { - width: 32px; - height: 32px; - border-radius: 50%; - } - - &-image-alt { - border-radius: 50%; - width: 32px; - height: 32px; - padding: 8px; - background: var(--color-overlay-alternative); - color: var(--color-overlay-inverse); - text-align: center; - } - } - - &__item-wrapper { - align-self: center; - - &__card { - overflow: hidden; - } - } - - &__item { - width: 100%; - display: flex; - justify-content: center; - cursor: pointer; - align-self: center; - padding: 0; - - &-image { - width: 100%; - height: 100%; - cursor: pointer; - } - } -} diff --git a/ui/components/app/assets/nfts/nfts-items/nfts-items.js b/ui/components/app/assets/nfts/nfts-items/nfts-items.js deleted file mode 100644 index 9dd53cd541fc..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/nfts-items.js +++ /dev/null @@ -1,397 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { isEqual } from 'lodash'; -import Box from '../../../../ui/box'; -import Typography from '../../../../ui/typography/typography'; -import { - Color, - TypographyVariant, - JustifyContent, - FLEX_DIRECTION, - AlignItems, - DISPLAY, - BLOCK_SIZES, - FLEX_WRAP, -} from '../../../../../helpers/constants/design-system'; -import { ENVIRONMENT_TYPE_POPUP } from '../../../../../../shared/constants/app'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { getEnvironmentType } from '../../../../../../app/scripts/lib/util'; -import { - getCurrentChainId, - getIpfsGateway, - getSelectedInternalAccount, - getCurrentNetwork, -} from '../../../../../selectors'; -import { - ASSET_ROUTE, - SEND_ROUTE, -} from '../../../../../helpers/constants/routes'; -import { getAssetImageURL } from '../../../../../helpers/utils/util'; -import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; -import { updateNftDropDownState } from '../../../../../store/actions'; -import { usePrevious } from '../../../../../hooks/usePrevious'; -import { getNftsDropdownState } from '../../../../../ducks/metamask/metamask'; -import { useI18nContext } from '../../../../../hooks/useI18nContext'; -import { Icon, IconName, Text } from '../../../../component-library'; -import { NftItem } from '../../../../multichain/nft-item'; -import { - getSendAnalyticProperties, - updateSendAsset, -} from '../../../../../ducks/send'; -import { AssetType } from '../../../../../../shared/constants/transaction'; -import { MetaMetricsContext } from '../../../../../contexts/metametrics'; -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../../../../shared/constants/metametrics'; -import { isEqualCaseInsensitive } from '../../../../../../shared/modules/string-utils'; -import { CollectionImageComponent } from './collection-image.component'; - -const width = (isModal) => { - const env = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; - - if (isModal) { - return BLOCK_SIZES.ONE_THIRD; - } - if (env === ENVIRONMENT_TYPE_POPUP) { - return BLOCK_SIZES.ONE_THIRD; - } - return BLOCK_SIZES.ONE_SIXTH; -}; - -const PREVIOUSLY_OWNED_KEY = 'previouslyOwned'; - -export default function NftsItems({ - collections = {}, - previouslyOwnedCollection = {}, - isModal = false, - onCloseModal = {}, - showTokenId = false, - displayPreviouslyOwnedCollection = true, -}) { - const dispatch = useDispatch(); - const collectionsKeys = Object.keys(collections); - const nftsDropdownState = useSelector(getNftsDropdownState); - const previousCollectionKeys = usePrevious(collectionsKeys); - const { address: selectedAddress } = useSelector(getSelectedInternalAccount); - const chainId = useSelector(getCurrentChainId); - const currentChain = useSelector(getCurrentNetwork); - const t = useI18nContext(); - const ipfsGateway = useSelector(getIpfsGateway); - - const [updatedNfts, setUpdatedNfts] = useState([]); - - const trackEvent = useContext(MetaMetricsContext); - const sendAnalytics = useSelector(getSendAnalyticProperties); - - useEffect(() => { - if ( - chainId !== undefined && - selectedAddress !== undefined && - !isEqual(previousCollectionKeys, collectionsKeys) && - (nftsDropdownState?.[selectedAddress]?.[chainId] === undefined || - Object.keys(nftsDropdownState?.[selectedAddress]?.[chainId]).length === - 0) - ) { - const initState = {}; - collectionsKeys.forEach((key) => { - initState[key] = true; - }); - - const newNftDropdownState = { - ...nftsDropdownState, - [selectedAddress]: { - ...nftsDropdownState?.[selectedAddress], - [chainId]: initState, - }, - }; - - dispatch(updateNftDropDownState(newNftDropdownState)); - } - }, [ - collectionsKeys, - previousCollectionKeys, - nftsDropdownState, - selectedAddress, - chainId, - dispatch, - ]); - - const getAssetImageUrlAndUpdate = async (image, nft) => { - const nftImage = await getAssetImageURL(image, ipfsGateway); - const updatedNFt = { - ...nft, - ipfsImageUpdated: nftImage, - }; - return updatedNFt; - }; - - useEffect(() => { - const promisesArr = []; - const modifyItems = async () => { - for (const key of collectionsKeys) { - const { nfts } = collections[key]; - for (const singleNft of nfts) { - const { image, imageOriginal } = singleNft; - - const isImageHosted = - image?.startsWith('https:') || image?.startsWith('http:'); - if (!isImageHosted) { - promisesArr.push( - getAssetImageUrlAndUpdate(imageOriginal ?? image, singleNft), - ); - } - } - } - const settled = await Promise.all(promisesArr); - setUpdatedNfts(settled); - }; - - modifyItems(); - }, []); - - const history = useHistory(); - - const updateNftDropDownStateKey = (key, isExpanded) => { - const newCurrentAccountState = { - ...nftsDropdownState?.[selectedAddress]?.[chainId], - [key]: !isExpanded, - }; - - const newState = { - ...nftsDropdownState, - [selectedAddress]: { - [chainId]: newCurrentAccountState, - }, - }; - - dispatch(updateNftDropDownState(newState)); - }; - - const onSendNft = async (nft) => { - trackEvent( - { - event: MetaMetricsEventName.sendAssetSelected, - category: MetaMetricsEventCategory.Send, - properties: { - is_destination_asset_picker_modal: false, - is_nft: true, - }, - sensitiveProperties: { - ...sendAnalytics, - new_asset_symbol: nft.name, - new_asset_address: nft.address, - }, - }, - { excludeMetaMetricsId: false }, - ); - await dispatch( - updateSendAsset({ - type: AssetType.NFT, - details: nft, - skipComputeEstimatedGasLimit: false, - }), - ); - history.push(SEND_ROUTE); - onCloseModal(); - }; - - const renderCollection = ({ nfts, collectionName, collectionImage, key }) => { - if (!nfts.length) { - return null; - } - const getSource = (isImageHosted, nft) => { - if (!isImageHosted) { - const found = updatedNfts.find( - (elm) => - elm.tokenId === nft.tokenId && - isEqualCaseInsensitive(elm.address, nft.address), - ); - if (found) { - return found.ipfsImageUpdated; - } - } - return nft.image; - }; - - const isExpanded = nftsDropdownState[selectedAddress]?.[chainId]?.[key]; - return ( -
- - - {isExpanded ? ( - - {nfts.map((nft, i) => { - const { image, address, tokenId, name, imageOriginal, tokenURI } = - nft; - const nftImageAlt = getNftImageAlt(nft); - const isImageHosted = - image?.startsWith('https:') || image?.startsWith('http:'); - - const source = getSource(isImageHosted, nft); - - const isIpfsURL = ( - imageOriginal ?? - image ?? - tokenURI - )?.startsWith('ipfs:'); - const handleImageClick = () => { - if (isModal) { - return onSendNft(nft); - } - return history.push( - `${ASSET_ROUTE}/${currentChain.chainId}/${address}/${tokenId}`, - ); - }; - return ( - - - {showTokenId ? {`${t('id')}: ${tokenId}`} : null} - - ); - })} - - ) : null} -
- ); - }; - - return ( -
- - <> - {collectionsKeys.map((key) => { - const { nfts, collectionName, collectionImage } = collections[key]; - - return renderCollection({ - nfts, - collectionName, - collectionImage, - key, - isPreviouslyOwnedCollection: false, - }); - })} - {displayPreviouslyOwnedCollection - ? renderCollection({ - nfts: previouslyOwnedCollection.nfts, - collectionName: previouslyOwnedCollection.collectionName, - collectionImage: previouslyOwnedCollection.nfts[0]?.image, - isPreviouslyOwnedCollection: true, - key: PREVIOUSLY_OWNED_KEY, - }) - : null} - - -
- ); -} - -NftsItems.propTypes = { - previouslyOwnedCollection: PropTypes.shape({ - nfts: PropTypes.arrayOf( - PropTypes.shape({ - address: PropTypes.string.isRequired, - tokenId: PropTypes.string.isRequired, - name: PropTypes.string, - description: PropTypes.string, - image: PropTypes.string, - standard: PropTypes.string, - imageThumbnail: PropTypes.string, - imagePreview: PropTypes.string, - creator: PropTypes.shape({ - address: PropTypes.string, - config: PropTypes.string, - profile_img_url: PropTypes.string, - }), - }), - ), - collectionName: PropTypes.string, - collectionImage: PropTypes.string, - }), - collections: PropTypes.shape({ - nfts: PropTypes.arrayOf( - PropTypes.shape({ - address: PropTypes.string.isRequired, - tokenId: PropTypes.string.isRequired, - name: PropTypes.string, - description: PropTypes.string, - image: PropTypes.string, - standard: PropTypes.string, - imageThumbnail: PropTypes.string, - imagePreview: PropTypes.string, - creator: PropTypes.shape({ - address: PropTypes.string, - config: PropTypes.string, - profile_img_url: PropTypes.string, - }), - }), - ), - collectionImage: PropTypes.string, - collectionName: PropTypes.string, - }), - isModal: PropTypes.bool, - onCloseModal: PropTypes.func, - showTokenId: PropTypes.bool, - displayPreviouslyOwnedCollection: PropTypes.bool, -}; diff --git a/ui/components/app/assets/nfts/nfts-items/nfts-items.stories.tsx b/ui/components/app/assets/nfts/nfts-items/nfts-items.stories.tsx deleted file mode 100644 index f13af8c13ea4..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/nfts-items.stories.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import type { Meta, StoryObj } from '@storybook/react'; -import NftsItems from './nfts-items'; -import { mockNetworkState } from '../../../../../../test/stub/networks'; -import { CHAIN_IDS } from '../../../../../../shared/constants/network'; - - -// Custom middleware to ensure actions are plain objects -const ensurePlainObjectMiddleware = () => (next) => (action) => { - if (typeof action === 'function') { - return next(action()); - } - return next(action); -}; - -const mockStore = configureStore([thunk, ensurePlainObjectMiddleware]); - -const createMockState = () => ({ - metamask: { - nftsDropdownState: { - '0x123': { - '0x1': { - '0x123': true, // This sets isExpanded to true for the specific collection - previouslyOwned: true, // Expanded previously owned collection - }, - }, - }, - selectedAddress: '0x123', - selectedAccount: { - address: '0x123', - id: 'selected-account-id', - balance: '0x0', - name: 'Account 1', - }, - internalAccounts: { - selectedAccount: 'selected-account-id', - accounts: { - 'selected-account-id': { - address: '0x123', - id: 'selected-account-id', - balance: '0x0', - name: 'Account 1', - metadata: { - keyring: { - type: 'HD Key Tree', - }, - }, - }, - }, - }, - accounts: { - '0x123': { - address: '0x123', - balance: '0x0', - }, - }, - identities: { - '0x123': { - address: '0x123', - name: 'Account 1', - }, - }, - chainId: '0x1', - ipfsGateway: 'https://ipfs.io/ipfs/', - openSeaEnabled: true, - nativeCurrency: 'ETH', - currentCurrency: 'usd', - provider: { - type: 'mainnet', - chainId: '0x1', - nickname: 'Ethereum Mainnet', - }, - network: '1', - nftContracts: [], - nfts: [], - ...mockNetworkState({ chainId: CHAIN_IDS.MAINNET }), - useRequestQueue: true, - }, - appState: { - isLoading: false, - }, - send: { - currentTransactionUUID: '0x123', - draftTransactions: { - '0x123': { - id: '0x123', - status: 'unapproved', - time: Date.now(), - txParams: { - from: '0x123', - to: '0x456', - value: '0x0', - gas: '0x5208', - gasPrice: '0x3b9aca00', - }, - type: 'standard', - }, - }, - }, -}); - -const mockCollections = { - '0x123': { - nfts: [ - { - address: '0x123', - tokenId: '1', - name: 'NFT 1', - description: 'This is NFT 1', - image: './catnip-spicywright.png', - standard: 'ERC721', - }, - { - address: '0x123', - tokenId: '2', - name: 'NFT 2', - description: 'This is NFT 2', - image: './catnip-spicywright.png', - standard: 'ERC721', - }, - ], - collectionName: 'Test Collection', - collectionImage: './catnip-spicywright.png', - }, -}; - -const meta: Meta = { - title: 'Components/App/Assets/NFTs/NftsItems', - component: NftsItems, - decorators: [ - (Story) => { - const store = mockStore(createMockState()); - const originalDispatch = store.dispatch; - store.dispatch = ((action: any) => { - if (typeof action === 'function') { - return action(originalDispatch, store.getState); - } - return originalDispatch(action); - }) as typeof store.dispatch; - return ( - - - - ); - }, - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - collections: mockCollections, - previouslyOwnedCollection: { nfts: [] }, - }, -}; - -export const Modal: Story = { - args: { - ...Default.args, - isModal: true, - }, -}; - -export const WithPreviouslyOwnedCollection: Story = { - args: { - ...Default.args, - previouslyOwnedCollection: { - nfts: [ - { - address: '0x456', - tokenId: '3', - name: 'Previously Owned NFT', - description: 'This is a previously owned NFT', - image: './catnip-spicywright.png', - standard: 'ERC721', - }, - ], - collectionName: 'Previously Owned', - }, - }, -}; diff --git a/ui/components/app/assets/nfts/nfts-items/nfts-items.test.js b/ui/components/app/assets/nfts/nfts-items/nfts-items.test.js deleted file mode 100644 index 0eebd960c100..000000000000 --- a/ui/components/app/assets/nfts/nfts-items/nfts-items.test.js +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react'; -import { fireEvent } from '@testing-library/react'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { toHex } from '@metamask/controller-utils'; -import mockState from '../../../../../../test/data/mock-state.json'; -import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; -import { updateNftDropDownState } from '../../../../../store/actions'; -import { getSelectedInternalAccountFromMockState } from '../../../../../../test/jest/mocks'; -import NftsItems from '.'; - -const mockHistoryPush = jest.fn(); - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: jest.fn(() => ({ search: '' })), - useHistory: () => ({ - push: mockHistoryPush, - }), -})); - -jest.mock('../../../../../store/actions.ts', () => ({ - ...jest.requireActual('../../../../../store/actions.ts'), - updateNftDropDownState: jest.fn().mockReturnValue(jest.fn()), -})); - -const mockSelectedInternalAccount = - getSelectedInternalAccountFromMockState(mockState); - -describe('NFTs Item Component', () => { - const nfts = - mockState.metamask.allNfts[mockSelectedInternalAccount.address][toHex(5)]; - const props = { - collections: { - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { - nfts, - collectionImage: '', - collectionName: 'NFT Collection', - }, - }, - previouslyOwnedCollection: { - nfts: [], - }, - }; - - const mockStore = configureMockStore([thunk])(mockState); - - it('should expand NFT collection showing individual NFTs', async () => { - const { queryByTestId, queryAllByTestId } = renderWithProvider( - , - mockStore, - ); - - const collectionExpanderButton = queryByTestId( - 'collection-expander-button', - ); - - expect(queryAllByTestId('nft-wrapper')).toHaveLength(0); - - fireEvent.click(collectionExpanderButton); - - const expectedParams = { - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { - '0x5': { - '0x495f947276749Ce646f68AC8c248420045cb7b5e': false, - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': true, - }, - }, - }; - - expect(updateNftDropDownState).toHaveBeenCalledWith(expectedParams); - - // Force rerender component with state/store update - mockState.metamask.nftsDropdownState = expectedParams; - renderWithProvider(, mockStore); - - expect(queryAllByTestId('nft-wrapper')).toHaveLength(8); - }); - - it('should NFT click image', () => { - const { queryAllByTestId } = renderWithProvider( - , - mockStore, - ); - - const nftImages = queryAllByTestId('nft-image'); - const nftDefaultImages = queryAllByTestId('nft-default-image'); - if (nftImages.length) { - fireEvent.click(nftImages[0]); - } else { - fireEvent.click(nftDefaultImages[0]); - } - - const firstNft = nfts[0]; - const nftRoute = `/asset/${toHex(5)}/${firstNft.address}/${ - firstNft.tokenId - }`; - - expect(mockHistoryPush).toHaveBeenCalledWith(nftRoute); - }); -}); diff --git a/ui/components/app/assets/nfts/nfts-tab/index.scss b/ui/components/app/assets/nfts/nfts-tab/index.scss deleted file mode 100644 index 44c20656d417..000000000000 --- a/ui/components/app/assets/nfts/nfts-tab/index.scss +++ /dev/null @@ -1,17 +0,0 @@ -.nfts-tab { - &__fetching { - display: flex; - height: 100px; - align-items: center; - justify-content: center; - padding: 30px; - } - - &__loading { - display: flex; - height: 200px; - align-items: center; - justify-content: center; - padding: 30px; - } -} diff --git a/ui/components/app/assets/nfts/nfts-tab/index.js b/ui/components/app/assets/nfts/nfts-tab/index.ts similarity index 100% rename from ui/components/app/assets/nfts/nfts-tab/index.js rename to ui/components/app/assets/nfts/nfts-tab/index.ts diff --git a/ui/components/app/assets/nfts/nfts-tab/nfts-tab.test.js b/ui/components/app/assets/nfts/nfts-tab/nfts-tab.test.js index 52acdbcd84f4..ee694da48fca 100644 --- a/ui/components/app/assets/nfts/nfts-tab/nfts-tab.test.js +++ b/ui/components/app/assets/nfts/nfts-tab/nfts-tab.test.js @@ -133,20 +133,6 @@ const NFTS = [ }, ]; -const NFTS_CONTRACTS = [ - { - address: '0x495f947276749Ce646f68AC8c248420045cb7b5e', - name: 'PUNKS', - symbol: 'PNKS', - schemaName: 'ERC1155', - }, - { - address: '0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414', - name: 'Munks', - symbol: 'MNKS', - }, -]; - const nftsDropdownState = { '0x495f947276749ce646f68ac8c248420045cb7b5e': true, '0xdc7382eb0bc9c352a4cba23c909bda01e0206414': true, @@ -312,27 +298,6 @@ describe('NFT Items', () => { }); }); - describe('Collections', () => { - it('should render the name of the collections and number of NFTs in each collection if current account/chainId combination has NFTs', () => { - render({ - selectedAddress: ACCOUNT_1, - nfts: NFTS, - nftContracts: NFTS_CONTRACTS, - }); - expect(screen.queryByText('PUNKS (5)')).toBeInTheDocument(); - expect(screen.queryByText('Munks (3)')).toBeInTheDocument(); - }); - it('should not render collections if current account/chainId combination has NFTs', () => { - render({ - selectedAddress: ACCOUNT_2, - nfts: NFTS, - nftContracts: NFTS_CONTRACTS, - }); - expect(screen.queryByText('PUNKS (5)')).not.toBeInTheDocument(); - expect(screen.queryByText('Munks (3)')).not.toBeInTheDocument(); - }); - }); - describe('NFTs options', () => { it('should render a link "Refresh list" when some NFTs are present on mainnet and NFT auto-detection preference is set to true, which, when clicked calls methods DetectNFTs and checkAndUpdateNftsOwnershipStatus', () => { render({ diff --git a/ui/components/app/assets/nfts/nfts-tab/nfts-tab.js b/ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx similarity index 50% rename from ui/components/app/assets/nfts/nfts-tab/nfts-tab.js rename to ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx index 2a9f3287346e..3faae7c68e16 100644 --- a/ui/components/app/assets/nfts/nfts-tab/nfts-tab.js +++ b/ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx @@ -1,17 +1,16 @@ import React, { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; +import { Hex } from '@metamask/utils'; import { AlignItems, Display, FlexDirection, JustifyContent, - Size, TextAlign, TextColor, TextVariant, } from '../../../../../helpers/constants/design-system'; -import { SECURITY_ROUTE } from '../../../../../helpers/constants/routes'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { useNftsCollections } from '../../../../../hooks/useNftsCollections'; import { @@ -19,18 +18,16 @@ import { getIsMainnet, getUseNftDetection, getNftIsStillFetchingIndication, + getPreferences, } from '../../../../../selectors'; import { - checkAndUpdateAllNftsOwnershipStatus, - detectNfts, - showImportNftsModal, -} from '../../../../../store/actions'; -import { Box, ButtonLink, IconName, Text } from '../../../../component-library'; + Box, + ButtonLink, + ButtonLinkSize, + IconName, + Text, +} from '../../../../component-library'; import NFTsDetectionNoticeNFTsTab from '../nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab'; -import NftsItems from '../nfts-items'; -///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) -import ZENDESK_URLS from '../../../../../helpers/constants/zendesk-url'; -///: END:ONLY_INCLUDE_IF import { MetaMetricsContext } from '../../../../../contexts/metametrics'; import { ORIGIN_METAMASK } from '../../../../../../shared/constants/app'; import { @@ -40,31 +37,43 @@ import { import { getCurrentLocale } from '../../../../../ducks/locale/locale'; import Spinner from '../../../../ui/spinner'; import { endTrace, TraceName } from '../../../../../../shared/lib/trace'; +import { useNfts } from '../../../../../hooks/useNfts'; +import { NFT } from '../../../../multichain/asset-picker-amount/asset-picker-modal/types'; +import { + checkAndUpdateAllNftsOwnershipStatus, + detectNfts, + showImportNftsModal, +} from '../../../../../store/actions'; +import { + ASSET_ROUTE, + SECURITY_ROUTE, +} from '../../../../../helpers/constants/routes'; +import NftGrid from '../nft-grid/nft-grid'; +///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) +import ZENDESK_URLS from '../../../../../helpers/constants/zendesk-url'; +///: END:ONLY_INCLUDE_IF +import { sortAssets } from '../../util/sort'; export default function NftsTab() { + const history = useHistory(); + const dispatch = useDispatch(); const useNftDetection = useSelector(getUseNftDetection); const isMainnet = useSelector(getIsMainnet); - const history = useHistory(); + const { privacyMode } = useSelector(getPreferences); const t = useI18nContext(); - const dispatch = useDispatch(); const trackEvent = useContext(MetaMetricsContext); const nftsStillFetchingIndication = useSelector( getNftIsStillFetchingIndication, ); - - const { nftsLoading, collections, previouslyOwnedCollection } = - useNftsCollections(); - - const onEnableAutoDetect = () => { - history.push(SECURITY_ROUTE); + const currentChain = useSelector(getCurrentNetwork) as { + chainId: Hex; + nickname: string; + rpcPrefs?: { imageUrl: string }; }; - const onRefresh = () => { - if (isMainnet) { - dispatch(detectNfts()); - } - checkAndUpdateAllNftsOwnershipStatus(); - }; + const { nftsLoading, collections } = useNftsCollections(); + + const { currentlyOwnedNfts, previouslyOwnedNfts } = useNfts(); const hasAnyNfts = Object.keys(collections).length > 0; const showNftBanner = hasAnyNfts === false; @@ -100,6 +109,29 @@ export default function NftsTab() { } }, [nftsLoading, nftsStillFetchingIndication]); + const handleNftClick = (nft: NFT) => { + history.push( + `${ASSET_ROUTE}/${currentChain.chainId}/${nft.address}/${nft.tokenId}`, + ); + }; + + const onEnableAutoDetect = () => { + history.push(SECURITY_ROUTE); + }; + + const onRefresh = () => { + if (isMainnet) { + dispatch(detectNfts()); + } + checkAndUpdateAllNftsOwnershipStatus(); + }; + + const sortedNfts = sortAssets(currentlyOwnedNfts, { + key: 'collection.name', + order: 'asc', + sortCallback: 'alphaNumeric', + }); + if (!hasAnyNfts && nftsStillFetchingIndication) { return ( @@ -119,21 +151,62 @@ export default function NftsTab() { ) : null} - {hasAnyNfts > 0 || previouslyOwnedCollection.nfts.length > 0 ? ( + {hasAnyNfts || previouslyOwnedNfts.length > 0 ? ( - + + { + dispatch(showImportNftsModal({})); + }} + > + {t('importNFT')} + - {nftsStillFetchingIndication ? ( - - - - ) : null} + {!isMainnet && Object.keys(collections).length < 1 ? null : ( + <> + + {isMainnet && !useNftDetection ? ( + + {t('enableAutoDetect')} + + ) : ( + + {t('refreshList')} + + )} + + + )} + ) : ( <> @@ -159,7 +232,7 @@ export default function NftsTab() { {t('noNFTs')} @@ -167,7 +240,7 @@ export default function NftsTab() { { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) @@ -177,56 +250,57 @@ export default function NftsTab() { } - - )} - - { - dispatch(showImportNftsModal()); - }} - > - {t('importNFT')} - - {!isMainnet && Object.keys(collections).length < 1 ? null : ( - <> - + { + dispatch(showImportNftsModal({})); + }} > - {isMainnet && !useNftDetection ? ( - - {t('enableAutoDetect')} - - ) : ( - + + {!isMainnet && Object.keys(collections).length < 1 ? null : ( + <> + - {t('refreshList')} - - )} - - - )} - + {isMainnet && !useNftDetection ? ( + + {t('enableAutoDetect')} + + ) : ( + + {t('refreshList')} + + )} + + + )} + + + )} ); diff --git a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap index 30efc922ec7e..3ec0face8295 100644 --- a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap +++ b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap @@ -3,13 +3,14 @@ exports[`Token Cell should match snapshot 1`] = `
network logo
diff --git a/ui/components/app/assets/token-cell/token-cell.test.tsx b/ui/components/app/assets/token-cell/token-cell.test.tsx index 285c3dfac778..d80996776c49 100644 --- a/ui/components/app/assets/token-cell/token-cell.test.tsx +++ b/ui/components/app/assets/token-cell/token-cell.test.tsx @@ -5,10 +5,10 @@ import { fireEvent } from '@testing-library/react'; import { useSelector } from 'react-redux'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; import { getTokenList, getPreferences, - getCurrentCurrency, getCurrencyRates, } from '../../../../selectors'; import { diff --git a/ui/components/app/assets/token-cell/token-cell.tsx b/ui/components/app/assets/token-cell/token-cell.tsx index d470ef3dc21a..c194bdca485c 100644 --- a/ui/components/app/assets/token-cell/token-cell.tsx +++ b/ui/components/app/assets/token-cell/token-cell.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { BigNumber } from 'bignumber.js'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; import { - getCurrentCurrency, getTokenList, selectERC20TokensByChain, getNativeCurrencyForChain, diff --git a/ui/components/app/assets/token-list/token-list.tsx b/ui/components/app/assets/token-list/token-list.tsx index 5e25c7ff5f41..5555c8782f8a 100644 --- a/ui/components/app/assets/token-list/token-list.tsx +++ b/ui/components/app/assets/token-list/token-list.tsx @@ -5,9 +5,11 @@ import TokenCell from '../token-cell'; import { TEST_CHAINS } from '../../../../../shared/constants/network'; import { sortAssets } from '../util/sort'; import { + getChainIdsToPoll, getCurrencyRates, getCurrentNetwork, getIsTestnet, + getIsTokenNetworkFilterEqualCurrentNetwork, getMarketData, getNetworkConfigurationIdByChainId, getNewTokensImported, @@ -17,6 +19,7 @@ import { getSelectedAccountTokensAcrossChains, getShowFiatInTestnets, getTokenExchangeRates, + getTokenNetworkFilter, } from '../../../../selectors'; import { getConversionRate } from '../../../../ducks/metamask/metamask'; import { filterAssets } from '../util/filter'; @@ -25,7 +28,6 @@ import { calculateTokenFiatAmount } from '../util/calculateTokenFiatAmount'; import { endTrace, TraceName } from '../../../../../shared/lib/trace'; import { useTokenBalances } from '../../../../hooks/useTokenBalances'; import { setTokenNetworkFilter } from '../../../../store/actions'; -import { useI18nContext } from '../../../../hooks/useI18nContext'; import { useMultichainSelector } from '../../../../hooks/useMultichainSelector'; import { getMultichainShouldShowFiat } from '../../../../selectors/multichain'; @@ -82,22 +84,28 @@ export default function TokenList({ onTokenClick, nativeToken, }: TokenListProps) { - const t = useI18nContext(); const dispatch = useDispatch(); const currentNetwork = useSelector(getCurrentNetwork); const allNetworks = useSelector(getNetworkConfigurationIdByChainId); - const { tokenSortConfig, tokenNetworkFilter, privacyMode } = + const { tokenSortConfig, privacyMode, hideZeroBalanceTokens } = useSelector(getPreferences); + const tokenNetworkFilter = useSelector(getTokenNetworkFilter); const selectedAccount = useSelector(getSelectedAccount); const conversionRate = useSelector(getConversionRate); + const chainIdsToPoll = useSelector(getChainIdsToPoll); const contractExchangeRates = useSelector( getTokenExchangeRates, shallowEqual, ); const newTokensImported = useSelector(getNewTokensImported); const selectedAccountTokensChains = useFilteredAccountTokens(currentNetwork); + const isOnCurrentNetwork = useSelector( + getIsTokenNetworkFilterEqualCurrentNetwork, + ); - const { tokenBalances } = useTokenBalances(); + const { tokenBalances } = useTokenBalances({ + chainIds: chainIdsToPoll as Hex[], + }); const selectedAccountTokenBalancesAcrossChains = tokenBalances[selectedAccount.address]; @@ -109,14 +117,13 @@ export default function TokenList({ const nativeBalances: Record = useSelector( getSelectedAccountNativeTokenCachedBalanceByChainId, ) as Record; - + const isTestnet = useSelector(getIsTestnet); // Ensure newly added networks are included in the tokenNetworkFilter useEffect(() => { if (process.env.PORTFOLIO_VIEW) { const allNetworkFilters = Object.fromEntries( Object.keys(allNetworks).map((chainId) => [chainId, true]), ); - if (Object.keys(tokenNetworkFilter).length > 1) { dispatch(setTokenNetworkFilter(allNetworkFilters)); } @@ -125,7 +132,6 @@ export default function TokenList({ const consolidatedBalances = () => { const tokensWithBalance: TokenWithFiatAmount[] = []; - Object.entries(selectedAccountTokensChains).forEach( ([stringChainKey, tokens]) => { const chainId = stringChainKey as Hex; @@ -149,14 +155,28 @@ export default function TokenList({ currencyRates, }); - // Append processed token with balance and fiat amount - tokensWithBalance.push({ - ...token, - balance, - tokenFiatAmount, - chainId, - string: String(balance), - }); + // Respect the "hide zero balance" setting (when true): + // - Native tokens should always display with zero balance when on the current network filter. + // - Native tokens should not display with zero balance when on all networks filter + // - ERC20 tokens with zero balances should respect the setting on both the current and all networks. + + // Respect the "hide zero balance" setting (when false): + // - Native tokens should always display with zero balance when on the current network filter. + // - Native tokens should always display with zero balance when on all networks filter + // - ERC20 tokens always display with zero balance on both the current and all networks filter. + if ( + !hideZeroBalanceTokens || + balance !== '0' || + (token.isNative && isOnCurrentNetwork) + ) { + tokensWithBalance.push({ + ...token, + balance, + tokenFiatAmount, + chainId, + string: String(balance), + }); + } }); }, ); @@ -212,14 +232,6 @@ export default function TokenList({ return React.cloneElement(nativeToken as React.ReactElement); } - // TODO: We can remove this string. However it will result in a huge file 50+ file diff - // Lets remove it in a separate PR - if (sortedFilteredTokens === undefined) { - console.log(t('loadingTokens')); - } - - // Check if testnet - const isTestnet = useSelector(getIsTestnet); const shouldShowFiat = useMultichainSelector( getMultichainShouldShowFiat, selectedAccount, diff --git a/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx b/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx index a6b083eccc16..52faaf0384af 100644 --- a/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx +++ b/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx @@ -37,7 +37,7 @@ import { import { MetaMetricsContext } from '../../../contexts/metametrics'; import { getUseExternalServices } from '../../../selectors'; import { selectIsMetamaskNotificationsEnabled } from '../../../selectors/metamask-notifications/metamask-notifications'; -import { selectIsProfileSyncingEnabled } from '../../../selectors/metamask-notifications/profile-syncing'; +import { selectIsProfileSyncingEnabled } from '../../../selectors/identity/profile-syncing'; import { hideBasicFunctionalityModal, onboardingToggleBasicFunctionalityOff, diff --git a/ui/components/app/confirm/info/row/__snapshots__/address.test.tsx.snap b/ui/components/app/confirm/info/row/__snapshots__/address.test.tsx.snap index ec2eacb0d44b..87a709c0d80e 100644 --- a/ui/components/app/confirm/info/row/__snapshots__/address.test.tsx.snap +++ b/ui/components/app/confirm/info/row/__snapshots__/address.test.tsx.snap @@ -9,10 +9,11 @@ exports[`ConfirmInfoRowAddress renders appropriately with PetNames disabled with class="mm-box mm-box--display-flex mm-box--flex-direction-row mm-box--align-items-center" >