From 54525f189153a08a1bde55a846825c723663d52d Mon Sep 17 00:00:00 2001 From: Vince Howard Date: Wed, 23 Oct 2024 04:47:07 -0600 Subject: [PATCH 01/19] fix(1702): update onboarding settings UI (#11723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This is a follow up to this [pull request](https://github.com/MetaMask/metamask-mobile/pull/11127). Updating the UI according to feedback as well as copy @yanrong-chen @hesterbruikman ![Screenshot 2024-10-09 at 11 16 10 AM](https://github.com/user-attachments/assets/8602d32f-72a0-4542-8aed-169f75914f70) ![Screenshot 2024-10-09 at 11 16 25 AM](https://github.com/user-attachments/assets/bc431893-f850-41b1-aaf6-242faded5f8c) ![Screenshot 2024-10-09 at 11 16 41 AM](https://github.com/user-attachments/assets/46bd14d8-d9d6-45c2-bd2b-f9fc1d269b6b) ## **Related issues** Fixes: Feature: [#1702](https://github.com/MetaMask/mobile-planning/issues/1702) ## **Manual testing steps** 1. Fresh install application 2. Create/import new wallet 3. Success screen will appear with the title "Your Wallet is ready" and below the title and description, click on the text that says "Manage default settings" 4. View the changes in within the Default Settings Screen 5. Click on Assets and scroll down to view no copy for Batch Transactions ## **Screenshots/Recordings** ### Default Settings Screen | Before | After | |:---:|:---:| |![Simulator Screenshot - iPhone 16 Pro Max - 2024-10-09 at 15 57 48](https://github.com/user-attachments/assets/c4e9ec67-ff10-4f19-949f-1a44b1c01db4)|![Simulator Screenshot - iPhone 16 Pro Max - 2024-10-09 at 15 58 07](https://github.com/user-attachments/assets/afe1b076-958c-4a03-b35a-8496a5bb0919)| ### Manage Default Settings | Before | After | |:---:|:---:| |![Simulator Screenshot - iPhone 16 Pro Max - 2024-10-09 at 15 58 18](https://github.com/user-attachments/assets/d26c8684-90da-43e2-94ad-f819592072e8)|![Simulator Screenshot - iPhone 16 Pro Max - 2024-10-09 at 15 58 29](https://github.com/user-attachments/assets/a7a8354f-29e3-490d-a638-3a439a12568b)| ### **Before** NA ### **After** NA ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/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. --- .../__snapshots__/index.test.tsx.snap | 37 +++++++++++-------- .../DefaultSettings/index.styles.ts | 7 ++++ .../DefaultSettings/index.tsx | 18 +++++---- .../__snapshots__/index.test.tsx.snap | 2 +- .../__snapshots__/index.test.js.snap | 1 + .../Views/OnboardingSuccess/index.styles.ts | 1 + .../__snapshots__/index.test.tsx.snap | 2 +- .../SecuritySettings.test.tsx.snap | 2 +- locales/languages/en.json | 2 +- 9 files changed, 45 insertions(+), 27 deletions(-) diff --git a/app/components/Views/OnboardingSuccess/DefaultSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/OnboardingSuccess/DefaultSettings/__snapshots__/index.test.tsx.snap index 889294a0f8e..1188da6d302 100644 --- a/app/components/Views/OnboardingSuccess/DefaultSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/OnboardingSuccess/DefaultSettings/__snapshots__/index.test.tsx.snap @@ -5,32 +5,23 @@ exports[`DefaultSettings should render correctly 1`] = ` style={ { "flex": 1, - "paddingHorizontal": 16, "paddingTop": 16, } } > - - MetaMask uses default settings to best balance safety and ease of use. Change these settings to further increase your privacy. - - Learn more about privacy best practices. + MetaMask uses default settings to best balance safety and ease of use. Change these settings to further increase your privacy. + + + Learn more about privacy best practices. + - + diff --git a/app/components/Views/OnboardingSuccess/DefaultSettings/index.styles.ts b/app/components/Views/OnboardingSuccess/DefaultSettings/index.styles.ts index c8eb0846267..b7d364a31f8 100644 --- a/app/components/Views/OnboardingSuccess/DefaultSettings/index.styles.ts +++ b/app/components/Views/OnboardingSuccess/DefaultSettings/index.styles.ts @@ -7,6 +7,13 @@ const styleSheet = () => paddingHorizontal: 16, paddingTop: 16, }, + scrollRoot: { + flex: 1, + paddingTop: 16, + }, + textContainer: { + paddingHorizontal: 16, + }, }); export default styleSheet; diff --git a/app/components/Views/OnboardingSuccess/DefaultSettings/index.tsx b/app/components/Views/OnboardingSuccess/DefaultSettings/index.tsx index 04398e444e9..23f69a74bc6 100644 --- a/app/components/Views/OnboardingSuccess/DefaultSettings/index.tsx +++ b/app/components/Views/OnboardingSuccess/DefaultSettings/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ScrollView, Linking } from 'react-native'; +import { ScrollView, Linking, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useOnboardingHeader } from '../../../hooks/useOnboardingHeader'; import { useStyles } from '../../../../component-library/hooks'; @@ -23,14 +23,16 @@ const DefaultSettings = () => { }; return ( - - - {strings('default_settings.description')} - - {' '} - {strings('default_settings.learn_more_about_privacy')} + + + + {strings('default_settings.description')} + + {' '} + {strings('default_settings.learn_more_about_privacy')} + - + - We batch accounts and query Infura to responsively show your balances. If you turn this off, only active accounts will be queried. Some dApps won’t work unless you connect your wallet. + Get balance updates for all your accounts at once. Turning off this feature means others are less likely to associate one account with another. diff --git a/app/components/Views/OnboardingSuccess/__snapshots__/index.test.js.snap b/app/components/Views/OnboardingSuccess/__snapshots__/index.test.js.snap index 719f3b38306..fe9a3706b63 100644 --- a/app/components/Views/OnboardingSuccess/__snapshots__/index.test.js.snap +++ b/app/components/Views/OnboardingSuccess/__snapshots__/index.test.js.snap @@ -120,6 +120,7 @@ exports[`OnboardingSuccess should render correctly 1`] = ` { "alignItems": "center", "flexDirection": "row", + "marginTop": 16, }, { "paddingTop": 10, diff --git a/app/components/Views/OnboardingSuccess/index.styles.ts b/app/components/Views/OnboardingSuccess/index.styles.ts index 10b6abba87a..9740f0ae402 100644 --- a/app/components/Views/OnboardingSuccess/index.styles.ts +++ b/app/components/Views/OnboardingSuccess/index.styles.ts @@ -54,6 +54,7 @@ const styles = StyleSheet.create({ linkWrapper: { flexDirection: 'row', alignItems: 'center', + marginTop: 16, }, footerText: { fontSize: 12, diff --git a/app/components/Views/Settings/BatchAccountBalanceSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/BatchAccountBalanceSettings/__snapshots__/index.test.tsx.snap index 2d527d6f72d..b55d2c4c1e5 100644 --- a/app/components/Views/Settings/BatchAccountBalanceSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/BatchAccountBalanceSettings/__snapshots__/index.test.tsx.snap @@ -84,7 +84,7 @@ exports[`BatchAccountBalanceSettings should render correctly 1`] = ` } } > - We batch accounts and query Infura to responsively show your balances. If you turn this off, only active accounts will be queried. Some dApps won’t work unless you connect your wallet. + Get balance updates for all your accounts at once. Turning off this feature means others are less likely to associate one account with another. `; diff --git a/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap b/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap index fff27caa527..efb9abf4c98 100644 --- a/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap +++ b/app/components/Views/Settings/SecuritySettings/__snapshots__/SecuritySettings.test.tsx.snap @@ -1501,7 +1501,7 @@ exports[`SecuritySettings should render correctly 1`] = ` } } > - We batch accounts and query Infura to responsively show your balances. If you turn this off, only active accounts will be queried. Some dApps won’t work unless you connect your wallet. + Get balance updates for all your accounts at once. Turning off this feature means others are less likely to associate one account with another. Date: Wed, 23 Oct 2024 11:17:06 +0000 Subject: [PATCH 02/19] fix: migrate from legacy ethjs to @metamask/ethjs (#11969) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - fix: migrate from legacy `ethjs-` packages to maintained and already used `@metamask/ethjs-` forks - Removes dependency on deprecated `babel-runtime` v6 - Bundle size reduction ## **Related issues** - #11952 - #11932 #### Blocking - #11968 ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/components/UI/Swaps/QuotesView.js | 2 +- .../confirmations/SendFlow/Confirm/index.js | 2 +- .../ApproveTransactionReview/index.js | 2 +- .../components/TransactionReview/index.js | 2 +- app/lib/ens-ipfs/resolver.js | 4 +- app/util/number/index.js | 2 +- package.json | 7 +- patches/@metamask+ethjs-contract+0.4.1.patch | 13 ++++ patches/@metamask+ethjs-query+0.7.1.patch | 13 ++++ patches/ethjs-contract+0.2.3.patch | 13 ---- patches/ethjs-query+0.3.8.patch | 13 ---- yarn.lock | 65 +------------------ 12 files changed, 38 insertions(+), 100 deletions(-) create mode 100644 patches/@metamask+ethjs-contract+0.4.1.patch create mode 100644 patches/@metamask+ethjs-query+0.7.1.patch delete mode 100644 patches/ethjs-contract+0.2.3.patch delete mode 100644 patches/ethjs-query+0.3.8.patch diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 982759f89c0..47b02c0ad94 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import Eth from 'ethjs-query'; +import Eth from '@metamask/ethjs-query'; import { View, StyleSheet, diff --git a/app/components/Views/confirmations/SendFlow/Confirm/index.js b/app/components/Views/confirmations/SendFlow/Confirm/index.js index 7fe1fda6d76..fb876cbb790 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/index.js +++ b/app/components/Views/confirmations/SendFlow/Confirm/index.js @@ -11,7 +11,7 @@ import { import { connect } from 'react-redux'; import { getSendFlowTitle } from '../../../../UI/Navbar'; import PropTypes from 'prop-types'; -import Eth from 'ethjs-query'; +import Eth from '@metamask/ethjs-query'; import { renderFromWei, renderFromTokenMinimalUnit, diff --git a/app/components/Views/confirmations/components/ApproveTransactionReview/index.js b/app/components/Views/confirmations/components/ApproveTransactionReview/index.js index 3d51f439938..249031cf7af 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionReview/index.js +++ b/app/components/Views/confirmations/components/ApproveTransactionReview/index.js @@ -6,7 +6,7 @@ import { Linking, ScrollView, } from 'react-native'; -import Eth from 'ethjs-query'; +import Eth from '@metamask/ethjs-query'; import ActionView, { ConfirmButtonState } from '../../../../UI/ActionView'; import PropTypes from 'prop-types'; import { getApproveNavbar } from '../../../../UI/Navbar'; diff --git a/app/components/Views/confirmations/components/TransactionReview/index.js b/app/components/Views/confirmations/components/TransactionReview/index.js index 369e3764d3a..48fc2914b78 100644 --- a/app/components/Views/confirmations/components/TransactionReview/index.js +++ b/app/components/Views/confirmations/components/TransactionReview/index.js @@ -1,5 +1,5 @@ +import Eth from '@metamask/ethjs-query'; import { withNavigation } from '@react-navigation/compat'; -import Eth from 'ethjs-query'; import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; import { Animated, ScrollView, StyleSheet, View } from 'react-native'; diff --git a/app/lib/ens-ipfs/resolver.js b/app/lib/ens-ipfs/resolver.js index 2eb3b24944a..6df0b72b34a 100644 --- a/app/lib/ens-ipfs/resolver.js +++ b/app/lib/ens-ipfs/resolver.js @@ -1,6 +1,6 @@ import namehash from 'eth-ens-namehash'; -import Eth from 'ethjs-query'; -import EthContract from 'ethjs-contract'; +import Eth from '@metamask/ethjs-query'; +import EthContract from '@metamask/ethjs-contract'; import registryAbi from './contracts/registry'; import resolverAbi from './contracts/resolver'; import contentHash from 'content-hash'; diff --git a/app/util/number/index.js b/app/util/number/index.js index a4ce4d98e05..28027c52b7a 100644 --- a/app/util/number/index.js +++ b/app/util/number/index.js @@ -3,7 +3,7 @@ */ import { BN, stripHexPrefix } from 'ethereumjs-util'; import { utils as ethersUtils } from 'ethers'; -import convert from 'ethjs-unit'; +import convert from '@metamask/ethjs-unit'; import { BNToHex, hexToBN as controllerHexToBN, diff --git a/package.json b/package.json index 7ce3e5e67fe..c474cba0370 100644 --- a/package.json +++ b/package.json @@ -153,6 +153,9 @@ "@metamask/eth-sig-util": "^7.0.2", "@metamask/eth-snap-keyring": "^4.3.3", "@metamask/etherscan-link": "^2.0.0", + "@metamask/ethjs-contract": "^0.4.1", + "@metamask/ethjs-query": "^0.7.1", + "@metamask/ethjs-unit": "^0.3.0", "@metamask/gas-fee-controller": "^18.0.0", "@metamask/key-tree": "^9.0.0", "@metamask/keyring-api": "^8.1.0", @@ -242,10 +245,7 @@ "ethereumjs-abi": "0.6.6", "ethereumjs-util": "6.1.0", "ethers": "^5.0.14", - "ethjs-contract": "0.2.3", "ethjs-ens": "2.0.1", - "ethjs-query": "0.3.8", - "ethjs-unit": "0.1.6", "eventemitter2": "^6.4.9", "events": "3.0.0", "fuse.js": "3.4.4", @@ -558,7 +558,6 @@ "eciesjs>secp256k1": true, "ethereumjs-util>keccak": true, "ethereumjs-util>secp256k1": true, - "ethjs-query>babel-runtime>core-js": false, "ganache>@trufflesuite/bigint-buffer": false, "ganache>bufferutil": false, "ganache>keccak": true, diff --git a/patches/@metamask+ethjs-contract+0.4.1.patch b/patches/@metamask+ethjs-contract+0.4.1.patch new file mode 100644 index 00000000000..1c8b36e6eb5 --- /dev/null +++ b/patches/@metamask+ethjs-contract+0.4.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@metamask/ethjs-contract/package.json b/node_modules/@metamask/ethjs-contract/package.json +index 4991d59..8343099 100644 +--- a/node_modules/@metamask/ethjs-contract/package.json ++++ b/node_modules/@metamask/ethjs-contract/package.json +@@ -194,7 +194,7 @@ + "rpc" + ], + "license": "MIT", +- "main": "lib/index.js", ++ "main": "src/index.js", + "name": "@metamask/ethjs-contract", + "peerDependencies": { + "@babel/runtime": "^7.0.0" diff --git a/patches/@metamask+ethjs-query+0.7.1.patch b/patches/@metamask+ethjs-query+0.7.1.patch new file mode 100644 index 00000000000..7447fe7212f --- /dev/null +++ b/patches/@metamask+ethjs-query+0.7.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@metamask/ethjs-query/package.json b/node_modules/@metamask/ethjs-query/package.json +index 14e1062..6860d86 100644 +--- a/node_modules/@metamask/ethjs-query/package.json ++++ b/node_modules/@metamask/ethjs-query/package.json +@@ -2,7 +2,7 @@ + "name": "@metamask/ethjs-query", + "version": "0.7.1", + "description": "A simple query layer for the Ethereum RPC.", +- "main": "lib/index.js", ++ "main": "src/index.js", + "files": [ + "dist", + "internals", diff --git a/patches/ethjs-contract+0.2.3.patch b/patches/ethjs-contract+0.2.3.patch deleted file mode 100644 index 0c16ee54860..00000000000 --- a/patches/ethjs-contract+0.2.3.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/ethjs-contract/package.json b/node_modules/ethjs-contract/package.json -index 8e58ff5..470d00b 100644 ---- a/node_modules/ethjs-contract/package.json -+++ b/node_modules/ethjs-contract/package.json -@@ -197,7 +197,7 @@ - "lint-staged": { - "lint:eslint": "*.js" - }, -- "main": "lib/index.js", -+ "main": "src/index.js", - "name": "ethjs-contract", - "pre-commit": "build", - "repository": { diff --git a/patches/ethjs-query+0.3.8.patch b/patches/ethjs-query+0.3.8.patch deleted file mode 100644 index e5088845174..00000000000 --- a/patches/ethjs-query+0.3.8.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/ethjs-query/package.json b/node_modules/ethjs-query/package.json -index fb82d51..503159c 100644 ---- a/node_modules/ethjs-query/package.json -+++ b/node_modules/ethjs-query/package.json -@@ -2,7 +2,7 @@ - "name": "ethjs-query", - "version": "0.3.8", - "description": "A simple query layer for the Ethereum RPC.", -- "main": "lib/index.js", -+ "main": "src/index.js", - "files": [ - "dist", - "internals", diff --git a/yarn.lock b/yarn.lock index 532ddd37cd1..cfc7baf393b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13179,14 +13179,6 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - backo2@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -14773,7 +14765,7 @@ core-js-compat@^3.31.0, core-js-compat@^3.36.1: dependencies: browserslist "^4.23.0" -core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.5: +core-js@^2.5.7, core-js@^2.6.5: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== @@ -17358,17 +17350,6 @@ ethjs-abi@^0.2.0: js-sha3 "0.5.5" number-to-bn "1.7.0" -ethjs-contract@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/ethjs-contract/-/ethjs-contract-0.2.3.tgz#f113ced8ed1c9c635b0b7ec71901340b64e8cded" - integrity sha512-fKsHm57wxwHrZhVlD8AHU2lC2G3c1fmvoEz15BpqIkuGWiTbjuvrQo2Avc+3EQpSsTFWNdyxC0h1WKRcn5kkyQ== - dependencies: - babel-runtime "^6.26.0" - ethjs-abi "0.2.0" - ethjs-filter "0.1.8" - ethjs-util "0.1.3" - js-sha3 "0.5.5" - ethjs-contract@^0.1.7: version "0.1.9" resolved "https://registry.yarnpkg.com/ethjs-contract/-/ethjs-contract-0.1.9.tgz#1c2766896a56d47ec1d6d661829c49cc38a5520a" @@ -17394,11 +17375,6 @@ ethjs-filter@0.1.5: resolved "https://registry.yarnpkg.com/ethjs-filter/-/ethjs-filter-0.1.5.tgz#0112af6017c24677e32b8fdeb20e6196019b7598" integrity sha512-ii+Av0Kv0mOzQSP2aBSWTeamTntWDXgHN0Sj1UnTpzYH+gQdJCvirsc0BO07MWcsk0kuLogITDOPVFFd7qcUMQ== -ethjs-filter@0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/ethjs-filter/-/ethjs-filter-0.1.8.tgz#2b02726b820ed4dd3860614d185c0c0f7ed1747f" - integrity sha512-qTDPskDL2UadHwjvM8A+WG9HwM4/FoSY3p3rMJORkHltYcAuiQZd2otzOYKcL5w2Q3sbAkW/E3yt/FPFL/AVXA== - ethjs-format@0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/ethjs-format/-/ethjs-format-0.2.2.tgz#d73b3a605c2e1257079f7077fd5448e998ce0fcd" @@ -17411,28 +17387,6 @@ ethjs-format@0.2.2: number-to-bn "1.7.0" strip-hex-prefix "1.0.0" -ethjs-format@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/ethjs-format/-/ethjs-format-0.2.7.tgz#20c92f31c259a381588d069830d838b489774b86" - integrity sha512-uNYAi+r3/mvR3xYu2AfSXx5teP4ovy9z2FrRsblU+h2logsaIKZPi9V3bn3V7wuRcnG0HZ3QydgZuVaRo06C4Q== - dependencies: - bn.js "4.11.6" - ethjs-schema "0.2.1" - ethjs-util "0.1.3" - is-hex-prefixed "1.0.0" - number-to-bn "1.7.0" - strip-hex-prefix "1.0.0" - -ethjs-query@0.3.8: - version "0.3.8" - resolved "https://registry.yarnpkg.com/ethjs-query/-/ethjs-query-0.3.8.tgz#aa5af02887bdd5f3c78b3256d0f22ffd5d357490" - integrity sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ== - dependencies: - babel-runtime "^6.26.0" - ethjs-format "0.2.7" - ethjs-rpc "0.2.0" - promise-to-callback "^1.0.0" - ethjs-query@^0.2.4: version "0.2.9" resolved "https://registry.yarnpkg.com/ethjs-query/-/ethjs-query-0.2.9.tgz#a26e6b4f38699e92f34b2184e75c7894329c42f1" @@ -17446,13 +17400,6 @@ ethjs-rpc@0.1.5: resolved "https://registry.yarnpkg.com/ethjs-rpc/-/ethjs-rpc-0.1.5.tgz#099e22f27dc4c18b6978a485fc36b1b0f7969080" integrity sha512-RSqGrUjkA1UHl0d/FteGHFCv3spO/sA+lQHiIYIczYKE1nkM1Q2fKjiqssq+6LnYc+g3NWOBr4AIKQzXc8RQ1g== -ethjs-rpc@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz#3d0011e32cfff156ed6147818c6fb8f801701b4c" - integrity sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg== - dependencies: - promise-to-callback "^1.0.0" - ethjs-schema@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.1.5.tgz#59740e3b3977bcdbb9b11bc3068201e8aceabb0d" @@ -17463,14 +17410,6 @@ ethjs-schema@0.2.1: resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.2.1.tgz#47e138920421453617069034684642e26bb310f4" integrity sha512-DXd8lwNrhT9sjsh/Vd2Z+4pfyGxhc0POVnLBUfwk5udtdoBzADyq+sK39dcb48+ZU+2VgtwHxtGWnLnCfmfW5g== -ethjs-unit@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" - integrity sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk= - dependencies: - bn.js "4.11.6" - number-to-bn "1.7.0" - ethjs-util@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.3.tgz#dfd5ea4a400dc5e421a889caf47e081ada78bb55" @@ -26442,7 +26381,7 @@ regenerator-runtime@0.13.9: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regenerator-runtime@^0.11.0, regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7, regenerator-runtime@^0.13.8: +regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7, regenerator-runtime@^0.13.8: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== From d6c52feae00cfc40023025a26e9ed2f6d7627a10 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 23 Oct 2024 12:33:17 +0100 Subject: [PATCH 03/19] chore: upgrade signature controller to remove message managers (#11911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Upgrade `@metamask/signature-controller` to version `20.1.0` to remove all usage of `metamask/message-managers`. Upgrade `@metamask/approval-controller` to version `7.1.0` and remove the unnecessary patch. Upgrade Node version from `20.12.2` to `20.14.0`. ## **Related issues** ## **Manual testing steps** Full regression of all signature functionality. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .nvmrc | 2 +- app/core/Engine.ts | 1 - app/util/test/initial-background-state.json | 1 + bitrise.yml | 2 +- package.json | 6 +- .../@metamask+approval-controller+7.0.2.patch | 404 ------------------ scripts/generate-attributions/package.json | 2 +- yarn.lock | 34 +- 8 files changed, 31 insertions(+), 421 deletions(-) delete mode 100644 patches/@metamask+approval-controller+7.0.2.patch diff --git a/.nvmrc b/.nvmrc index 87834047a6f..48b14e6b2b5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.12.2 +20.14.0 diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 89ede70f472..a5969f5edf3 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -506,7 +506,6 @@ class Engine { }; const approvalController = new ApprovalController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'ApprovalController', allowedEvents: [], diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 201cdd78024..87e02448df0 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -320,6 +320,7 @@ "domains": {} }, "SignatureController": { + "signatureRequests": {}, "unapprovedPersonalMsgs": {}, "unapprovedTypedMessages": {}, "unapprovedPersonalMsgCount": 0, diff --git a/bitrise.yml b/bitrise.yml index 1fc61f98351..070b13b5846 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1553,7 +1553,7 @@ app: NVM_SHA256SUM: '8e45fa547f428e9196a5613efad3bfa4d4608b74ca870f930090598f5af5f643' - opts: is_expand: false - NODE_VERSION: 20.12.2 + NODE_VERSION: 20.14.0 - opts: is_expand: false YARN_VERSION: 1.22.22 diff --git a/package.json b/package.json index c474cba0370..ac835169e01 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "@ledgerhq/react-native-hw-transport-ble": "^6.33.2", "@metamask/accounts-controller": "^18.2.1", "@metamask/address-book-controller": "^6.0.1", - "@metamask/approval-controller": "^7.0.1", + "@metamask/approval-controller": "^7.1.0", "@metamask/assets-controllers": "^35.0.0", "@metamask/base-controller": "^7.0.1", "@metamask/composable-controller": "^3.0.0", @@ -179,7 +179,7 @@ "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", "@metamask/selected-network-controller": "^15.0.2", - "@metamask/signature-controller": "^19.1.0", + "@metamask/signature-controller": "^20.1.0", "@metamask/slip44": "3.1.0", "@metamask/smart-transactions-controller": "^13.0.0", "@metamask/snaps-controllers": "^9.8.0", @@ -532,7 +532,7 @@ "fs": "react-native-level-fs" }, "engines": { - "node": "^20.12.2", + "node": "^20.14.0", "yarn": "^1.22.22" }, "lavamoat": { diff --git a/patches/@metamask+approval-controller+7.0.2.patch b/patches/@metamask+approval-controller+7.0.2.patch deleted file mode 100644 index dd655204a82..00000000000 --- a/patches/@metamask+approval-controller+7.0.2.patch +++ /dev/null @@ -1,404 +0,0 @@ -diff --git a/node_modules/@metamask/approval-controller/dist/.patch.txt b/node_modules/@metamask/approval-controller/dist/.patch.txt -new file mode 100644 -index 0000000..1c8ef8f ---- /dev/null -+++ b/node_modules/@metamask/approval-controller/dist/.patch.txt -@@ -0,0 +1,7 @@ -+PATCH GENERATED FROM MetaMask/core branch: patch/mobile-approval-controller-7-0-1 -+This patch backports various transaction controller features from the main branch of MetaMask/core -+Steps to update patch: -+* Create a new core branch from: patch/mobile-approval-controller-7-0-1 -+* Run "yarn build" in the core monorepo -+* Run "yarn patch:approval " in the mobile repo -+* Once the new patch is merged, add your changes to: patch/mobile-approval-controller-7-0-1 -diff --git a/node_modules/@metamask/approval-controller/dist/ApprovalController.js b/node_modules/@metamask/approval-controller/dist/ApprovalController.js -index 21178e9..db8fdcf 100644 ---- a/node_modules/@metamask/approval-controller/dist/ApprovalController.js -+++ b/node_modules/@metamask/approval-controller/dist/ApprovalController.js -@@ -4,7 +4,7 @@ - - - --var _chunkPMXPCCKSjs = require('./chunk-PMXPCCKS.js'); -+var _chunkNONDXCHJjs = require('./chunk-NONDXCHJ.js'); - require('./chunk-LKCXZAKD.js'); - - -@@ -12,5 +12,5 @@ require('./chunk-LKCXZAKD.js'); - - - --exports.APPROVAL_TYPE_RESULT_ERROR = _chunkPMXPCCKSjs.APPROVAL_TYPE_RESULT_ERROR; exports.APPROVAL_TYPE_RESULT_SUCCESS = _chunkPMXPCCKSjs.APPROVAL_TYPE_RESULT_SUCCESS; exports.ApprovalController = _chunkPMXPCCKSjs.ApprovalController; exports.ORIGIN_METAMASK = _chunkPMXPCCKSjs.ORIGIN_METAMASK; exports.default = _chunkPMXPCCKSjs.ApprovalController_default; -+exports.APPROVAL_TYPE_RESULT_ERROR = _chunkNONDXCHJjs.APPROVAL_TYPE_RESULT_ERROR; exports.APPROVAL_TYPE_RESULT_SUCCESS = _chunkNONDXCHJjs.APPROVAL_TYPE_RESULT_SUCCESS; exports.ApprovalController = _chunkNONDXCHJjs.ApprovalController; exports.ORIGIN_METAMASK = _chunkNONDXCHJjs.ORIGIN_METAMASK; exports.default = _chunkNONDXCHJjs.ApprovalController_default; - //# sourceMappingURL=ApprovalController.js.map -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/ApprovalController.js.map b/node_modules/@metamask/approval-controller/dist/ApprovalController.js.map -deleted file mode 100644 -index a464c67..0000000 ---- a/node_modules/@metamask/approval-controller/dist/ApprovalController.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":[],"names":[],"mappings":""} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs b/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs -index a6f0950..664ec6b 100644 ---- a/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs -+++ b/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs -@@ -4,7 +4,7 @@ import { - ApprovalController, - ApprovalController_default, - ORIGIN_METAMASK --} from "./chunk-PIJZDVKC.mjs"; -+} from "./chunk-CZANKQ6E.mjs"; - import "./chunk-C4VZRQ2J.mjs"; - export { - APPROVAL_TYPE_RESULT_ERROR, -diff --git a/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs.map b/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs.map -deleted file mode 100644 -index 84c51b2..0000000 ---- a/node_modules/@metamask/approval-controller/dist/ApprovalController.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/chunk-C4VZRQ2J.mjs.map b/node_modules/@metamask/approval-controller/dist/chunk-C4VZRQ2J.mjs.map -deleted file mode 100644 -index 0df9138..0000000 ---- a/node_modules/@metamask/approval-controller/dist/chunk-C4VZRQ2J.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["export class ApprovalRequestNotFoundError extends Error {\n constructor(id: string) {\n super(`Approval request with id '${id}' not found.`);\n }\n}\n\nexport class ApprovalRequestNoResultSupportError extends Error {\n constructor(id: string) {\n super(\n `Approval acceptance requested result but request with id '${id}' does not support it.`,\n );\n }\n}\n\nexport class NoApprovalFlowsError extends Error {\n constructor() {\n super(`No approval flows found.`);\n }\n}\n\nexport class EndInvalidFlowError extends Error {\n constructor(id: string, flowIds: string[]) {\n super(\n `Attempted to end flow with id '${id}' which does not match current flow with id '${\n flowIds.slice(-1)[0]\n }'. All Flows: ${flowIds.join(', ')}`,\n );\n }\n}\n\nexport class MissingApprovalFlowError extends Error {\n constructor(id: string) {\n super(`No approval flows found with id '${id}'.`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAO,IAAM,+BAAN,cAA2C,MAAM;AAAA,EACtD,YAAY,IAAY;AACtB,UAAM,6BAA6B,EAAE,cAAc;AAAA,EACrD;AACF;AAEO,IAAM,sCAAN,cAAkD,MAAM;AAAA,EAC7D,YAAY,IAAY;AACtB;AAAA,MACE,6DAA6D,EAAE;AAAA,IACjE;AAAA,EACF;AACF;AAEO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,cAAc;AACZ,UAAM,0BAA0B;AAAA,EAClC;AACF;AAEO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YAAY,IAAY,SAAmB;AACzC;AAAA,MACE,kCAAkC,EAAE,gDAClC,QAAQ,MAAM,EAAE,EAAE,CAAC,CACrB,iBAAiB,QAAQ,KAAK,IAAI,CAAC;AAAA,IACrC;AAAA,EACF;AACF;AAEO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YAAY,IAAY;AACtB,UAAM,oCAAoC,EAAE,IAAI;AAAA,EAClD;AACF;","names":[]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/chunk-PIJZDVKC.mjs b/node_modules/@metamask/approval-controller/dist/chunk-CZANKQ6E.mjs -similarity index 94% -rename from node_modules/@metamask/approval-controller/dist/chunk-PIJZDVKC.mjs -rename to node_modules/@metamask/approval-controller/dist/chunk-CZANKQ6E.mjs -index 57f7b7b..573794d 100644 ---- a/node_modules/@metamask/approval-controller/dist/chunk-PIJZDVKC.mjs -+++ b/node_modules/@metamask/approval-controller/dist/chunk-CZANKQ6E.mjs -@@ -33,7 +33,7 @@ var getDefaultState = () => { - approvalFlows: [] - }; - }; --var _approvals, _origins, _showApprovalRequest, _typesExcludedFromRateLimiting, _add, add_fn, _validateAddParams, validateAddParams_fn, _addPendingApprovalOrigin, addPendingApprovalOrigin_fn, _addToStore, addToStore_fn, _delete, delete_fn, _deleteApprovalAndGetCallbacks, deleteApprovalAndGetCallbacks_fn, _result, result_fn; -+var _approvals, _origins, _showApprovalRequest, _typesExcludedFromRateLimiting, _add, add_fn, _validateAddParams, validateAddParams_fn, _addPendingApprovalOrigin, addPendingApprovalOrigin_fn, _addToStore, addToStore_fn, _delete, delete_fn, _getCallbacks, getCallbacks_fn, _result, result_fn; - var ApprovalController = class extends BaseController { - /** - * Construct an Approval controller. -@@ -100,23 +100,15 @@ var ApprovalController = class extends BaseController { - */ - __privateAdd(this, _addToStore); - /** -- * Deletes the approval with the given id. The approval promise must be -- * resolved or reject before this method is called. -+ * Deletes the approval with the given id. -+ * - * Deletion is an internal operation because approval state is solely - * managed by this controller. - * - * @param id - The id of the approval request to be deleted. - */ - __privateAdd(this, _delete); -- /** -- * Gets the approval callbacks for the given id, deletes the entry, and then -- * returns the callbacks for promise resolution. -- * Throws an error if no approval is found for the given id. -- * -- * @param id - The id of the approval request. -- * @returns The promise callbacks associated with the approval request. -- */ -- __privateAdd(this, _deleteApprovalAndGetCallbacks); -+ __privateAdd(this, _getCallbacks); - __privateAdd(this, _result); - __privateAdd(this, _approvals, void 0); - __privateAdd(this, _origins, void 0); -@@ -306,7 +298,12 @@ var ApprovalController = class extends BaseController { - */ - accept(id, value, options) { - const approval = this.get(id); -- const requestPromise = __privateMethod(this, _deleteApprovalAndGetCallbacks, deleteApprovalAndGetCallbacks_fn).call(this, id); -+ const requestPromise = __privateMethod(this, _getCallbacks, getCallbacks_fn).call(this, id); -+ let requestDeleted = false; -+ if (!options?.deleteAfterResult || !options.waitForResult) { -+ __privateMethod(this, _delete, delete_fn).call(this, id); -+ requestDeleted = true; -+ } - return new Promise((resolve, reject) => { - const resultCallbacks = { - success: (acceptValue) => resolve({ value: acceptValue }), -@@ -322,6 +319,10 @@ var ApprovalController = class extends BaseController { - if (!options?.waitForResult) { - resolve({ value: void 0 }); - } -+ }).finally(() => { -+ if (!requestDeleted) { -+ __privateMethod(this, _delete, delete_fn).call(this, id); -+ } - }); - } - /** -@@ -332,7 +333,9 @@ var ApprovalController = class extends BaseController { - * @param error - The error to reject the approval promise with. - */ - reject(id, error) { -- __privateMethod(this, _deleteApprovalAndGetCallbacks, deleteApprovalAndGetCallbacks_fn).call(this, id).reject(error); -+ const callbacks = __privateMethod(this, _getCallbacks, getCallbacks_fn).call(this, id); -+ __privateMethod(this, _delete, delete_fn).call(this, id); -+ callbacks.reject(error); - } - /** - * Rejects and deletes all approval requests. -@@ -533,6 +536,9 @@ addToStore_fn = function(id, origin, type, requestData, requestState, expectsRes - }; - _delete = new WeakSet(); - delete_fn = function(id) { -+ if (!__privateGet(this, _approvals).has(id)) { -+ throw new ApprovalRequestNotFoundError(id); -+ } - __privateGet(this, _approvals).delete(id); - const { origin, type } = this.state.pendingApprovals[id]; - const originMap = __privateGet(this, _origins).get(origin); -@@ -550,13 +556,12 @@ delete_fn = function(id) { - ).length; - }); - }; --_deleteApprovalAndGetCallbacks = new WeakSet(); --deleteApprovalAndGetCallbacks_fn = function(id) { -+_getCallbacks = new WeakSet(); -+getCallbacks_fn = function(id) { - const callbacks = __privateGet(this, _approvals).get(id); - if (!callbacks) { - throw new ApprovalRequestNotFoundError(id); - } -- __privateMethod(this, _delete, delete_fn).call(this, id); - return callbacks; - }; - _result = new WeakSet(); -@@ -588,4 +593,4 @@ export { - ApprovalController, - ApprovalController_default - }; --//# sourceMappingURL=chunk-PIJZDVKC.mjs.map -\ No newline at end of file -+//# sourceMappingURL=chunk-CZANKQ6E.mjs.map -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/chunk-LKCXZAKD.js.map b/node_modules/@metamask/approval-controller/dist/chunk-LKCXZAKD.js.map -deleted file mode 100644 -index 4834941..0000000 ---- a/node_modules/@metamask/approval-controller/dist/chunk-LKCXZAKD.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/errors.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAO,IAAM,+BAAN,cAA2C,MAAM;AAAA,EACtD,YAAY,IAAY;AACtB,UAAM,6BAA6B,EAAE,cAAc;AAAA,EACrD;AACF;AAEO,IAAM,sCAAN,cAAkD,MAAM;AAAA,EAC7D,YAAY,IAAY;AACtB;AAAA,MACE,6DAA6D,EAAE;AAAA,IACjE;AAAA,EACF;AACF;AAEO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,cAAc;AACZ,UAAM,0BAA0B;AAAA,EAClC;AACF;AAEO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YAAY,IAAY,SAAmB;AACzC;AAAA,MACE,kCAAkC,EAAE,gDAClC,QAAQ,MAAM,EAAE,EAAE,CAAC,CACrB,iBAAiB,QAAQ,KAAK,IAAI,CAAC;AAAA,IACrC;AAAA,EACF;AACF;AAEO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YAAY,IAAY;AACtB,UAAM,oCAAoC,EAAE,IAAI;AAAA,EAClD;AACF","sourcesContent":["export class ApprovalRequestNotFoundError extends Error {\n constructor(id: string) {\n super(`Approval request with id '${id}' not found.`);\n }\n}\n\nexport class ApprovalRequestNoResultSupportError extends Error {\n constructor(id: string) {\n super(\n `Approval acceptance requested result but request with id '${id}' does not support it.`,\n );\n }\n}\n\nexport class NoApprovalFlowsError extends Error {\n constructor() {\n super(`No approval flows found.`);\n }\n}\n\nexport class EndInvalidFlowError extends Error {\n constructor(id: string, flowIds: string[]) {\n super(\n `Attempted to end flow with id '${id}' which does not match current flow with id '${\n flowIds.slice(-1)[0]\n }'. All Flows: ${flowIds.join(', ')}`,\n );\n }\n}\n\nexport class MissingApprovalFlowError extends Error {\n constructor(id: string) {\n super(`No approval flows found with id '${id}'.`);\n }\n}\n"]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/chunk-PMXPCCKS.js b/node_modules/@metamask/approval-controller/dist/chunk-NONDXCHJ.js -similarity index 94% -rename from node_modules/@metamask/approval-controller/dist/chunk-PMXPCCKS.js -rename to node_modules/@metamask/approval-controller/dist/chunk-NONDXCHJ.js -index d840c27..42b4765 100644 ---- a/node_modules/@metamask/approval-controller/dist/chunk-PMXPCCKS.js -+++ b/node_modules/@metamask/approval-controller/dist/chunk-NONDXCHJ.js -@@ -33,7 +33,7 @@ var getDefaultState = () => { - approvalFlows: [] - }; - }; --var _approvals, _origins, _showApprovalRequest, _typesExcludedFromRateLimiting, _add, add_fn, _validateAddParams, validateAddParams_fn, _addPendingApprovalOrigin, addPendingApprovalOrigin_fn, _addToStore, addToStore_fn, _delete, delete_fn, _deleteApprovalAndGetCallbacks, deleteApprovalAndGetCallbacks_fn, _result, result_fn; -+var _approvals, _origins, _showApprovalRequest, _typesExcludedFromRateLimiting, _add, add_fn, _validateAddParams, validateAddParams_fn, _addPendingApprovalOrigin, addPendingApprovalOrigin_fn, _addToStore, addToStore_fn, _delete, delete_fn, _getCallbacks, getCallbacks_fn, _result, result_fn; - var ApprovalController = class extends _basecontroller.BaseController { - /** - * Construct an Approval controller. -@@ -100,23 +100,15 @@ var ApprovalController = class extends _basecontroller.BaseController { - */ - _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _addToStore); - /** -- * Deletes the approval with the given id. The approval promise must be -- * resolved or reject before this method is called. -+ * Deletes the approval with the given id. -+ * - * Deletion is an internal operation because approval state is solely - * managed by this controller. - * - * @param id - The id of the approval request to be deleted. - */ - _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _delete); -- /** -- * Gets the approval callbacks for the given id, deletes the entry, and then -- * returns the callbacks for promise resolution. -- * Throws an error if no approval is found for the given id. -- * -- * @param id - The id of the approval request. -- * @returns The promise callbacks associated with the approval request. -- */ -- _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _deleteApprovalAndGetCallbacks); -+ _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _getCallbacks); - _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _result); - _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _approvals, void 0); - _chunkLKCXZAKDjs.__privateAdd.call(void 0, this, _origins, void 0); -@@ -306,7 +298,12 @@ var ApprovalController = class extends _basecontroller.BaseController { - */ - accept(id, value, options) { - const approval = this.get(id); -- const requestPromise = _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _deleteApprovalAndGetCallbacks, deleteApprovalAndGetCallbacks_fn).call(this, id); -+ const requestPromise = _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _getCallbacks, getCallbacks_fn).call(this, id); -+ let requestDeleted = false; -+ if (!options?.deleteAfterResult || !options.waitForResult) { -+ _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _delete, delete_fn).call(this, id); -+ requestDeleted = true; -+ } - return new Promise((resolve, reject) => { - const resultCallbacks = { - success: (acceptValue) => resolve({ value: acceptValue }), -@@ -322,6 +319,10 @@ var ApprovalController = class extends _basecontroller.BaseController { - if (!options?.waitForResult) { - resolve({ value: void 0 }); - } -+ }).finally(() => { -+ if (!requestDeleted) { -+ _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _delete, delete_fn).call(this, id); -+ } - }); - } - /** -@@ -332,7 +333,9 @@ var ApprovalController = class extends _basecontroller.BaseController { - * @param error - The error to reject the approval promise with. - */ - reject(id, error) { -- _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _deleteApprovalAndGetCallbacks, deleteApprovalAndGetCallbacks_fn).call(this, id).reject(error); -+ const callbacks = _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _getCallbacks, getCallbacks_fn).call(this, id); -+ _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _delete, delete_fn).call(this, id); -+ callbacks.reject(error); - } - /** - * Rejects and deletes all approval requests. -@@ -533,6 +536,9 @@ addToStore_fn = function(id, origin, type, requestData, requestState, expectsRes - }; - _delete = new WeakSet(); - delete_fn = function(id) { -+ if (!_chunkLKCXZAKDjs.__privateGet.call(void 0, this, _approvals).has(id)) { -+ throw new (0, _chunkLKCXZAKDjs.ApprovalRequestNotFoundError)(id); -+ } - _chunkLKCXZAKDjs.__privateGet.call(void 0, this, _approvals).delete(id); - const { origin, type } = this.state.pendingApprovals[id]; - const originMap = _chunkLKCXZAKDjs.__privateGet.call(void 0, this, _origins).get(origin); -@@ -550,13 +556,12 @@ delete_fn = function(id) { - ).length; - }); - }; --_deleteApprovalAndGetCallbacks = new WeakSet(); --deleteApprovalAndGetCallbacks_fn = function(id) { -+_getCallbacks = new WeakSet(); -+getCallbacks_fn = function(id) { - const callbacks = _chunkLKCXZAKDjs.__privateGet.call(void 0, this, _approvals).get(id); - if (!callbacks) { - throw new (0, _chunkLKCXZAKDjs.ApprovalRequestNotFoundError)(id); - } -- _chunkLKCXZAKDjs.__privateMethod.call(void 0, this, _delete, delete_fn).call(this, id); - return callbacks; - }; - _result = new WeakSet(); -@@ -588,4 +593,4 @@ var ApprovalController_default = ApprovalController; - - - exports.ORIGIN_METAMASK = ORIGIN_METAMASK; exports.APPROVAL_TYPE_RESULT_ERROR = APPROVAL_TYPE_RESULT_ERROR; exports.APPROVAL_TYPE_RESULT_SUCCESS = APPROVAL_TYPE_RESULT_SUCCESS; exports.ApprovalController = ApprovalController; exports.ApprovalController_default = ApprovalController_default; --//# sourceMappingURL=chunk-PMXPCCKS.js.map -\ No newline at end of file -+//# sourceMappingURL=chunk-NONDXCHJ.js.map -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/chunk-PIJZDVKC.mjs.map b/node_modules/@metamask/approval-controller/dist/chunk-PIJZDVKC.mjs.map -deleted file mode 100644 -index 11b7edd..0000000 ---- a/node_modules/@metamask/approval-controller/dist/chunk-PIJZDVKC.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/ApprovalController.ts"],"sourcesContent":["import type { ControllerGetStateAction } from '@metamask/base-controller';\nimport {\n BaseController,\n type ControllerStateChangeEvent,\n type RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport type { JsonRpcError, DataWithOptionalCause } from '@metamask/rpc-errors';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { Json, OptionalField } from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport {\n ApprovalRequestNotFoundError,\n ApprovalRequestNoResultSupportError,\n EndInvalidFlowError,\n NoApprovalFlowsError,\n MissingApprovalFlowError,\n} from './errors';\n\n// Constants\n\n// Avoiding dependency on controller-utils\nexport const ORIGIN_METAMASK = 'metamask';\nexport const APPROVAL_TYPE_RESULT_ERROR = 'result_error';\nexport const APPROVAL_TYPE_RESULT_SUCCESS = 'result_success';\n\nconst controllerName = 'ApprovalController';\n\nconst stateMetadata = {\n pendingApprovals: { persist: false, anonymous: true },\n pendingApprovalCount: { persist: false, anonymous: false },\n approvalFlows: { persist: false, anonymous: false },\n};\n\nconst getAlreadyPendingMessage = (origin: string, type: string) =>\n `Request of type '${type}' already pending for origin ${origin}. Please wait.`;\n\nconst getDefaultState = (): ApprovalControllerState => {\n return {\n pendingApprovals: {},\n pendingApprovalCount: 0,\n approvalFlows: [],\n };\n};\n\n// Internal Types\n\ntype ApprovalPromiseResolve = (value?: unknown | AddResult) => void;\n\ntype ApprovalPromiseReject = (error?: unknown) => void;\n\ntype ApprovalRequestData = Record | null;\n\ntype ApprovalRequestState = Record | null;\n\ntype ApprovalCallbacks = {\n resolve: ApprovalPromiseResolve;\n reject: ApprovalPromiseReject;\n};\n\ntype ApprovalFlow = {\n id: string;\n loadingText: string | null;\n};\n\ntype ResultOptions = {\n flowToEnd?: string;\n header?: (string | ResultComponent)[];\n icon?: string | null;\n title?: string | null;\n};\n\n// Miscellaneous Types\n\nexport type ApprovalRequest = {\n /**\n * The ID of the approval request.\n */\n id: string;\n\n /**\n * The origin of the approval request.\n */\n origin: string;\n\n /**\n * The time that the request was received, per Date.now().\n */\n time: number;\n\n /**\n * The type of the approval request.\n * Unfortunately, not all values will match the `ApprovalType` enum, so we are using `string` here.\n * TODO: Replace `string` with `ApprovalType` when all `type` values used by the clients can be encompassed by the `ApprovalType` enum.\n */\n type: string;\n\n /**\n * Additional data associated with the request.\n */\n requestData: RequestData;\n\n /**\n * Additional mutable state associated with the request\n */\n requestState: ApprovalRequestState;\n\n /**\n * Whether the request expects a result object to be returned instead of just the approval value.\n */\n expectsResult: boolean;\n};\n\nexport type ApprovalFlowState = ApprovalFlow;\n\nexport type ApprovalControllerState = {\n pendingApprovals: Record>>;\n pendingApprovalCount: number;\n approvalFlows: ApprovalFlowState[];\n};\n\nexport type ApprovalControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n ApprovalControllerActions,\n ApprovalControllerEvents,\n never,\n never\n>;\n\n// Option Types\n\nexport type ShowApprovalRequest = () => void | Promise;\n\nexport type ResultComponent = {\n /**\n * A unique identifier for this instance of the component.\n */\n key: string;\n\n /**\n * The name of the component to render.\n */\n name: string;\n\n /**\n * Any properties required by the component.\n */\n properties?: Record;\n\n /**\n * Any child components to render inside the component.\n */\n children?: string | ResultComponent | (string | ResultComponent)[];\n};\n\nexport type ApprovalControllerOptions = {\n messenger: ApprovalControllerMessenger;\n showApprovalRequest: ShowApprovalRequest;\n state?: Partial;\n typesExcludedFromRateLimiting?: string[];\n};\n\nexport type AddApprovalOptions = {\n id?: string;\n origin: string;\n type: string;\n requestData?: Record;\n requestState?: Record;\n expectsResult?: boolean;\n};\n\nexport type UpdateRequestStateOptions = {\n id: string;\n requestState: Record;\n};\n\nexport type AcceptOptions = {\n /**\n * Whether to resolve the returned promise only when the request creator indicates the success of the\n * post-approval logic using the result callbacks.\n * If false or unspecified, the promise will resolve immediately.\n */\n waitForResult?: boolean;\n};\n\nexport type StartFlowOptions = OptionalField<\n ApprovalFlow,\n 'id' | 'loadingText'\n> & { show?: boolean };\n\nexport type EndFlowOptions = Pick;\n\nexport type SetFlowLoadingTextOptions = ApprovalFlow;\n\nexport type SuccessOptions = ResultOptions & {\n message?: string | ResultComponent | (string | ResultComponent)[];\n};\n\nexport type ErrorOptions = ResultOptions & {\n error?: string | ResultComponent | (string | ResultComponent)[];\n};\n\n// Result Types\n\nexport type AcceptResultCallbacks = {\n /**\n * Inform the request acceptor that the post-approval logic was successful.\n *\n * @param value - An optional value generated by the post-approval logic.\n */\n success: (value?: unknown) => void;\n\n /**\n * Inform the request acceptor that the post-approval logic failed.\n *\n * @param error - The reason for the failure.\n */\n error: (error: Error) => void;\n};\n\nexport type AddResult = {\n /**\n * An optional value provided by the request acceptor.\n */\n value?: unknown;\n\n /**\n * Callback functions that must be used to indicate to the request acceptor whether the post-approval logic was successful or not.\n * Will be undefined if the request acceptor did not specify that they want to wait for a result.\n */\n resultCallbacks?: AcceptResultCallbacks;\n};\n\nexport type AcceptResult = {\n /**\n * An optional value provided by the request creator when indicating a successful result.\n */\n value?: unknown;\n};\n\nexport type ApprovalFlowStartResult = ApprovalFlow;\n\nexport type SuccessResult = Record;\n\nexport type ErrorResult = Record;\n\n// Event Types\n\nexport type ApprovalStateChange = ControllerStateChangeEvent<\n typeof controllerName,\n ApprovalControllerState\n>;\n\nexport type ApprovalControllerEvents = ApprovalStateChange;\n\n// Action Types\n\nexport type GetApprovalsState = ControllerGetStateAction<\n typeof controllerName,\n ApprovalControllerState\n>;\n\nexport type ClearApprovalRequests = {\n type: `${typeof controllerName}:clearRequests`;\n handler: (error: JsonRpcError) => void;\n};\n\nexport type AddApprovalRequest = {\n type: `${typeof controllerName}:addRequest`;\n handler: (\n opts: AddApprovalOptions,\n shouldShowRequest: boolean,\n ) => ReturnType;\n};\n\nexport type HasApprovalRequest = {\n type: `${typeof controllerName}:hasRequest`;\n handler: ApprovalController['has'];\n};\n\nexport type AcceptRequest = {\n type: `${typeof controllerName}:acceptRequest`;\n handler: ApprovalController['accept'];\n};\n\nexport type RejectRequest = {\n type: `${typeof controllerName}:rejectRequest`;\n handler: ApprovalController['reject'];\n};\n\nexport type UpdateRequestState = {\n type: `${typeof controllerName}:updateRequestState`;\n handler: ApprovalController['updateRequestState'];\n};\n\nexport type StartFlow = {\n type: `${typeof controllerName}:startFlow`;\n handler: ApprovalController['startFlow'];\n};\n\nexport type EndFlow = {\n type: `${typeof controllerName}:endFlow`;\n handler: ApprovalController['endFlow'];\n};\n\nexport type SetFlowLoadingText = {\n type: `${typeof controllerName}:setFlowLoadingText`;\n handler: ApprovalController['setFlowLoadingText'];\n};\n\nexport type ShowSuccess = {\n type: `${typeof controllerName}:showSuccess`;\n handler: ApprovalController['success'];\n};\n\nexport type ShowError = {\n type: `${typeof controllerName}:showError`;\n handler: ApprovalController['error'];\n};\n\nexport type ApprovalControllerActions =\n | GetApprovalsState\n | ClearApprovalRequests\n | AddApprovalRequest\n | HasApprovalRequest\n | AcceptRequest\n | RejectRequest\n | UpdateRequestState\n | StartFlow\n | EndFlow\n | SetFlowLoadingText\n | ShowSuccess\n | ShowError;\n\n/**\n * Controller for managing requests that require user approval.\n *\n * Enables limiting the number of pending requests by origin and type, counting\n * pending requests, and more.\n *\n * Adding a request returns a promise that resolves or rejects when the request\n * is approved or denied, respectively.\n */\nexport class ApprovalController extends BaseController<\n typeof controllerName,\n ApprovalControllerState,\n ApprovalControllerMessenger\n> {\n #approvals: Map;\n\n #origins: Map>;\n\n #showApprovalRequest: () => void;\n\n #typesExcludedFromRateLimiting: string[];\n\n /**\n * Construct an Approval controller.\n *\n * @param options - The controller options.\n * @param options.showApprovalRequest - Function for opening the UI such that\n * the request can be displayed to the user.\n * @param options.messenger - The restricted controller messenger for the Approval controller.\n * @param options.state - The initial controller state.\n * @param options.typesExcludedFromRateLimiting - Array of approval types which allow multiple pending approval requests from the same origin.\n */\n constructor({\n messenger,\n showApprovalRequest,\n state = {},\n typesExcludedFromRateLimiting = [],\n }: ApprovalControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state: { ...getDefaultState(), ...state },\n });\n\n this.#approvals = new Map();\n this.#origins = new Map();\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#showApprovalRequest = showApprovalRequest;\n this.#typesExcludedFromRateLimiting = typesExcludedFromRateLimiting;\n this.registerMessageHandlers();\n }\n\n /**\n * Constructor helper for registering this controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:clearRequests` as const,\n this.clear.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:addRequest` as const,\n (opts: AddApprovalOptions, shouldShowRequest: boolean) => {\n if (shouldShowRequest) {\n return this.addAndShowApprovalRequest(opts);\n }\n return this.add(opts);\n },\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasRequest` as const,\n this.has.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:acceptRequest` as const,\n this.accept.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:rejectRequest` as const,\n this.reject.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:updateRequestState` as const,\n this.updateRequestState.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:startFlow` as const,\n this.startFlow.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:endFlow` as const,\n this.endFlow.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:setFlowLoadingText` as const,\n this.setFlowLoadingText.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:showSuccess` as const,\n this.success.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:showError` as const,\n this.error.bind(this),\n );\n }\n\n /**\n * Adds an approval request per the given arguments, calls the show approval\n * request function, and returns the associated approval promise resolving to\n * an AddResult object.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * @param opts.requestState - Additional state associated with the request,\n * if any.\n * @returns The approval promise resolving to an AddResult object.\n */\n addAndShowApprovalRequest(\n opts: AddApprovalOptions & { expectsResult: true },\n ): Promise;\n\n /**\n * Adds an approval request per the given arguments, calls the show approval\n * request function, and returns the associated approval promise resolving\n * to a value provided during acceptance.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * @param opts.requestState - Additional state associated with the request,\n * if any.\n * @returns The approval promise resolving to a value provided during acceptance.\n */\n addAndShowApprovalRequest(opts: AddApprovalOptions): Promise;\n\n addAndShowApprovalRequest(opts: AddApprovalOptions): Promise {\n const promise = this.#add(\n opts.origin,\n opts.type,\n opts.id,\n opts.requestData,\n opts.requestState,\n opts.expectsResult,\n );\n this.#showApprovalRequest();\n return promise;\n }\n\n /**\n * Adds an approval request per the given arguments and returns the approval\n * promise resolving to an AddResult object.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise resolving to an AddResult object.\n */\n add(opts: AddApprovalOptions & { expectsResult: true }): Promise;\n\n /**\n * Adds an approval request per the given arguments and returns the approval\n * promise resolving to a value provided during acceptance.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise resolving to a value provided during acceptance.\n */\n add(opts: AddApprovalOptions): Promise;\n\n add(opts: AddApprovalOptions): Promise {\n return this.#add(\n opts.origin,\n opts.type,\n opts.id,\n opts.requestData,\n opts.requestState,\n opts.expectsResult,\n );\n }\n\n /**\n * Gets the info for the approval request with the given id.\n *\n * @param id - The id of the approval request.\n * @returns The approval request data associated with the id.\n */\n get(id: string): ApprovalRequest | undefined {\n return this.state.pendingApprovals[id];\n }\n\n /**\n * Gets the number of pending approvals, by origin and/or type.\n *\n * If only `origin` is specified, all approvals for that origin will be\n * counted, regardless of type.\n * If only `type` is specified, all approvals for that type will be counted,\n * regardless of origin.\n * If both `origin` and `type` are specified, 0 or 1 will be returned.\n *\n * @param opts - The approval count options.\n * @param opts.origin - An approval origin.\n * @param opts.type - The type of the approval request.\n * @returns The current approval request count for the given origin and/or\n * type.\n */\n getApprovalCount(opts: { origin?: string; type?: string } = {}): number {\n if (!opts.origin && !opts.type) {\n throw new Error('Must specify origin, type, or both.');\n }\n const { origin, type: _type } = opts;\n\n if (origin && _type) {\n return this.#origins.get(origin)?.get(_type) || 0;\n }\n\n if (origin) {\n return Array.from(\n (this.#origins.get(origin) || new Map()).values(),\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n ).reduce((total, value) => total + value, 0);\n }\n\n // Only \"type\" was specified\n let count = 0;\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n count += 1;\n }\n }\n return count;\n }\n\n /**\n * Get the total count of all pending approval requests for all origins.\n *\n * @returns The total pending approval request count.\n */\n getTotalApprovalCount(): number {\n return this.state.pendingApprovalCount;\n }\n\n /**\n * Checks if there's a pending approval request per the given parameters.\n * At least one parameter must be specified. An error will be thrown if the\n * parameters are invalid.\n *\n * If `id` is specified, all other parameters will be ignored.\n * If `id` is not specified, the method will check for requests that match\n * all of the specified parameters.\n *\n * @param opts - Options bag.\n * @param opts.id - The ID to check for.\n * @param opts.origin - The origin to check for.\n * @param opts.type - The type to check for.\n * @returns `true` if a matching approval is found, and `false` otherwise.\n */\n has(opts: { id?: string; origin?: string; type?: string } = {}): boolean {\n const { id, origin, type: _type } = opts;\n\n if (id) {\n if (typeof id !== 'string') {\n throw new Error('May not specify non-string id.');\n }\n return this.#approvals.has(id);\n }\n\n if (_type && typeof _type !== 'string') {\n throw new Error('May not specify non-string type.');\n }\n\n if (origin) {\n if (typeof origin !== 'string') {\n throw new Error('May not specify non-string origin.');\n }\n\n // Check origin and type pair if type also specified\n if (_type) {\n return Boolean(this.#origins.get(origin)?.get(_type));\n }\n return this.#origins.has(origin);\n }\n\n if (_type) {\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n return true;\n }\n }\n return false;\n }\n throw new Error(\n 'Must specify a valid combination of id, origin, and type.',\n );\n }\n\n /**\n * Resolves the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param value - The value to resolve the approval promise with.\n * @param options - Options bag.\n * @returns A promise that either resolves once a result is provided by\n * the creator of the approval request, or immediately if `options.waitForResult`\n * is `false` or `undefined`.\n */\n accept(\n id: string,\n value?: unknown,\n options?: AcceptOptions,\n ): Promise {\n // Safe to cast as the delete method below will throw if the ID is not found\n const approval = this.get(id) as ApprovalRequest;\n const requestPromise = this.#deleteApprovalAndGetCallbacks(id);\n\n return new Promise((resolve, reject) => {\n const resultCallbacks: AcceptResultCallbacks = {\n success: (acceptValue?: unknown) => resolve({ value: acceptValue }),\n error: reject,\n };\n\n if (options?.waitForResult && !approval.expectsResult) {\n reject(new ApprovalRequestNoResultSupportError(id));\n return;\n }\n\n const resultValue = options?.waitForResult ? resultCallbacks : undefined;\n\n const resolveValue = approval.expectsResult\n ? { value, resultCallbacks: resultValue }\n : value;\n\n requestPromise.resolve(resolveValue);\n\n if (!options?.waitForResult) {\n resolve({ value: undefined });\n }\n });\n }\n\n /**\n * Rejects the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param error - The error to reject the approval promise with.\n */\n reject(id: string, error: unknown): void {\n this.#deleteApprovalAndGetCallbacks(id).reject(error);\n }\n\n /**\n * Rejects and deletes all approval requests.\n *\n * @param rejectionError - The JsonRpcError to reject the approval\n * requests with.\n */\n clear(rejectionError: JsonRpcError): void {\n for (const id of this.#approvals.keys()) {\n this.reject(id, rejectionError);\n }\n this.#origins.clear();\n this.update((draftState) => {\n draftState.pendingApprovals = {};\n draftState.pendingApprovalCount = 0;\n });\n }\n\n /**\n * Updates the request state of the approval with the given id.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request.\n * @param opts.requestState - Additional data associated with the request\n */\n updateRequestState(opts: UpdateRequestStateOptions): void {\n if (!this.state.pendingApprovals[opts.id]) {\n throw new ApprovalRequestNotFoundError(opts.id);\n }\n\n this.update((draftState) => {\n draftState.pendingApprovals[opts.id].requestState =\n opts.requestState as never;\n });\n }\n\n /**\n * Starts a new approval flow.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval flow.\n * @param opts.loadingText - The loading text that will be associated to the approval flow.\n * @param opts.show - A flag to determine whether the approval should show to the user.\n * @returns The object containing the approval flow id.\n */\n startFlow(opts: StartFlowOptions = {}): ApprovalFlowStartResult {\n const id = opts.id ?? nanoid();\n const loadingText = opts.loadingText ?? null;\n\n this.update((draftState) => {\n draftState.approvalFlows.push({ id, loadingText });\n });\n\n // By default, if nothing else is specified, we always show the approval.\n if (opts.show !== false) {\n this.#showApprovalRequest();\n }\n\n return { id, loadingText };\n }\n\n /**\n * Ends the current approval flow.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval flow that will be finished.\n */\n endFlow({ id }: EndFlowOptions) {\n if (!this.state.approvalFlows.length) {\n throw new NoApprovalFlowsError();\n }\n\n const currentFlow = this.state.approvalFlows.slice(-1)[0];\n\n if (id !== currentFlow.id) {\n throw new EndInvalidFlowError(\n id,\n this.state.approvalFlows.map((flow) => flow.id),\n );\n }\n\n this.update((draftState) => {\n draftState.approvalFlows.pop();\n });\n }\n\n /**\n * Sets the loading text for the approval flow.\n *\n * @param opts - Options bag.\n * @param opts.id - The approval flow loading text that will be displayed.\n * @param opts.loadingText - The loading text that will be associated to the approval flow.\n */\n setFlowLoadingText({ id, loadingText }: SetFlowLoadingTextOptions) {\n const flowIndex = this.state.approvalFlows.findIndex(\n (flow) => flow.id === id,\n );\n\n if (flowIndex === -1) {\n throw new MissingApprovalFlowError(id);\n }\n\n this.update((draftState) => {\n draftState.approvalFlows[flowIndex].loadingText = loadingText;\n });\n }\n\n /**\n * Show a success page.\n *\n * @param opts - Options bag.\n * @param opts.message - The message text or components to display in the page.\n * @param opts.header - The text or components to display in the header of the page.\n * @param opts.flowToEnd - The ID of the approval flow to end once the success page is approved.\n * @param opts.title - The title to display above the message. Shown by default but can be hidden with `null`.\n * @param opts.icon - The icon to display in the page. Shown by default but can be hidden with `null`.\n * @returns Empty object to support future additions.\n */\n async success(opts: SuccessOptions = {}): Promise {\n await this.#result(APPROVAL_TYPE_RESULT_SUCCESS, opts, {\n message: opts.message,\n header: opts.header,\n title: opts.title,\n icon: opts.icon,\n } as Record);\n\n return {};\n }\n\n /**\n * Show an error page.\n *\n * @param opts - Options bag.\n * @param opts.message - The message text or components to display in the page.\n * @param opts.header - The text or components to display in the header of the page.\n * @param opts.flowToEnd - The ID of the approval flow to end once the error page is approved.\n * @param opts.title - The title to display above the message. Shown by default but can be hidden with `null`.\n * @param opts.icon - The icon to display in the page. Shown by default but can be hidden with `null`.\n * @returns Empty object to support future additions.\n */\n async error(opts: ErrorOptions = {}): Promise {\n await this.#result(APPROVAL_TYPE_RESULT_ERROR, opts, {\n error: opts.error,\n header: opts.header,\n title: opts.title,\n icon: opts.icon,\n } as Record);\n\n return {};\n }\n\n /**\n * Implementation of add operation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param id - The id of the approval request.\n * @param requestData - The request data associated with the approval request.\n * @param requestState - The request state associated with the approval request.\n * @param expectsResult - Whether the approval request expects a result object to be returned.\n * @returns The approval promise.\n */\n #add(\n origin: string,\n type: string,\n id: string = nanoid(),\n requestData?: Record,\n requestState?: Record,\n expectsResult?: boolean,\n ): Promise {\n this.#validateAddParams(id, origin, type, requestData, requestState);\n\n if (\n !this.#typesExcludedFromRateLimiting.includes(type) &&\n this.has({ origin, type })\n ) {\n throw rpcErrors.resourceUnavailable(\n getAlreadyPendingMessage(origin, type),\n );\n }\n\n // add pending approval\n return new Promise((resolve, reject) => {\n this.#approvals.set(id, { resolve, reject });\n this.#addPendingApprovalOrigin(origin, type);\n\n this.#addToStore(\n id,\n origin,\n type,\n requestData,\n requestState,\n expectsResult,\n );\n });\n }\n\n /**\n * Validates parameters to the add method.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n * @param requestState - The request state associated with the approval request.\n */\n #validateAddParams(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n requestState?: Record,\n ): void {\n let errorMessage = null;\n if (!id || typeof id !== 'string') {\n errorMessage = 'Must specify non-empty string id.';\n } else if (this.#approvals.has(id)) {\n errorMessage = `Approval request with id '${id}' already exists.`;\n } else if (!origin || typeof origin !== 'string') {\n errorMessage = 'Must specify non-empty string origin.';\n } else if (!type || typeof type !== 'string') {\n errorMessage = 'Must specify non-empty string type.';\n } else if (\n requestData &&\n (typeof requestData !== 'object' || Array.isArray(requestData))\n ) {\n errorMessage = 'Request data must be a plain object if specified.';\n } else if (\n requestState &&\n (typeof requestState !== 'object' || Array.isArray(requestState))\n ) {\n errorMessage = 'Request state must be a plain object if specified.';\n }\n\n if (errorMessage) {\n throw rpcErrors.internal(errorMessage);\n }\n }\n\n /**\n * Adds an entry to _origins.\n * Performs no validation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n */\n #addPendingApprovalOrigin(origin: string, type: string): void {\n let originMap = this.#origins.get(origin);\n\n if (!originMap) {\n originMap = new Map();\n this.#origins.set(origin, originMap);\n }\n\n const currentValue = originMap.get(type) || 0;\n originMap.set(type, currentValue + 1);\n }\n\n /**\n * Adds an entry to the store.\n * Performs no validation.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n * @param requestState - The request state associated with the approval request.\n * @param expectsResult - Whether the request expects a result object to be returned.\n */\n #addToStore(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n requestState?: Record,\n expectsResult?: boolean,\n ): void {\n const approval = {\n id,\n origin,\n type,\n time: Date.now(),\n requestData: requestData || null,\n requestState: requestState || null,\n expectsResult: expectsResult || false,\n };\n\n this.update((draftState) => {\n draftState.pendingApprovals[id] = approval as never;\n\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Deletes the approval with the given id. The approval promise must be\n * resolved or reject before this method is called.\n * Deletion is an internal operation because approval state is solely\n * managed by this controller.\n *\n * @param id - The id of the approval request to be deleted.\n */\n #delete(id: string): void {\n this.#approvals.delete(id);\n\n // This method is only called after verifying that the approval with the\n // specified id exists.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const { origin, type } = this.state.pendingApprovals[id]!;\n\n const originMap = this.#origins.get(origin) as Map;\n const originTotalCount = this.getApprovalCount({ origin });\n const originTypeCount = originMap.get(type) as number;\n\n if (originTotalCount === 1) {\n this.#origins.delete(origin);\n } else {\n originMap.set(type, originTypeCount - 1);\n }\n\n this.update((draftState) => {\n delete draftState.pendingApprovals[id];\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Gets the approval callbacks for the given id, deletes the entry, and then\n * returns the callbacks for promise resolution.\n * Throws an error if no approval is found for the given id.\n *\n * @param id - The id of the approval request.\n * @returns The promise callbacks associated with the approval request.\n */\n #deleteApprovalAndGetCallbacks(id: string): ApprovalCallbacks {\n const callbacks = this.#approvals.get(id);\n if (!callbacks) {\n throw new ApprovalRequestNotFoundError(id);\n }\n\n this.#delete(id);\n return callbacks;\n }\n\n async #result(\n type: string,\n opts: ResultOptions,\n requestData: Record,\n ) {\n try {\n await this.addAndShowApprovalRequest({\n origin: ORIGIN_METAMASK,\n type,\n requestData,\n });\n } catch (error) {\n console.info('Failed to display result page', error);\n } finally {\n if (opts.flowToEnd) {\n try {\n this.endFlow({ id: opts.flowToEnd });\n } catch (error) {\n console.info('Failed to end flow', { id: opts.flowToEnd, error });\n }\n }\n }\n }\n}\n\nexport default ApprovalController;\n"],"mappings":"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","names":[]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/chunk-PMXPCCKS.js.map b/node_modules/@metamask/approval-controller/dist/chunk-PMXPCCKS.js.map -deleted file mode 100644 -index 3bf157a..0000000 ---- a/node_modules/@metamask/approval-controller/dist/chunk-PMXPCCKS.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/ApprovalController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ControllerGetStateAction } from '@metamask/base-controller';\nimport {\n BaseController,\n type ControllerStateChangeEvent,\n type RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport type { JsonRpcError, DataWithOptionalCause } from '@metamask/rpc-errors';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { Json, OptionalField } from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport {\n ApprovalRequestNotFoundError,\n ApprovalRequestNoResultSupportError,\n EndInvalidFlowError,\n NoApprovalFlowsError,\n MissingApprovalFlowError,\n} from './errors';\n\n// Constants\n\n// Avoiding dependency on controller-utils\nexport const ORIGIN_METAMASK = 'metamask';\nexport const APPROVAL_TYPE_RESULT_ERROR = 'result_error';\nexport const APPROVAL_TYPE_RESULT_SUCCESS = 'result_success';\n\nconst controllerName = 'ApprovalController';\n\nconst stateMetadata = {\n pendingApprovals: { persist: false, anonymous: true },\n pendingApprovalCount: { persist: false, anonymous: false },\n approvalFlows: { persist: false, anonymous: false },\n};\n\nconst getAlreadyPendingMessage = (origin: string, type: string) =>\n `Request of type '${type}' already pending for origin ${origin}. Please wait.`;\n\nconst getDefaultState = (): ApprovalControllerState => {\n return {\n pendingApprovals: {},\n pendingApprovalCount: 0,\n approvalFlows: [],\n };\n};\n\n// Internal Types\n\ntype ApprovalPromiseResolve = (value?: unknown | AddResult) => void;\n\ntype ApprovalPromiseReject = (error?: unknown) => void;\n\ntype ApprovalRequestData = Record | null;\n\ntype ApprovalRequestState = Record | null;\n\ntype ApprovalCallbacks = {\n resolve: ApprovalPromiseResolve;\n reject: ApprovalPromiseReject;\n};\n\ntype ApprovalFlow = {\n id: string;\n loadingText: string | null;\n};\n\ntype ResultOptions = {\n flowToEnd?: string;\n header?: (string | ResultComponent)[];\n icon?: string | null;\n title?: string | null;\n};\n\n// Miscellaneous Types\n\nexport type ApprovalRequest = {\n /**\n * The ID of the approval request.\n */\n id: string;\n\n /**\n * The origin of the approval request.\n */\n origin: string;\n\n /**\n * The time that the request was received, per Date.now().\n */\n time: number;\n\n /**\n * The type of the approval request.\n * Unfortunately, not all values will match the `ApprovalType` enum, so we are using `string` here.\n * TODO: Replace `string` with `ApprovalType` when all `type` values used by the clients can be encompassed by the `ApprovalType` enum.\n */\n type: string;\n\n /**\n * Additional data associated with the request.\n */\n requestData: RequestData;\n\n /**\n * Additional mutable state associated with the request\n */\n requestState: ApprovalRequestState;\n\n /**\n * Whether the request expects a result object to be returned instead of just the approval value.\n */\n expectsResult: boolean;\n};\n\nexport type ApprovalFlowState = ApprovalFlow;\n\nexport type ApprovalControllerState = {\n pendingApprovals: Record>>;\n pendingApprovalCount: number;\n approvalFlows: ApprovalFlowState[];\n};\n\nexport type ApprovalControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n ApprovalControllerActions,\n ApprovalControllerEvents,\n never,\n never\n>;\n\n// Option Types\n\nexport type ShowApprovalRequest = () => void | Promise;\n\nexport type ResultComponent = {\n /**\n * A unique identifier for this instance of the component.\n */\n key: string;\n\n /**\n * The name of the component to render.\n */\n name: string;\n\n /**\n * Any properties required by the component.\n */\n properties?: Record;\n\n /**\n * Any child components to render inside the component.\n */\n children?: string | ResultComponent | (string | ResultComponent)[];\n};\n\nexport type ApprovalControllerOptions = {\n messenger: ApprovalControllerMessenger;\n showApprovalRequest: ShowApprovalRequest;\n state?: Partial;\n typesExcludedFromRateLimiting?: string[];\n};\n\nexport type AddApprovalOptions = {\n id?: string;\n origin: string;\n type: string;\n requestData?: Record;\n requestState?: Record;\n expectsResult?: boolean;\n};\n\nexport type UpdateRequestStateOptions = {\n id: string;\n requestState: Record;\n};\n\nexport type AcceptOptions = {\n /**\n * Whether to resolve the returned promise only when the request creator indicates the success of the\n * post-approval logic using the result callbacks.\n * If false or unspecified, the promise will resolve immediately.\n */\n waitForResult?: boolean;\n};\n\nexport type StartFlowOptions = OptionalField<\n ApprovalFlow,\n 'id' | 'loadingText'\n> & { show?: boolean };\n\nexport type EndFlowOptions = Pick;\n\nexport type SetFlowLoadingTextOptions = ApprovalFlow;\n\nexport type SuccessOptions = ResultOptions & {\n message?: string | ResultComponent | (string | ResultComponent)[];\n};\n\nexport type ErrorOptions = ResultOptions & {\n error?: string | ResultComponent | (string | ResultComponent)[];\n};\n\n// Result Types\n\nexport type AcceptResultCallbacks = {\n /**\n * Inform the request acceptor that the post-approval logic was successful.\n *\n * @param value - An optional value generated by the post-approval logic.\n */\n success: (value?: unknown) => void;\n\n /**\n * Inform the request acceptor that the post-approval logic failed.\n *\n * @param error - The reason for the failure.\n */\n error: (error: Error) => void;\n};\n\nexport type AddResult = {\n /**\n * An optional value provided by the request acceptor.\n */\n value?: unknown;\n\n /**\n * Callback functions that must be used to indicate to the request acceptor whether the post-approval logic was successful or not.\n * Will be undefined if the request acceptor did not specify that they want to wait for a result.\n */\n resultCallbacks?: AcceptResultCallbacks;\n};\n\nexport type AcceptResult = {\n /**\n * An optional value provided by the request creator when indicating a successful result.\n */\n value?: unknown;\n};\n\nexport type ApprovalFlowStartResult = ApprovalFlow;\n\nexport type SuccessResult = Record;\n\nexport type ErrorResult = Record;\n\n// Event Types\n\nexport type ApprovalStateChange = ControllerStateChangeEvent<\n typeof controllerName,\n ApprovalControllerState\n>;\n\nexport type ApprovalControllerEvents = ApprovalStateChange;\n\n// Action Types\n\nexport type GetApprovalsState = ControllerGetStateAction<\n typeof controllerName,\n ApprovalControllerState\n>;\n\nexport type ClearApprovalRequests = {\n type: `${typeof controllerName}:clearRequests`;\n handler: (error: JsonRpcError) => void;\n};\n\nexport type AddApprovalRequest = {\n type: `${typeof controllerName}:addRequest`;\n handler: (\n opts: AddApprovalOptions,\n shouldShowRequest: boolean,\n ) => ReturnType;\n};\n\nexport type HasApprovalRequest = {\n type: `${typeof controllerName}:hasRequest`;\n handler: ApprovalController['has'];\n};\n\nexport type AcceptRequest = {\n type: `${typeof controllerName}:acceptRequest`;\n handler: ApprovalController['accept'];\n};\n\nexport type RejectRequest = {\n type: `${typeof controllerName}:rejectRequest`;\n handler: ApprovalController['reject'];\n};\n\nexport type UpdateRequestState = {\n type: `${typeof controllerName}:updateRequestState`;\n handler: ApprovalController['updateRequestState'];\n};\n\nexport type StartFlow = {\n type: `${typeof controllerName}:startFlow`;\n handler: ApprovalController['startFlow'];\n};\n\nexport type EndFlow = {\n type: `${typeof controllerName}:endFlow`;\n handler: ApprovalController['endFlow'];\n};\n\nexport type SetFlowLoadingText = {\n type: `${typeof controllerName}:setFlowLoadingText`;\n handler: ApprovalController['setFlowLoadingText'];\n};\n\nexport type ShowSuccess = {\n type: `${typeof controllerName}:showSuccess`;\n handler: ApprovalController['success'];\n};\n\nexport type ShowError = {\n type: `${typeof controllerName}:showError`;\n handler: ApprovalController['error'];\n};\n\nexport type ApprovalControllerActions =\n | GetApprovalsState\n | ClearApprovalRequests\n | AddApprovalRequest\n | HasApprovalRequest\n | AcceptRequest\n | RejectRequest\n | UpdateRequestState\n | StartFlow\n | EndFlow\n | SetFlowLoadingText\n | ShowSuccess\n | ShowError;\n\n/**\n * Controller for managing requests that require user approval.\n *\n * Enables limiting the number of pending requests by origin and type, counting\n * pending requests, and more.\n *\n * Adding a request returns a promise that resolves or rejects when the request\n * is approved or denied, respectively.\n */\nexport class ApprovalController extends BaseController<\n typeof controllerName,\n ApprovalControllerState,\n ApprovalControllerMessenger\n> {\n #approvals: Map;\n\n #origins: Map>;\n\n #showApprovalRequest: () => void;\n\n #typesExcludedFromRateLimiting: string[];\n\n /**\n * Construct an Approval controller.\n *\n * @param options - The controller options.\n * @param options.showApprovalRequest - Function for opening the UI such that\n * the request can be displayed to the user.\n * @param options.messenger - The restricted controller messenger for the Approval controller.\n * @param options.state - The initial controller state.\n * @param options.typesExcludedFromRateLimiting - Array of approval types which allow multiple pending approval requests from the same origin.\n */\n constructor({\n messenger,\n showApprovalRequest,\n state = {},\n typesExcludedFromRateLimiting = [],\n }: ApprovalControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state: { ...getDefaultState(), ...state },\n });\n\n this.#approvals = new Map();\n this.#origins = new Map();\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#showApprovalRequest = showApprovalRequest;\n this.#typesExcludedFromRateLimiting = typesExcludedFromRateLimiting;\n this.registerMessageHandlers();\n }\n\n /**\n * Constructor helper for registering this controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:clearRequests` as const,\n this.clear.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:addRequest` as const,\n (opts: AddApprovalOptions, shouldShowRequest: boolean) => {\n if (shouldShowRequest) {\n return this.addAndShowApprovalRequest(opts);\n }\n return this.add(opts);\n },\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasRequest` as const,\n this.has.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:acceptRequest` as const,\n this.accept.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:rejectRequest` as const,\n this.reject.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:updateRequestState` as const,\n this.updateRequestState.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:startFlow` as const,\n this.startFlow.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:endFlow` as const,\n this.endFlow.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:setFlowLoadingText` as const,\n this.setFlowLoadingText.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:showSuccess` as const,\n this.success.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:showError` as const,\n this.error.bind(this),\n );\n }\n\n /**\n * Adds an approval request per the given arguments, calls the show approval\n * request function, and returns the associated approval promise resolving to\n * an AddResult object.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * @param opts.requestState - Additional state associated with the request,\n * if any.\n * @returns The approval promise resolving to an AddResult object.\n */\n addAndShowApprovalRequest(\n opts: AddApprovalOptions & { expectsResult: true },\n ): Promise;\n\n /**\n * Adds an approval request per the given arguments, calls the show approval\n * request function, and returns the associated approval promise resolving\n * to a value provided during acceptance.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * @param opts.requestState - Additional state associated with the request,\n * if any.\n * @returns The approval promise resolving to a value provided during acceptance.\n */\n addAndShowApprovalRequest(opts: AddApprovalOptions): Promise;\n\n addAndShowApprovalRequest(opts: AddApprovalOptions): Promise {\n const promise = this.#add(\n opts.origin,\n opts.type,\n opts.id,\n opts.requestData,\n opts.requestState,\n opts.expectsResult,\n );\n this.#showApprovalRequest();\n return promise;\n }\n\n /**\n * Adds an approval request per the given arguments and returns the approval\n * promise resolving to an AddResult object.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise resolving to an AddResult object.\n */\n add(opts: AddApprovalOptions & { expectsResult: true }): Promise;\n\n /**\n * Adds an approval request per the given arguments and returns the approval\n * promise resolving to a value provided during acceptance.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise resolving to a value provided during acceptance.\n */\n add(opts: AddApprovalOptions): Promise;\n\n add(opts: AddApprovalOptions): Promise {\n return this.#add(\n opts.origin,\n opts.type,\n opts.id,\n opts.requestData,\n opts.requestState,\n opts.expectsResult,\n );\n }\n\n /**\n * Gets the info for the approval request with the given id.\n *\n * @param id - The id of the approval request.\n * @returns The approval request data associated with the id.\n */\n get(id: string): ApprovalRequest | undefined {\n return this.state.pendingApprovals[id];\n }\n\n /**\n * Gets the number of pending approvals, by origin and/or type.\n *\n * If only `origin` is specified, all approvals for that origin will be\n * counted, regardless of type.\n * If only `type` is specified, all approvals for that type will be counted,\n * regardless of origin.\n * If both `origin` and `type` are specified, 0 or 1 will be returned.\n *\n * @param opts - The approval count options.\n * @param opts.origin - An approval origin.\n * @param opts.type - The type of the approval request.\n * @returns The current approval request count for the given origin and/or\n * type.\n */\n getApprovalCount(opts: { origin?: string; type?: string } = {}): number {\n if (!opts.origin && !opts.type) {\n throw new Error('Must specify origin, type, or both.');\n }\n const { origin, type: _type } = opts;\n\n if (origin && _type) {\n return this.#origins.get(origin)?.get(_type) || 0;\n }\n\n if (origin) {\n return Array.from(\n (this.#origins.get(origin) || new Map()).values(),\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n ).reduce((total, value) => total + value, 0);\n }\n\n // Only \"type\" was specified\n let count = 0;\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n count += 1;\n }\n }\n return count;\n }\n\n /**\n * Get the total count of all pending approval requests for all origins.\n *\n * @returns The total pending approval request count.\n */\n getTotalApprovalCount(): number {\n return this.state.pendingApprovalCount;\n }\n\n /**\n * Checks if there's a pending approval request per the given parameters.\n * At least one parameter must be specified. An error will be thrown if the\n * parameters are invalid.\n *\n * If `id` is specified, all other parameters will be ignored.\n * If `id` is not specified, the method will check for requests that match\n * all of the specified parameters.\n *\n * @param opts - Options bag.\n * @param opts.id - The ID to check for.\n * @param opts.origin - The origin to check for.\n * @param opts.type - The type to check for.\n * @returns `true` if a matching approval is found, and `false` otherwise.\n */\n has(opts: { id?: string; origin?: string; type?: string } = {}): boolean {\n const { id, origin, type: _type } = opts;\n\n if (id) {\n if (typeof id !== 'string') {\n throw new Error('May not specify non-string id.');\n }\n return this.#approvals.has(id);\n }\n\n if (_type && typeof _type !== 'string') {\n throw new Error('May not specify non-string type.');\n }\n\n if (origin) {\n if (typeof origin !== 'string') {\n throw new Error('May not specify non-string origin.');\n }\n\n // Check origin and type pair if type also specified\n if (_type) {\n return Boolean(this.#origins.get(origin)?.get(_type));\n }\n return this.#origins.has(origin);\n }\n\n if (_type) {\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n return true;\n }\n }\n return false;\n }\n throw new Error(\n 'Must specify a valid combination of id, origin, and type.',\n );\n }\n\n /**\n * Resolves the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param value - The value to resolve the approval promise with.\n * @param options - Options bag.\n * @returns A promise that either resolves once a result is provided by\n * the creator of the approval request, or immediately if `options.waitForResult`\n * is `false` or `undefined`.\n */\n accept(\n id: string,\n value?: unknown,\n options?: AcceptOptions,\n ): Promise {\n // Safe to cast as the delete method below will throw if the ID is not found\n const approval = this.get(id) as ApprovalRequest;\n const requestPromise = this.#deleteApprovalAndGetCallbacks(id);\n\n return new Promise((resolve, reject) => {\n const resultCallbacks: AcceptResultCallbacks = {\n success: (acceptValue?: unknown) => resolve({ value: acceptValue }),\n error: reject,\n };\n\n if (options?.waitForResult && !approval.expectsResult) {\n reject(new ApprovalRequestNoResultSupportError(id));\n return;\n }\n\n const resultValue = options?.waitForResult ? resultCallbacks : undefined;\n\n const resolveValue = approval.expectsResult\n ? { value, resultCallbacks: resultValue }\n : value;\n\n requestPromise.resolve(resolveValue);\n\n if (!options?.waitForResult) {\n resolve({ value: undefined });\n }\n });\n }\n\n /**\n * Rejects the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param error - The error to reject the approval promise with.\n */\n reject(id: string, error: unknown): void {\n this.#deleteApprovalAndGetCallbacks(id).reject(error);\n }\n\n /**\n * Rejects and deletes all approval requests.\n *\n * @param rejectionError - The JsonRpcError to reject the approval\n * requests with.\n */\n clear(rejectionError: JsonRpcError): void {\n for (const id of this.#approvals.keys()) {\n this.reject(id, rejectionError);\n }\n this.#origins.clear();\n this.update((draftState) => {\n draftState.pendingApprovals = {};\n draftState.pendingApprovalCount = 0;\n });\n }\n\n /**\n * Updates the request state of the approval with the given id.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request.\n * @param opts.requestState - Additional data associated with the request\n */\n updateRequestState(opts: UpdateRequestStateOptions): void {\n if (!this.state.pendingApprovals[opts.id]) {\n throw new ApprovalRequestNotFoundError(opts.id);\n }\n\n this.update((draftState) => {\n draftState.pendingApprovals[opts.id].requestState =\n opts.requestState as never;\n });\n }\n\n /**\n * Starts a new approval flow.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval flow.\n * @param opts.loadingText - The loading text that will be associated to the approval flow.\n * @param opts.show - A flag to determine whether the approval should show to the user.\n * @returns The object containing the approval flow id.\n */\n startFlow(opts: StartFlowOptions = {}): ApprovalFlowStartResult {\n const id = opts.id ?? nanoid();\n const loadingText = opts.loadingText ?? null;\n\n this.update((draftState) => {\n draftState.approvalFlows.push({ id, loadingText });\n });\n\n // By default, if nothing else is specified, we always show the approval.\n if (opts.show !== false) {\n this.#showApprovalRequest();\n }\n\n return { id, loadingText };\n }\n\n /**\n * Ends the current approval flow.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval flow that will be finished.\n */\n endFlow({ id }: EndFlowOptions) {\n if (!this.state.approvalFlows.length) {\n throw new NoApprovalFlowsError();\n }\n\n const currentFlow = this.state.approvalFlows.slice(-1)[0];\n\n if (id !== currentFlow.id) {\n throw new EndInvalidFlowError(\n id,\n this.state.approvalFlows.map((flow) => flow.id),\n );\n }\n\n this.update((draftState) => {\n draftState.approvalFlows.pop();\n });\n }\n\n /**\n * Sets the loading text for the approval flow.\n *\n * @param opts - Options bag.\n * @param opts.id - The approval flow loading text that will be displayed.\n * @param opts.loadingText - The loading text that will be associated to the approval flow.\n */\n setFlowLoadingText({ id, loadingText }: SetFlowLoadingTextOptions) {\n const flowIndex = this.state.approvalFlows.findIndex(\n (flow) => flow.id === id,\n );\n\n if (flowIndex === -1) {\n throw new MissingApprovalFlowError(id);\n }\n\n this.update((draftState) => {\n draftState.approvalFlows[flowIndex].loadingText = loadingText;\n });\n }\n\n /**\n * Show a success page.\n *\n * @param opts - Options bag.\n * @param opts.message - The message text or components to display in the page.\n * @param opts.header - The text or components to display in the header of the page.\n * @param opts.flowToEnd - The ID of the approval flow to end once the success page is approved.\n * @param opts.title - The title to display above the message. Shown by default but can be hidden with `null`.\n * @param opts.icon - The icon to display in the page. Shown by default but can be hidden with `null`.\n * @returns Empty object to support future additions.\n */\n async success(opts: SuccessOptions = {}): Promise {\n await this.#result(APPROVAL_TYPE_RESULT_SUCCESS, opts, {\n message: opts.message,\n header: opts.header,\n title: opts.title,\n icon: opts.icon,\n } as Record);\n\n return {};\n }\n\n /**\n * Show an error page.\n *\n * @param opts - Options bag.\n * @param opts.message - The message text or components to display in the page.\n * @param opts.header - The text or components to display in the header of the page.\n * @param opts.flowToEnd - The ID of the approval flow to end once the error page is approved.\n * @param opts.title - The title to display above the message. Shown by default but can be hidden with `null`.\n * @param opts.icon - The icon to display in the page. Shown by default but can be hidden with `null`.\n * @returns Empty object to support future additions.\n */\n async error(opts: ErrorOptions = {}): Promise {\n await this.#result(APPROVAL_TYPE_RESULT_ERROR, opts, {\n error: opts.error,\n header: opts.header,\n title: opts.title,\n icon: opts.icon,\n } as Record);\n\n return {};\n }\n\n /**\n * Implementation of add operation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param id - The id of the approval request.\n * @param requestData - The request data associated with the approval request.\n * @param requestState - The request state associated with the approval request.\n * @param expectsResult - Whether the approval request expects a result object to be returned.\n * @returns The approval promise.\n */\n #add(\n origin: string,\n type: string,\n id: string = nanoid(),\n requestData?: Record,\n requestState?: Record,\n expectsResult?: boolean,\n ): Promise {\n this.#validateAddParams(id, origin, type, requestData, requestState);\n\n if (\n !this.#typesExcludedFromRateLimiting.includes(type) &&\n this.has({ origin, type })\n ) {\n throw rpcErrors.resourceUnavailable(\n getAlreadyPendingMessage(origin, type),\n );\n }\n\n // add pending approval\n return new Promise((resolve, reject) => {\n this.#approvals.set(id, { resolve, reject });\n this.#addPendingApprovalOrigin(origin, type);\n\n this.#addToStore(\n id,\n origin,\n type,\n requestData,\n requestState,\n expectsResult,\n );\n });\n }\n\n /**\n * Validates parameters to the add method.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n * @param requestState - The request state associated with the approval request.\n */\n #validateAddParams(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n requestState?: Record,\n ): void {\n let errorMessage = null;\n if (!id || typeof id !== 'string') {\n errorMessage = 'Must specify non-empty string id.';\n } else if (this.#approvals.has(id)) {\n errorMessage = `Approval request with id '${id}' already exists.`;\n } else if (!origin || typeof origin !== 'string') {\n errorMessage = 'Must specify non-empty string origin.';\n } else if (!type || typeof type !== 'string') {\n errorMessage = 'Must specify non-empty string type.';\n } else if (\n requestData &&\n (typeof requestData !== 'object' || Array.isArray(requestData))\n ) {\n errorMessage = 'Request data must be a plain object if specified.';\n } else if (\n requestState &&\n (typeof requestState !== 'object' || Array.isArray(requestState))\n ) {\n errorMessage = 'Request state must be a plain object if specified.';\n }\n\n if (errorMessage) {\n throw rpcErrors.internal(errorMessage);\n }\n }\n\n /**\n * Adds an entry to _origins.\n * Performs no validation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n */\n #addPendingApprovalOrigin(origin: string, type: string): void {\n let originMap = this.#origins.get(origin);\n\n if (!originMap) {\n originMap = new Map();\n this.#origins.set(origin, originMap);\n }\n\n const currentValue = originMap.get(type) || 0;\n originMap.set(type, currentValue + 1);\n }\n\n /**\n * Adds an entry to the store.\n * Performs no validation.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n * @param requestState - The request state associated with the approval request.\n * @param expectsResult - Whether the request expects a result object to be returned.\n */\n #addToStore(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n requestState?: Record,\n expectsResult?: boolean,\n ): void {\n const approval = {\n id,\n origin,\n type,\n time: Date.now(),\n requestData: requestData || null,\n requestState: requestState || null,\n expectsResult: expectsResult || false,\n };\n\n this.update((draftState) => {\n draftState.pendingApprovals[id] = approval as never;\n\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Deletes the approval with the given id. The approval promise must be\n * resolved or reject before this method is called.\n * Deletion is an internal operation because approval state is solely\n * managed by this controller.\n *\n * @param id - The id of the approval request to be deleted.\n */\n #delete(id: string): void {\n this.#approvals.delete(id);\n\n // This method is only called after verifying that the approval with the\n // specified id exists.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const { origin, type } = this.state.pendingApprovals[id]!;\n\n const originMap = this.#origins.get(origin) as Map;\n const originTotalCount = this.getApprovalCount({ origin });\n const originTypeCount = originMap.get(type) as number;\n\n if (originTotalCount === 1) {\n this.#origins.delete(origin);\n } else {\n originMap.set(type, originTypeCount - 1);\n }\n\n this.update((draftState) => {\n delete draftState.pendingApprovals[id];\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Gets the approval callbacks for the given id, deletes the entry, and then\n * returns the callbacks for promise resolution.\n * Throws an error if no approval is found for the given id.\n *\n * @param id - The id of the approval request.\n * @returns The promise callbacks associated with the approval request.\n */\n #deleteApprovalAndGetCallbacks(id: string): ApprovalCallbacks {\n const callbacks = this.#approvals.get(id);\n if (!callbacks) {\n throw new ApprovalRequestNotFoundError(id);\n }\n\n this.#delete(id);\n return callbacks;\n }\n\n async #result(\n type: string,\n opts: ResultOptions,\n requestData: Record,\n ) {\n try {\n await this.addAndShowApprovalRequest({\n origin: ORIGIN_METAMASK,\n type,\n requestData,\n });\n } catch (error) {\n console.info('Failed to display result page', error);\n } finally {\n if (opts.flowToEnd) {\n try {\n this.endFlow({ id: opts.flowToEnd });\n } catch (error) {\n console.info('Failed to end flow', { id: opts.flowToEnd, error });\n }\n }\n }\n }\n}\n\nexport default ApprovalController;\n"]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/errors.js.map b/node_modules/@metamask/approval-controller/dist/errors.js.map -deleted file mode 100644 -index a464c67..0000000 ---- a/node_modules/@metamask/approval-controller/dist/errors.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":[],"names":[],"mappings":""} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/errors.mjs.map b/node_modules/@metamask/approval-controller/dist/errors.mjs.map -deleted file mode 100644 -index 84c51b2..0000000 ---- a/node_modules/@metamask/approval-controller/dist/errors.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/index.js b/node_modules/@metamask/approval-controller/dist/index.js -index 59a39d3..e1c5c4d 100644 ---- a/node_modules/@metamask/approval-controller/dist/index.js -+++ b/node_modules/@metamask/approval-controller/dist/index.js -@@ -3,7 +3,7 @@ - - - --var _chunkPMXPCCKSjs = require('./chunk-PMXPCCKS.js'); -+var _chunkNONDXCHJjs = require('./chunk-NONDXCHJ.js'); - - - -@@ -21,5 +21,5 @@ var _chunkLKCXZAKDjs = require('./chunk-LKCXZAKD.js'); - - - --exports.APPROVAL_TYPE_RESULT_ERROR = _chunkPMXPCCKSjs.APPROVAL_TYPE_RESULT_ERROR; exports.APPROVAL_TYPE_RESULT_SUCCESS = _chunkPMXPCCKSjs.APPROVAL_TYPE_RESULT_SUCCESS; exports.ApprovalController = _chunkPMXPCCKSjs.ApprovalController; exports.ApprovalRequestNoResultSupportError = _chunkLKCXZAKDjs.ApprovalRequestNoResultSupportError; exports.ApprovalRequestNotFoundError = _chunkLKCXZAKDjs.ApprovalRequestNotFoundError; exports.EndInvalidFlowError = _chunkLKCXZAKDjs.EndInvalidFlowError; exports.MissingApprovalFlowError = _chunkLKCXZAKDjs.MissingApprovalFlowError; exports.NoApprovalFlowsError = _chunkLKCXZAKDjs.NoApprovalFlowsError; exports.ORIGIN_METAMASK = _chunkPMXPCCKSjs.ORIGIN_METAMASK; -+exports.APPROVAL_TYPE_RESULT_ERROR = _chunkNONDXCHJjs.APPROVAL_TYPE_RESULT_ERROR; exports.APPROVAL_TYPE_RESULT_SUCCESS = _chunkNONDXCHJjs.APPROVAL_TYPE_RESULT_SUCCESS; exports.ApprovalController = _chunkNONDXCHJjs.ApprovalController; exports.ApprovalRequestNoResultSupportError = _chunkLKCXZAKDjs.ApprovalRequestNoResultSupportError; exports.ApprovalRequestNotFoundError = _chunkLKCXZAKDjs.ApprovalRequestNotFoundError; exports.EndInvalidFlowError = _chunkLKCXZAKDjs.EndInvalidFlowError; exports.MissingApprovalFlowError = _chunkLKCXZAKDjs.MissingApprovalFlowError; exports.NoApprovalFlowsError = _chunkLKCXZAKDjs.NoApprovalFlowsError; exports.ORIGIN_METAMASK = _chunkNONDXCHJjs.ORIGIN_METAMASK; - //# sourceMappingURL=index.js.map -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/index.js.map b/node_modules/@metamask/approval-controller/dist/index.js.map -deleted file mode 100644 -index a464c67..0000000 ---- a/node_modules/@metamask/approval-controller/dist/index.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":[],"names":[],"mappings":""} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/index.mjs b/node_modules/@metamask/approval-controller/dist/index.mjs -index c1c5b95..c482411 100644 ---- a/node_modules/@metamask/approval-controller/dist/index.mjs -+++ b/node_modules/@metamask/approval-controller/dist/index.mjs -@@ -3,7 +3,7 @@ import { - APPROVAL_TYPE_RESULT_SUCCESS, - ApprovalController, - ORIGIN_METAMASK --} from "./chunk-PIJZDVKC.mjs"; -+} from "./chunk-CZANKQ6E.mjs"; - import { - ApprovalRequestNoResultSupportError, - ApprovalRequestNotFoundError, -diff --git a/node_modules/@metamask/approval-controller/dist/index.mjs.map b/node_modules/@metamask/approval-controller/dist/index.mjs.map -deleted file mode 100644 -index 84c51b2..0000000 ---- a/node_modules/@metamask/approval-controller/dist/index.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} -\ No newline at end of file -diff --git a/node_modules/@metamask/approval-controller/dist/types/ApprovalController.d.ts b/node_modules/@metamask/approval-controller/dist/types/ApprovalController.d.ts -index 2b9ad4d..26efec3 100644 ---- a/node_modules/@metamask/approval-controller/dist/types/ApprovalController.d.ts -+++ b/node_modules/@metamask/approval-controller/dist/types/ApprovalController.d.ts -@@ -101,6 +101,12 @@ export type AcceptOptions = { - * If false or unspecified, the promise will resolve immediately. - */ - waitForResult?: boolean; -+ /** -+ * Patch for #10758 -+ * provide the AcceptOptions with extra options which have been used in `engine.acceptPendingApproval()` function in mobile. -+ * the metamask mobile already patch the AprrovalControll.accept() function to accept the extra options. -+ */ -+ deleteAfterResult?: boolean; - }; - export type StartFlowOptions = OptionalField & { - show?: boolean; diff --git a/scripts/generate-attributions/package.json b/scripts/generate-attributions/package.json index e260d5a2f83..ae90d1c2b5e 100644 --- a/scripts/generate-attributions/package.json +++ b/scripts/generate-attributions/package.json @@ -12,7 +12,7 @@ "@metamask/oss-attribution-generator": "^2.0.2" }, "engines": { - "node": "^20.12.2", + "node": "^20.14.0", "yarn": "^1.22.0" }, "lavamoat": { diff --git a/yarn.lock b/yarn.lock index cfc7baf393b..ddd7f1392d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4320,12 +4320,12 @@ "@metamask/controller-utils" "^11.3.0" "@metamask/utils" "^9.1.0" -"@metamask/approval-controller@^7.0.0", "@metamask/approval-controller@^7.0.1", "@metamask/approval-controller@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-7.0.2.tgz#bf46090fb900a5687b93978fb93eb3cf61d39a56" - integrity sha512-cjCXpdxQ1IzmNrfTtw0MpYcBDklzvWcyRz5AutvlBaLafrWh2FDQZFvofd/rO49DdrvAxEQHUt6GXE9aFjCFTQ== +"@metamask/approval-controller@^7.0.0", "@metamask/approval-controller@^7.0.2", "@metamask/approval-controller@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-7.1.0.tgz#34b07bc4eaf6938b15f9d915c6885d4a5c0a5205" + integrity sha512-dhqUeX8wMzW88U+Vgr7oKf0Vouol10ncB3lxmvWyC1VZJhSOdO3VUkn0tH1lzt3ybxYVMOkPaB3gfdksfnNRyA== dependencies: - "@metamask/base-controller" "^6.0.2" + "@metamask/base-controller" "^7.0.1" "@metamask/rpc-errors" "^6.3.1" "@metamask/utils" "^9.1.0" nanoid "^3.1.31" @@ -4642,6 +4642,18 @@ ethereum-cryptography "^2.1.2" tweetnacl "^1.0.3" +"@metamask/eth-sig-util@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-8.0.0.tgz#6310d93cd1101cab3cc6bc2a1ff526290ed2695b" + integrity sha512-IwE6aoxUL39IhmsAgE4nk+OZbNo+ThFZRNsUjE1pjdEa4MFpWzm1Rue4zJ5DMy1oUyZBi/aiCLMhdMnjl2bh2Q== + dependencies: + "@ethereumjs/util" "^8.1.0" + "@metamask/abi-utils" "^2.0.4" + "@metamask/utils" "^9.0.0" + "@scure/base" "~1.1.3" + ethereum-cryptography "^2.1.2" + tweetnacl "^1.0.3" + "@metamask/eth-simple-keyring@^6.0.5": version "6.0.5" resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-6.0.5.tgz#3e2f783092f9d873740e43852880216192802290" @@ -5336,16 +5348,18 @@ "@metamask/swappable-obj-proxy" "^2.2.0" "@metamask/utils" "^8.3.0" -"@metamask/signature-controller@^19.1.0": - version "19.1.0" - resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-19.1.0.tgz#47c7435f51d3273fb5a0373f4ebdcfccb6d490a9" - integrity sha512-1rDKxV7HYoKZjBbqkeWLk3TLJJcl8cRgAkPTG8jMRIp7eYW2vUxo5nsiaJlBBHDV/fbjR12SbiawMjj93zD2DA== +"@metamask/signature-controller@^20.1.0": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-20.1.0.tgz#b2ab41ec6eb191f9b21a74bf636d2b8e34d590e0" + integrity sha512-29pYdr9Q5YB5tGrYLEkpCh3l41W5Otg87cQ+cRf7Je0xGo34VdrIHNv6MJQcEgng5qTmR0tG3Hq23nPyZo8rSw== dependencies: "@metamask/base-controller" "^7.0.1" "@metamask/controller-utils" "^11.3.0" - "@metamask/message-manager" "^10.1.1" + "@metamask/eth-sig-util" "^8.0.0" "@metamask/utils" "^9.1.0" + jsonschema "^1.2.4" lodash "^4.17.21" + uuid "^8.3.2" "@metamask/slip44@3.1.0", "@metamask/slip44@^3.1.0": version "3.1.0" From c380b2e4e9ebabe392b7eadc2c71e749b0574b56 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Wed, 23 Oct 2024 14:48:34 +0200 Subject: [PATCH 04/19] chore: upgrade assets-controllers to v36.0.0 (#11971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Pr to upgrade assets-controllers to v36.0.0 ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/0c6bb4e4-0679-4054-8415-0b0504f55ef4 ### **After** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- package.json | 2 +- ...@metamask+assets-controllers+36.0.0.patch} | 354 ++++-------------- yarn.lock | 24 +- 3 files changed, 83 insertions(+), 297 deletions(-) rename patches/{@metamask+assets-controllers+35.0.0.patch => @metamask+assets-controllers+36.0.0.patch} (67%) diff --git a/package.json b/package.json index ac835169e01..1540430eef9 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "@metamask/accounts-controller": "^18.2.1", "@metamask/address-book-controller": "^6.0.1", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^35.0.0", + "@metamask/assets-controllers": "^36.0.0", "@metamask/base-controller": "^7.0.1", "@metamask/composable-controller": "^3.0.0", "@metamask/contract-metadata": "^2.1.0", diff --git a/patches/@metamask+assets-controllers+35.0.0.patch b/patches/@metamask+assets-controllers+36.0.0.patch similarity index 67% rename from patches/@metamask+assets-controllers+35.0.0.patch rename to patches/@metamask+assets-controllers+36.0.0.patch index f731509033c..af8d00e058a 100644 --- a/patches/@metamask+assets-controllers+35.0.0.patch +++ b/patches/@metamask+assets-controllers+36.0.0.patch @@ -1,10 +1,10 @@ -diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-2NQRWANM.js b/node_modules/@metamask/assets-controllers/dist/chunk-2NQRWANM.js -index c164c90..bd2fe09 100644 ---- a/node_modules/@metamask/assets-controllers/dist/chunk-2NQRWANM.js -+++ b/node_modules/@metamask/assets-controllers/dist/chunk-2NQRWANM.js -@@ -189,6 +189,19 @@ var TokensController = class extends _basecontroller.BaseController { - } - ); +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-DSDI75PJ.js b/node_modules/@metamask/assets-controllers/dist/chunk-DSDI75PJ.js +index f95d90f..3c33263 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-DSDI75PJ.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-DSDI75PJ.js +@@ -173,6 +173,17 @@ var TokenRatesController = class extends _pollingcontroller.StaticIntervalPollin + _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _subscribeToNetworkStateChange, subscribeToNetworkStateChange_fn).call(this); + _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _subscribeToAccountChange, subscribeToAccountChange_fn).call(this); } + + /** @@ -13,104 +13,18 @@ index c164c90..bd2fe09 100644 + */ + reset() { + this.update((state) => { -+ state.allTokens = {}; -+ state.allIgnoredTokens = {}; -+ state.ignoredTokens = []; -+ state.tokens = []; ++ state.marketData = {}; + }); + } ++ /** - * Adds a token to the stored token list. - * -diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-354SINOH.js b/node_modules/@metamask/assets-controllers/dist/chunk-354SINOH.js -index 7f87776..0100806 100644 ---- a/node_modules/@metamask/assets-controllers/dist/chunk-354SINOH.js -+++ b/node_modules/@metamask/assets-controllers/dist/chunk-354SINOH.js -@@ -10,7 +10,7 @@ var _basecontroller = require('@metamask/base-controller'); - - - -- -+var _chunkNYVA7ZTQjs = require('./chunk-NYVA7ZTQ.js'); - - - -@@ -25,6 +25,7 @@ var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { - BlockaidResultType2["Malicious"] = "Malicious"; - return BlockaidResultType2; - })(BlockaidResultType || {}); -+var MAX_GET_COLLECTION_BATCH_SIZE = 20; - var _disabled, _addNft, _getNftState, _inProcessNftFetchingUpdates, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _getOwnerNftApi, getOwnerNftApi_fn, _getOwnerNfts, getOwnerNfts_fn; - var NftDetectionController = class extends _basecontroller.BaseController { - /** -@@ -134,6 +135,62 @@ var NftDetectionController = class extends _basecontroller.BaseController { - apiNfts = resultNftApi.tokens.filter( - (elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true) - ); -+ -+ const collections = apiNfts.reduce((acc, currValue) => { -+ if (!acc.includes(currValue.token.contract) && currValue.token.contract === currValue?.token?.collection?.id) { -+ acc.push(currValue.token.contract); -+ } -+ return acc; -+ }, []); -+ if (collections.length !== 0) { -+ const collectionResponse = await _chunkNYVA7ZTQjs.reduceInBatchesSerially.call(void 0, { -+ values: collections, -+ batchSize: MAX_GET_COLLECTION_BATCH_SIZE, -+ eachBatch: async (allResponses, batch) => { -+ const params = new URLSearchParams( -+ batch.map((s) => ["contract", s]) -+ ); -+ params.append("chainId", "1"); -+ const collectionResponseForBatch = await _controllerutils.fetchWithErrorHandling.call(void 0, -+ { -+ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${params.toString()}`, -+ options: { -+ headers: { -+ Version: '1' -+ } -+ }, -+ timeout: 15000 -+ } -+ ); -+ return { -+ ...allResponses, -+ ...collectionResponseForBatch -+ }; -+ }, -+ initialResult: {} -+ }); -+ if (collectionResponse.collections?.length) { -+ apiNfts.forEach((singleNFT) => { -+ const found = collectionResponse.collections.find( -+ (elm) => elm.id?.toLowerCase() === singleNFT.token.contract.toLowerCase() -+ ); -+ if (found) { -+ singleNFT.token = { -+ ...singleNFT.token, -+ collection: { -+ ...singleNFT.token.collection ?? {}, -+ creator: found?.creator, -+ openseaVerificationStatus: found?.openseaVerificationStatus, -+ contractDeployedAt: found.contractDeployedAt, -+ ownerCount: found.ownerCount, -+ topBid: found.topBid -+ } -+ }; -+ } -+ }); -+ } -+ } -+ - const addNftPromises = apiNfts.map(async (nft) => { - const { - tokenId, -diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-3GGCHDRG.js b/node_modules/@metamask/assets-controllers/dist/chunk-3GGCHDRG.js -index 5a6e21d..121d2db 100644 ---- a/node_modules/@metamask/assets-controllers/dist/chunk-3GGCHDRG.js -+++ b/node_modules/@metamask/assets-controllers/dist/chunk-3GGCHDRG.js -@@ -44,7 +44,7 @@ var getDefaultNftControllerState = () => ({ + * Allows controller to make active and passive polling requests + */ +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-HTIZ4JKG.js b/node_modules/@metamask/assets-controllers/dist/chunk-HTIZ4JKG.js +index a9f6736..ecd98e7 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-HTIZ4JKG.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-HTIZ4JKG.js +@@ -46,7 +46,7 @@ var getDefaultNftControllerState = () => ({ allNfts: {}, ignoredNfts: [] }); @@ -119,7 +33,7 @@ index 5a6e21d..121d2db 100644 var NftController = class extends _basecontroller.BaseController { /** * Creates an NftController instance. -@@ -52,7 +52,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -54,7 +54,7 @@ var NftController = class extends _basecontroller.BaseController { * @param options - The controller options. * @param options.chainId - The chain ID of the current network. * @param options.ipfsGateway - The configured IPFS gateway. @@ -128,7 +42,7 @@ index 5a6e21d..121d2db 100644 * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used. * @param options.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. * @param options.getERC721AssetName - Gets the name of the asset at the given address. -@@ -69,7 +69,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -71,7 +71,7 @@ var NftController = class extends _basecontroller.BaseController { constructor({ chainId: initialChainId, ipfsGateway = _controllerutils.IPFS_DEFAULT_GATEWAY_URL, @@ -137,7 +51,7 @@ index 5a6e21d..121d2db 100644 useIpfsSubdomains = true, isIpfsGatewayEnabled = true, getERC721AssetName, -@@ -101,7 +101,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -103,7 +103,7 @@ var NftController = class extends _basecontroller.BaseController { * Handles the state change of the preference controller. * @param preferencesState - The new state of the preference controller. * @param preferencesState.ipfsGateway - The configured IPFS gateway. @@ -146,7 +60,7 @@ index 5a6e21d..121d2db 100644 * @param preferencesState.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. */ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onPreferencesControllerStateChange); -@@ -237,7 +237,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -239,7 +239,7 @@ var NftController = class extends _basecontroller.BaseController { _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _selectedAccountId, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _chainId, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _ipfsGateway, void 0); @@ -155,7 +69,7 @@ index 5a6e21d..121d2db 100644 _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _useIpfsSubdomains, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isIpfsGatewayEnabled, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getERC721AssetName, void 0); -@@ -252,7 +252,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -254,7 +254,7 @@ var NftController = class extends _basecontroller.BaseController { ).id); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _chainId, initialChainId); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _ipfsGateway, ipfsGateway); @@ -164,10 +78,11 @@ index 5a6e21d..121d2db 100644 _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _useIpfsSubdomains, useIpfsSubdomains); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _isIpfsGatewayEnabled, isIpfsGatewayEnabled); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getERC721AssetName, getERC721AssetName); -@@ -279,6 +279,17 @@ var NftController = class extends _basecontroller.BaseController { +@@ -281,6 +281,19 @@ var NftController = class extends _basecontroller.BaseController { _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _onSelectedAccountChange, onSelectedAccountChange_fn).bind(this) ); } ++ + /** + * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO + * Resets to the default state @@ -179,10 +94,11 @@ index 5a6e21d..121d2db 100644 + state.ignoredNfts = []; + }); + } ++ getNftApi() { return `${_controllerutils.NFT_API_BASE_URL}/tokens`; } -@@ -797,7 +808,7 @@ _mutex = new WeakMap(); +@@ -799,7 +812,7 @@ _mutex = new WeakMap(); _selectedAccountId = new WeakMap(); _chainId = new WeakMap(); _ipfsGateway = new WeakMap(); @@ -191,7 +107,7 @@ index 5a6e21d..121d2db 100644 _useIpfsSubdomains = new WeakMap(); _isIpfsGatewayEnabled = new WeakMap(); _getERC721AssetName = new WeakMap(); -@@ -822,7 +833,7 @@ onNetworkControllerNetworkDidChange_fn = function({ +@@ -824,7 +837,7 @@ onNetworkControllerNetworkDidChange_fn = function({ _onPreferencesControllerStateChange = new WeakSet(); onPreferencesControllerStateChange_fn = async function({ ipfsGateway, @@ -200,7 +116,7 @@ index 5a6e21d..121d2db 100644 isIpfsGatewayEnabled }) { const selectedAccount = this.messagingSystem.call( -@@ -830,9 +841,9 @@ onPreferencesControllerStateChange_fn = async function({ +@@ -832,9 +845,9 @@ onPreferencesControllerStateChange_fn = async function({ ); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _selectedAccountId, selectedAccount.id); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _ipfsGateway, ipfsGateway); @@ -212,7 +128,7 @@ index 5a6e21d..121d2db 100644 if (needsUpdateNftMetadata && selectedAccount) { await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn).call(this, selectedAccount); } -@@ -841,7 +852,7 @@ _onSelectedAccountChange = new WeakSet(); +@@ -843,7 +856,7 @@ _onSelectedAccountChange = new WeakSet(); onSelectedAccountChange_fn = async function(internalAccount) { const oldSelectedAccountId = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAccountId); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _selectedAccountId, internalAccount.id); @@ -221,43 +137,7 @@ index 5a6e21d..121d2db 100644 if (needsUpdateNftMetadata) { await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn).call(this, internalAccount); } -@@ -865,9 +876,14 @@ updateNestedNftState_fn = function(newCollection, baseStateKey, { userAddress, c - }); - }; - _getNftInformationFromApi = new WeakSet(); --getNftInformationFromApi_fn = async function(contractAddress, tokenId) { -+getNftInformationFromApi_fn = async function(contractAddress, tokenId, networkClientId) { -+ const chainId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getCorrectChainId, getCorrectChainId_fn).call(this, { -+ networkClientId -+ }); -+ // Convert hex chainId to number -+ const convertedChainId = (0, _controllerutils.convertHexToDecimal)(chainId).toString(); - const urlParams = new URLSearchParams({ -- chainIds: "1", -+ chainIds: convertedChainId, - tokens: `${contractAddress}:${tokenId}`, - includeTopBid: "true", - includeAttributes: "true", -@@ -881,12 +897,27 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { - } - } - }); -+ -+ const getCollectionParams = new URLSearchParams({ -+ chainId: convertedChainId, -+ id: `${nftInformation?.tokens[0]?.token?.collection?.id}` -+ }).toString(); -+ -+ const collectionInformation = await _controllerutils.fetchWithErrorHandling.call(void 0, { -+ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${getCollectionParams}`, -+ options: { -+ headers: { -+ Version: '1' -+ } -+ } -+ }); - if (!nftInformation?.tokens?.[0]?.token) { - return { +@@ -900,7 +913,8 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { name: null, description: null, image: null, @@ -267,25 +147,7 @@ index 5a6e21d..121d2db 100644 }; } const { -@@ -918,7 +949,16 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { - }, - rarityRank && { rarityRank }, - rarity && { rarity }, -- collection && { collection } -+ (collection || collectionInformation) && { -+ collection: { -+ ...collection || {}, -+ creator: collection?.creator || collectionInformation?.collections[0].creator, -+ openseaVerificationStatus: collectionInformation?.collections[0].openseaVerificationStatus, -+ contractDeployedAt: collectionInformation?.collections[0].contractDeployedAt, -+ ownerCount: collectionInformation?.collections[0].ownerCount, -+ topBid: collectionInformation?.collections[0].topBid -+ } -+ } - ); - return nftMetadata; - }; -@@ -938,7 +978,7 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -961,7 +975,7 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw tokenURI: tokenURI ?? null }; } @@ -294,7 +156,7 @@ index 5a6e21d..121d2db 100644 if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { return { image: null, -@@ -946,7 +986,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -969,7 +983,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw description: null, standard: standard || null, favorite: false, @@ -304,7 +166,7 @@ index 5a6e21d..121d2db 100644 }; } if (hasIpfsTokenURI) { -@@ -987,7 +1028,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -1010,7 +1025,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw description: null, standard: standard || null, favorite: false, @@ -314,14 +176,13 @@ index 5a6e21d..121d2db 100644 }; } }; -@@ -1018,10 +1060,21 @@ getNftInformation_fn = async function(contractAddress, tokenId, networkClientId) +@@ -1041,10 +1057,21 @@ getNftInformation_fn = async function(contractAddress, tokenId, networkClientId) _controllerutils.safelyExecute.call(void 0, () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformationFromTokenURI, getNftInformationFromTokenURI_fn).call(this, contractAddress, tokenId, networkClientId) ), - _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _openSeaEnabled) && chainId === "0x1" ? _controllerutils.safelyExecute.call(void 0, -- () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformationFromApi, getNftInformationFromApi_fn).call(this, contractAddress, tokenId) + _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _displayNftMedia) && chainId === "0x1" ? _controllerutils.safelyExecute.call(void 0, -+ () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformationFromApi, getNftInformationFromApi_fn).call(this, contractAddress, tokenId, networkClientId) + () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformationFromApi, getNftInformationFromApi_fn).call(this, contractAddress, tokenId) ) : void 0 ]); + if (blockchainMetadata?.error && nftApiMetadata?.error) { @@ -338,17 +199,7 @@ index 5a6e21d..121d2db 100644 return { ...nftApiMetadata, name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null, -@@ -1105,7 +1158,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont - nftMetadata, - existingEntry - ); -- if (!differentMetadata && existingEntry.isCurrentlyOwned) { -+ const hasNewFields = _chunkNEXY7SE2js.hasNewCollectionFields(nftMetadata, existingEntry); -+ if (!differentMetadata && existingEntry.isCurrentlyOwned && !hasNewFields) { - return; - } - const indexToUpdate = nfts.findIndex( -@@ -1137,7 +1191,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont +@@ -1161,7 +1188,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont symbol: nftContract.symbol, tokenId: tokenId.toString(), standard: nftMetadata.standard, @@ -359,7 +210,7 @@ index 5a6e21d..121d2db 100644 } } finally { diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-NHFZIY2K.js b/node_modules/@metamask/assets-controllers/dist/chunk-NHFZIY2K.js -index 995ec6b..84408ac 100644 +index 995ec6b..7222a8a 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-NHFZIY2K.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-NHFZIY2K.js @@ -19,7 +19,7 @@ function getDefaultTokenBalancesState() { @@ -383,10 +234,11 @@ index 995ec6b..84408ac 100644 this.messagingSystem.subscribe( "TokensController:stateChange", ({ tokens: newTokens, detectedTokens }) => { -@@ -67,6 +69,15 @@ var TokenBalancesController = class extends _basecontroller.BaseController { +@@ -67,6 +69,17 @@ var TokenBalancesController = class extends _basecontroller.BaseController { _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getERC20BalanceOf, getERC20BalanceOf); this.poll(); } ++ + /** + * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO + * Resets to the default state @@ -396,10 +248,11 @@ index 995ec6b..84408ac 100644 + state.contractBalances = {}; + }); + } ++ /** * Allows controller to update tracked tokens contract balances. */ -@@ -107,20 +118,27 @@ var TokenBalancesController = class extends _basecontroller.BaseController { +@@ -107,20 +120,27 @@ var TokenBalancesController = class extends _basecontroller.BaseController { "AccountsController:getSelectedAccount" ); const newContractBalances = {}; @@ -437,7 +290,7 @@ index 995ec6b..84408ac 100644 } }; _handle = new WeakMap(); -@@ -128,6 +146,7 @@ _getERC20BalanceOf = new WeakMap(); +@@ -128,6 +148,7 @@ _getERC20BalanceOf = new WeakMap(); _interval = new WeakMap(); _tokens = new WeakMap(); _disabled = new WeakMap(); @@ -445,27 +298,6 @@ index 995ec6b..84408ac 100644 var TokenBalancesController_default = TokenBalancesController; -diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-RPQ737HL.js b/node_modules/@metamask/assets-controllers/dist/chunk-RPQ737HL.js -index b14af30..ee7141e 100644 ---- a/node_modules/@metamask/assets-controllers/dist/chunk-RPQ737HL.js -+++ b/node_modules/@metamask/assets-controllers/dist/chunk-RPQ737HL.js -@@ -173,6 +173,16 @@ var TokenRatesController = class extends _pollingcontroller.StaticIntervalPollin - _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _subscribeToNetworkStateChange, subscribeToNetworkStateChange_fn).call(this); - _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _subscribeToAccountChange, subscribeToAccountChange_fn).call(this); - } -+ -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset() { -+ this.update((state) => { -+ state.marketData = {}; -+ }); -+ } - /** - * Allows controller to make active and passive polling requests - */ diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-T5ZX5BV7.js b/node_modules/@metamask/assets-controllers/dist/chunk-T5ZX5BV7.js index 9c89a65..2ac17ba 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-T5ZX5BV7.js @@ -519,35 +351,44 @@ index 9c89a65..2ac17ba 100644 } finally { releaseLock(); } +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-TPUVGGNO.js b/node_modules/@metamask/assets-controllers/dist/chunk-TPUVGGNO.js +index baaf7d0..cfefb60 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-TPUVGGNO.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-TPUVGGNO.js +@@ -189,6 +189,20 @@ var TokensController = class extends _basecontroller.BaseController { + } + ); + } ++ ++ /** ++ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO ++ * Resets to the default state ++ */ ++ reset() { ++ this.update((state) => { ++ state.allTokens = {}; ++ state.allIgnoredTokens = {}; ++ state.ignoredTokens = []; ++ state.tokens = []; ++ }); ++ } ++ + /** + * Adds a token to the stored token list. + * diff --git a/node_modules/@metamask/assets-controllers/dist/types/NftController.d.ts b/node_modules/@metamask/assets-controllers/dist/types/NftController.d.ts -index b663e26..09f72c6 100644 +index a69c32d..077e2db 100644 --- a/node_modules/@metamask/assets-controllers/dist/types/NftController.d.ts +++ b/node_modules/@metamask/assets-controllers/dist/types/NftController.d.ts -@@ -7,7 +7,7 @@ import type { PreferencesControllerStateChangeEvent } from '@metamask/preference - import type { Hex } from '@metamask/utils'; - import type { AssetsContractController } from './AssetsContractController'; - import { Source } from './constants'; --import type { Collection, Attributes, LastSale } from './NftDetectionController'; -+import type { Collection, Attributes, LastSale, TopBid } from './NftDetectionController'; - type NFTStandardType = 'ERC721' | 'ERC1155'; - type SuggestedNftMeta = { - asset: { -@@ -108,11 +108,13 @@ export type NftMetadata = { +@@ -108,6 +108,7 @@ export type NftMetadata = { creator?: string; transactionId?: string; tokenURI?: string | null; + error?: string; collection?: Collection; address?: string; -- attributes?: Attributes; -+ attributes?: Attributes[]; - lastSale?: LastSale; - rarityRank?: string; -+ topBid?: TopBid; - }; - /** - * @type NftControllerState -@@ -419,6 +421,11 @@ export declare class NftController extends BaseController; @@ -560,61 +401,6 @@ index b663e26..09f72c6 100644 export default NftController; //# sourceMappingURL=NftController.d.ts.map \ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/types/NftDetectionController.d.ts b/node_modules/@metamask/assets-controllers/dist/types/NftDetectionController.d.ts -index c645b3a..0c2a87c 100644 ---- a/node_modules/@metamask/assets-controllers/dist/types/NftDetectionController.d.ts -+++ b/node_modules/@metamask/assets-controllers/dist/types/NftDetectionController.d.ts -@@ -227,7 +227,39 @@ export type Attributes = { - topBidValue?: number | null; - createdAt?: string; - }; --export type Collection = { -+export type GetCollectionsResponse = { -+ collections: CollectionResponse[]; -+ }; -+export type CollectionResponse = { -+ id?: string; -+ openseaVerificationStatus?: string; -+ contractDeployedAt?: string; -+ creator?: string; -+ ownerCount?: string; -+ topBid?: TopBid & { -+ sourceDomain?: string; -+ }; -+}; -+export type FloorAskCollection = { -+ id?: string; -+ price?: Price; -+ maker?: string; -+ kind?: string; -+ validFrom?: number; -+ validUntil?: number; -+ source?: SourceCollection; -+ rawData?: Metadata; -+ isNativeOffChainCancellable?: boolean; -+}; -+ -+export type SourceCollection = { -+ id: string; -+ domain: string; -+ name: string; -+ icon: string; -+ url: string; -+}; -+export type TokenCollection = { - id?: string; - name?: string; - slug?: string; -@@ -243,7 +275,9 @@ export type Collection = { - floorAskPrice?: Price; - royaltiesBps?: number; - royalties?: Royalties[]; -+ floorAsk?: FloorAskCollection; - }; -+export type Collection = TokenCollection & CollectionResponse; - export type Royalties = { - bps?: number; - recipient?: string; diff --git a/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts b/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts index 45d58f8..ce24723 100644 --- a/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts diff --git a/yarn.lock b/yarn.lock index ddd7f1392d1..6529332cb94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4320,7 +4320,7 @@ "@metamask/controller-utils" "^11.3.0" "@metamask/utils" "^9.1.0" -"@metamask/approval-controller@^7.0.0", "@metamask/approval-controller@^7.0.2", "@metamask/approval-controller@^7.1.0": +"@metamask/approval-controller@^7.0.0", "@metamask/approval-controller@^7.0.1", "@metamask/approval-controller@^7.0.2", "@metamask/approval-controller@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-7.1.0.tgz#34b07bc4eaf6938b15f9d915c6885d4a5c0a5205" integrity sha512-dhqUeX8wMzW88U+Vgr7oKf0Vouol10ncB3lxmvWyC1VZJhSOdO3VUkn0tH1lzt3ybxYVMOkPaB3gfdksfnNRyA== @@ -4330,10 +4330,10 @@ "@metamask/utils" "^9.1.0" nanoid "^3.1.31" -"@metamask/assets-controllers@^35.0.0": - version "35.0.0" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-35.0.0.tgz#564b1b91f199863090221cad256af1c6061d4ea8" - integrity sha512-OAkh89NZKMPihBSCB4MsMXzavvQ01myWHvLsDtfhRMrV5cXqNL5NynCm5Q2hz2X4JjUyW/Kdqc69nBBjVj++oA== +"@metamask/assets-controllers@^36.0.0": + version "36.0.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-36.0.0.tgz#17f48d65b0b444aae742b8221fd16513da148458" + integrity sha512-leYjYcH6TIxDzrQebJjeBc02H2YMx9JfWsGQ9tZHydB0BF4st3pDUZYneVZRfR2XlIAyoVS7cSgvfdXZ+tmstQ== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/address" "^5.7.0" @@ -4342,18 +4342,18 @@ "@ethersproject/providers" "^5.7.0" "@metamask/abi-utils" "^2.0.2" "@metamask/accounts-controller" "^17.2.0" - "@metamask/approval-controller" "^7.0.0" - "@metamask/base-controller" "^6.0.0" + "@metamask/approval-controller" "^7.0.1" + "@metamask/base-controller" "^6.0.1" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.0.0" + "@metamask/controller-utils" "^11.0.1" "@metamask/eth-query" "^4.0.0" "@metamask/keyring-controller" "^17.1.0" "@metamask/metamask-eth-abis" "^3.1.1" "@metamask/network-controller" "^20.0.0" "@metamask/polling-controller" "^9.0.0" "@metamask/preferences-controller" "^13.0.0" - "@metamask/rpc-errors" "^6.2.1" - "@metamask/utils" "^8.3.0" + "@metamask/rpc-errors" "^6.3.1" + "@metamask/utils" "^9.0.0" "@types/bn.js" "^5.1.5" "@types/uuid" "^8.3.0" async-mutex "^0.5.0" @@ -4388,7 +4388,7 @@ "@metamask/utils" "^8.3.0" immer "^9.0.6" -"@metamask/base-controller@^6.0.0", "@metamask/base-controller@^6.0.2": +"@metamask/base-controller@^6.0.0", "@metamask/base-controller@^6.0.1", "@metamask/base-controller@^6.0.2": version "6.0.3" resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-6.0.3.tgz#9bb4e74234c1de5f99842c343ffa053c08055db1" integrity sha512-neUqsCXRT6QYcZO51y6Y5u9NPTHuxgNsW5Z4h///o1gDdV8lBeIG/b1ne+QPK422DZMAm4ChnkG1DDNf4PkErw== @@ -4438,7 +4438,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.2.0.tgz#277764d0d56e37180ae7644a9d11eb96295b36fc" integrity sha512-SM6A4C7vXNbVpgMTX67kfW8QWvu3eSXxMZlY5PqZBTkvri1s9zgQ0uwRkK5r2VXNEoVmXCDnnEX/tX5EzzgNUQ== -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.0.2", "@metamask/controller-utils@^11.3.0": +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.0.1", "@metamask/controller-utils@^11.0.2", "@metamask/controller-utils@^11.3.0": version "11.3.0" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.3.0.tgz#530fd22289f717b752b4a7b6e504e1f2911b30a4" integrity sha512-5b+Jg9sKKESzvQcuipHC1D7KSh98MVIi7hXQUk7iX+YVMl4KoKDv94Bl+li8g+jCBshMOV9bRMRh25/hdEvTZQ== From a533326c94d9e02f7289f8c370223bfb9d5c9a53 Mon Sep 17 00:00:00 2001 From: legobeat <109787230+legobeat@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:56:57 +0000 Subject: [PATCH 05/19] fix(deps): eth-json-rpc-filters@4.2.2->^5.1.0 (#11975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bump `eth-json-rpc-filters` ## **Related issues** - #11973 #### Blocking - #11968 ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- package.json | 2 +- yarn.lock | 28 +++++----------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 1540430eef9..194d60f6083 100644 --- a/package.json +++ b/package.json @@ -239,7 +239,7 @@ "eciesjs": "^0.3.15", "eth-block-tracker": "^7.0.1", "eth-ens-namehash": "2.0.8", - "eth-json-rpc-filters": "4.2.2", + "eth-json-rpc-filters": "^5.1.0", "eth-json-rpc-middleware": "4.3.0", "eth-url-parser": "1.0.4", "ethereumjs-abi": "0.6.6", diff --git a/yarn.lock b/yarn.lock index 6529332cb94..170808a4521 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17014,14 +17014,13 @@ eth-json-rpc-errors@^1.0.1: dependencies: fast-safe-stringify "^2.0.6" -eth-json-rpc-filters@4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz#eb35e1dfe9357ace8a8908e7daee80b2cd60a10d" - integrity sha512-DGtqpLU7bBg63wPMWg1sCpkKCf57dJ+hj/k3zF26anXMzkmtSBDExL8IhUu7LUd34f0Zsce3PYNO2vV2GaTzaw== +eth-json-rpc-filters@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-5.1.0.tgz#f0c2aeaec2a45e2dc6ca1b9843d8e85447821427" + integrity sha512-fos+9xmoa1A2Ytsc9eYof17r81BjdJOUcGcgZn4K/tKdCCTb+a8ytEtwlu1op5qsXFDlgGmstTELFrDEc89qEQ== dependencies: "@metamask/safe-event-emitter" "^2.0.0" async-mutex "^0.2.6" - eth-json-rpc-middleware "^6.0.0" eth-query "^2.1.2" json-rpc-engine "^6.1.0" pify "^5.0.0" @@ -17046,23 +17045,6 @@ eth-json-rpc-middleware@4.3.0: pify "^3.0.0" safe-event-emitter "^1.0.1" -eth-json-rpc-middleware@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-6.0.0.tgz#4fe16928b34231a2537856f08a5ebbc3d0c31175" - integrity sha512-qqBfLU2Uq1Ou15Wox1s+NX05S9OcAEL4JZ04VZox2NS0U+RtCMjSxzXhLFWekdShUPZ+P8ax3zCO2xcPrp6XJQ== - dependencies: - btoa "^1.2.1" - clone "^2.1.1" - eth-query "^2.1.2" - eth-rpc-errors "^3.0.0" - eth-sig-util "^1.4.2" - ethereumjs-util "^5.1.2" - json-rpc-engine "^5.3.0" - json-stable-stringify "^1.0.1" - node-fetch "^2.6.1" - pify "^3.0.0" - safe-event-emitter "^1.0.1" - eth-method-registry@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eth-method-registry/-/eth-method-registry-4.0.0.tgz#2c2b78d5711dbd8f2c8f5e2de85a7a05f9769800" @@ -21097,7 +21079,7 @@ json-pointer@^0.6.2: dependencies: foreach "^2.0.4" -json-rpc-engine@^5.1.3, json-rpc-engine@^5.3.0: +json-rpc-engine@^5.1.3: version "5.4.0" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz#75758609d849e1dba1e09021ae473f3ab63161e5" integrity sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g== From dfb411fbe79777969f47a205c0678c1cd091f850 Mon Sep 17 00:00:00 2001 From: legobeat <109787230+legobeat@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:07:46 +0000 Subject: [PATCH 06/19] chore: update setup docs and google-services-example.json (#11758) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - add flask and qa entries to `google-services-example.json` - add generated file to `.gitignore` - docs: no wrap base64 output - docs: update README.md with `GOOGLE_SERVICES_B64` docs ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .gitignore | 3 +++ README.md | 9 ++----- android/app/google-services-example.json | 32 +++++++++++++++++++----- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 18fa57f9d47..94e55b9bae8 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,6 @@ app/util/termsOfUse/termsOfUseContent.ts docs/assets/termsOfUse.html /app/images/branding + +# build metadata +android/app/src/main/assets/modules.json diff --git a/README.md b/README.md index 2c4a6de0445..2d19b29c7f8 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,12 @@ cd metamask-mobile Before running the app, keep in mind that MetaMask uses FCM (Firebase Cloud Message) to empower communications. Based on this, as an external contributor you would preferably need to provide your own FREE Firebase project config file with a matching client for package name `io.metamask`, and update your `google-services.json` file in the `android/app` directory as well your `.env` files (`.ios.env`, `.js.env`, `.android.env`), adding `GOOGLE_SERVICES_B64` variable depending on the environment you are running the app (ios/android). -The value you should provide to `GOOGLE_SERVICES_B64` is the base64 encoded version of your Firebase project config file, which can be generated as follows: +ATTENTION: In case you don't provide your own Firebase project config file, you can make use of a mock file at `android/app/google-services-example.json`, following the steps below from the root of the project: ```bash -base64 -i ./android/app/google-services-example.json +echo "export GOOGLE_SERVICES_B64=\"$(base64 -w0 -i ./android/app/google-services-example.json)\"" | tee -a .js.env .ios.env .android.env ``` -Copy the result to your clipboard and paste it in the `GOOGLE_SERVICES_B64` variable in the `.env` file you are running the app. - -> [!CAUTION] -> In case you don't provide your own Firebase project config file, you will face the error `No matching client found for package name 'io.metamask'`. - You can make usage of a mock file at `android/app/google-services-example.json`, following the same steps above from the root of the project. In case of any doubt, please follow the instructions in the link below to get your Firebase project config file. diff --git a/android/app/google-services-example.json b/android/app/google-services-example.json index f7765f0ad2a..02ea42cea0e 100644 --- a/android/app/google-services-example.json +++ b/android/app/google-services-example.json @@ -7,11 +7,7 @@ }, "client": [ { - "api_key": [ - { - "current_key": "" - } - ], + "api_key": [{ "current_key": "" }], "client_info": { "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", "client_id": "android:io.metamask", @@ -21,7 +17,31 @@ "certificate_hash": [] } } + }, + { + "api_key": [{ "current_key": "" }], + "client_info": { + "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", + "client_id": "android:io.metamask.qa", + "client_type": 1, + "android_client_info": { + "package_name": "io.metamask.qa", + "certificate_hash": [] + } + } + }, + { + "api_key": [{ "current_key": "" }], + "client_info": { + "mobilesdk_app_id": "1:123456789000:android:f1bf012572b04063", + "client_id": "android:io.metamask.flask", + "client_type": 1, + "android_client_info": { + "package_name": "io.metamask.flask", + "certificate_hash": [] + } + } } ], "configuration_version": "1" -} \ No newline at end of file +} From a9fc0dddd520fb4cdf5370d87bc9a6e891405ef6 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Wed, 23 Oct 2024 15:20:08 +0200 Subject: [PATCH 07/19] fix: fix render detected tokens section (#11923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fix render detected tokens section ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/11890 ## **Manual testing steps** 1. Import an account with tokens 2. Click on import detected tokens 3. Once all tokens are imported you should not see the section telling you "0 tokens found in this account" ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/ccc86b0f-fdbd-4012-8df8-5f6d19072cea ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx | 4 +++- app/components/UI/Tokens/index.test.tsx | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx index 663570c6c67..92093347577 100644 --- a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx +++ b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx @@ -25,6 +25,7 @@ import { import { getDecimalChainId } from '../../../../../util/networks'; import { selectChainId } from '../../../../../selectors/networkController'; import { TokenI } from '../../types'; +import { selectUseTokenDetection } from '../../../../../selectors/preferencesController'; interface TokenListFooterProps { tokens: TokenI[]; @@ -45,6 +46,7 @@ export const TokenListFooter = ({ const [isNetworkRampSupported, isNativeTokenRampSupported] = useRampNetwork(); const detectedTokens = useSelector(selectDetectedTokens); + const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); const chainId = useSelector(selectChainId); const styles = createStyles(colors); @@ -68,7 +70,7 @@ export const TokenListFooter = ({ return ( <> {/* renderTokensDetectedSection */} - {detectedTokens && ( + {detectedTokens?.length !== 0 && isTokenDetectionEnabled && ( { backgroundState: { ...backgroundState, TokensController: { + detectedTokens: [], tokens: [ { name: 'Link', From cce5d5c2ec7a55ca2e4f3f511ec0c5c2791bc03c Mon Sep 17 00:00:00 2001 From: tommasini <46944231+tommasini@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:16:20 +0100 Subject: [PATCH 08/19] chore: revert webview focused work (#11976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fix disconnect in, in-app browser ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/components/Views/BrowserTab/index.js | 14 ----- app/util/browser/webViewFocus.test.ts | 74 ------------------------ app/util/browser/webViewFocus.ts | 32 ---------- 3 files changed, 120 deletions(-) delete mode 100644 app/util/browser/webViewFocus.test.ts delete mode 100644 app/util/browser/webViewFocus.ts diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 501058acb1d..b602a19466c 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -108,8 +108,6 @@ import { useMetrics } from '../../../components/hooks/useMetrics'; import { trackDappViewedEvent } from '../../../util/metrics'; import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; import { selectPermissionControllerState } from '../../../selectors/snaps/permissionController'; -import { useIsFocused } from '@react-navigation/native'; -import handleWebViewFocus from '../../../util/browser/webViewFocus'; import { isTest } from '../../../util/test/utils.js'; import { EXTERNAL_LINK_TYPE } from '../../../constants/browser'; @@ -277,11 +275,9 @@ export const BrowserTab = (props) => { const [blockedUrl, setBlockedUrl] = useState(undefined); const [ipfsBannerVisible, setIpfsBannerVisible] = useState(false); const [isResolvedIpfsUrl, setIsResolvedIpfsUrl] = useState(false); - const previousChainIdRef = useRef('0x1'); const webviewRef = useRef(null); const blockListType = useRef(''); const allowList = useRef([]); - const isFocused = useIsFocused(); const url = useRef(''); const title = useRef(''); @@ -720,16 +716,6 @@ export const BrowserTab = (props) => { }; }, [goBack, isTabActive, props.navigation]); - useEffect(() => { - handleWebViewFocus({ - webviewRef, - isFocused, - chainId: props.chainId, - previousChainId: previousChainIdRef.current, - }); - previousChainIdRef.current = props.chainId; - }, [webviewRef, isFocused, props.chainId]); - /** * Inject home page scripts to get the favourites and set analytics key */ diff --git a/app/util/browser/webViewFocus.test.ts b/app/util/browser/webViewFocus.test.ts deleted file mode 100644 index c471fca34af..00000000000 --- a/app/util/browser/webViewFocus.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Hex } from '@metamask/utils'; -import handleWebViewFocus from './webViewFocus'; - -interface WebViewRef { - injectJavaScript: (script: string) => void; - stopLoading: () => void; - reload: () => void; -} - -describe('handleWebViewFocus', () => { - let webviewRef: React.RefObject; - let stopLoading: jest.Mock; - let reload: jest.Mock; - - beforeEach(() => { - stopLoading = jest.fn(); - reload = jest.fn(); - webviewRef = { - current: { - injectJavaScript: jest.fn(), - stopLoading, - reload, - }, - }; - }); - - it('should reload the webview if chainId has changed', () => { - handleWebViewFocus({ - webviewRef, - isFocused: true, - chainId: '0x1' as Hex, - previousChainId: '0x2' as Hex, - }); - - expect(reload).toHaveBeenCalled(); - expect(stopLoading).not.toHaveBeenCalled(); - }); - - it('should not reload the webview if chainId has not changed', () => { - handleWebViewFocus({ - webviewRef, - isFocused: true, - chainId: '0x1' as Hex, - previousChainId: '0x1' as Hex, - }); - - expect(reload).not.toHaveBeenCalled(); - expect(stopLoading).not.toHaveBeenCalled(); - }); - - it('should do nothing if webviewRef.current is null', () => { - handleWebViewFocus({ - webviewRef: { current: null }, - isFocused: true, - chainId: '0x1' as Hex, - previousChainId: '0x2' as Hex, - }); - - expect(stopLoading).not.toHaveBeenCalled(); - expect(reload).not.toHaveBeenCalled(); - }); - - it('should stop loading if webview is not focused', () => { - handleWebViewFocus({ - webviewRef, - isFocused: false, - chainId: '0x1' as Hex, - previousChainId: '0x1' as Hex, - }); - - expect(stopLoading).toHaveBeenCalled(); - expect(reload).not.toHaveBeenCalled(); - }); -}); diff --git a/app/util/browser/webViewFocus.ts b/app/util/browser/webViewFocus.ts deleted file mode 100644 index bcf5430f08b..00000000000 --- a/app/util/browser/webViewFocus.ts +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { Hex } from '@metamask/utils'; - -interface WebViewFocus { - webviewRef: React.RefObject<{ - injectJavaScript: (script: string) => void; - stopLoading: () => void; - reload: () => void; - }>; - isFocused: boolean; - chainId: Hex; - previousChainId?: Hex; -} - -const handleWebViewFocus = ({ - webviewRef, - isFocused, - chainId, - previousChainId, -}: WebViewFocus) => { - if (webviewRef.current) { - if (!isFocused) { - webviewRef.current.stopLoading(); - } - - if (previousChainId && previousChainId !== chainId) { - webviewRef.current.reload(); - } - } -}; - -export default handleWebViewFocus; From 3db08153e0f0973f94402e657966ec5c3bb73180 Mon Sep 17 00:00:00 2001 From: Vince Howard Date: Wed, 23 Oct 2024 08:39:13 -0600 Subject: [PATCH 09/19] feat: extend PickerNetwork component functionality (#11856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR changes the `PickerNetwork` to allow the network name to be hidden. These changes are necessary to accommodate the [Header Update](https://www.figma.com/design/aMYisczaJyEsYl1TYdcPUL/Portfolio-View?m=auto&node-id=5019-59596&t=PAdxL1bg2Mk08dSk-1) design requirements coming up. However the aim is to introduce very little change (other than padding) to what is pre-existing but by passing in the prop of `hideNetwork` it changes into the future design ## **Related issues** Related: [#11763](https://github.com/MetaMask/metamask-mobile/pull/11763) ## **Manual testing steps** 1. View any instance of `PickerNetwork` being used. For example the `PickerNetwork` in header in the home screen ## **Screenshots/Recordings** before After `hideNetwork=false` the default after (no prop) After `hideNetwork=true` after (prop) ### **Before** NA ### **After** NA ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/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. --- .../PickerNetwork/PickerNetwork.stories.tsx | 10 ++-- .../PickerNetwork/PickerNetwork.styles.ts | 3 ++ .../PickerNetwork/PickerNetwork.test.tsx | 34 ++++++++++++- .../Pickers/PickerNetwork/PickerNetwork.tsx | 35 +++++++------ .../PickerNetwork/PickerNetwork.types.ts | 4 ++ .../Pickers/PickerNetwork/README.md | 40 ++++++++++----- .../__snapshots__/PickerNetwork.test.tsx.snap | 46 +++++++++-------- .../__snapshots__/ManageNetworks.test.js.snap | 48 ++++++++++-------- .../__snapshots__/index.test.tsx.snap | 48 ++++++++++-------- .../NetworkVerificationInfo.test.tsx.snap | 50 ++++++++++--------- .../NetworkSwitcher.test.tsx.snap | 48 ++++++++++-------- .../AccountPermissions.test.tsx.snap | 50 ++++++++++--------- .../__snapshots__/index.test.tsx.snap | 48 ++++++++++-------- .../Wallet/__snapshots__/index.test.tsx.snap | 50 ++++++++++--------- 14 files changed, 304 insertions(+), 210 deletions(-) diff --git a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx index 2ff8a42fd2f..a3ade2544ae 100644 --- a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx +++ b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/display-name */ -import React from 'react'; +import React, { ComponentProps } from 'react'; // Internal dependencies. import { default as PickerNetworkComponent } from './PickerNetwork'; @@ -13,14 +13,16 @@ const PickerNetworkMeta = { control: { type: 'text' }, defaultValue: SAMPLE_PICKERNETWORK_PROPS.label, }, + hideNetworkName: { + control: { type: 'boolean' }, + defaultValue: false, + }, }, }; export default PickerNetworkMeta; export const PickerNetwork = { - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - render: (args: any) => ( + render: (args: ComponentProps) => ( { expect(queryByTestId(PICKERNETWORK_ARROW_TESTID)).toBeNull(); }); + + it('hides network name and shows icon when hideNetworkName is true', () => { + const { queryByTestId } = render( + , + ); + + expect( + queryByTestId(WalletViewSelectorsIDs.NAVBAR_NETWORK_TEXT), + ).toBeNull(); + }); + + it('calls onPress when pressed', () => { + const onPress = jest.fn(); + const { getByTestId } = render( + , + ); + + fireEvent.press(getByTestId(PICKERNETWORK_ARROW_TESTID)); + + expect(onPress).toHaveBeenCalled(); + }); }); diff --git a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx index 9c4dd1f150a..1b4642ba967 100644 --- a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx +++ b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx @@ -2,7 +2,7 @@ // Third party dependencies. import React from 'react'; -import { TouchableOpacity } from 'react-native'; +import { TouchableOpacity, View } from 'react-native'; // External dependencies. import Avatar, { AvatarSize, AvatarVariant } from '../../Avatars/Avatar'; @@ -21,26 +21,31 @@ const PickerNetwork = ({ style, label, imageSource, + hideNetworkName, ...props }: PickerNetworkProps) => { const { styles } = useStyles(stylesheet, { style }); return ( - - - {label} - + + + + {hideNetworkName ? null : ( + + {label} + + )} {onPress && ( void; + /** + * Whether to hide the network name. + */ + hideNetworkName?: boolean; } export type PickerNetworkStyleSheetVars = Pick; diff --git a/app/component-library/components/Pickers/PickerNetwork/README.md b/app/component-library/components/Pickers/PickerNetwork/README.md index f7e1dccc1f6..63cafd3a338 100644 --- a/app/component-library/components/Pickers/PickerNetwork/README.md +++ b/app/component-library/components/Pickers/PickerNetwork/README.md @@ -6,13 +6,13 @@ PickerNetwork is a component that renders an avatar based on the user selected n This component extends `TouchableOpacityProps` from React Native's [TouchableOpacity](https://reactnative.dev/docs/touchableopacity) component. -### `imageSource` +### `onPress` -Optional network image from either a local or remote source. +Callback to trigger when pressed. -| TYPE | REQUIRED | -| :-------------------------------------------------------------------- | :------------------------------------------------------ | -| [ImageSourcePropType](https://reactnative.dev/docs/image#imagesource) | Yes | +| TYPE | REQUIRED | +| :-------------------------------------------------- | :------------------------------------------------------ | +| function | No | ### `label` @@ -22,21 +22,37 @@ Network label to display. | :-------------------------------------------------- | :------------------------------------------------------ | | string | Yes | -### `onPress` +### `imageSource` + +The source for the network avatar image. -Callback to trigger when picker is pressed. +| TYPE | REQUIRED | +| :-------------------------------------------------- | :------------------------------------------------------ | +| ImageSourcePropType | No | + +### `hideNetworkName` + +Whether to hide the network name text. | TYPE | REQUIRED | | :-------------------------------------------------- | :------------------------------------------------------ | -| function | Yes | +| boolean | No | + +## Usage ```javascript -// Replace import with relative path. import PickerNetwork from 'app/component-library/components/Pickers/PickerNetwork'; console.log('Network picker pressed')} + label="Ethereum" + imageSource={require('./ethereum-logo.png')} />; ``` + +## Notes + +- The component uses an `Avatar` component to display the network icon. +- If `onPress` is provided, a dropdown arrow icon will be displayed. +- The network name can be hidden using the `hideNetworkName` prop. +- The component uses custom styles defined in `PickerNetwork.styles.ts`. \ No newline at end of file diff --git a/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap b/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap index c9c4f7fe797..f59fab74933 100644 --- a/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap +++ b/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap @@ -16,34 +16,38 @@ exports[`PickerNetwork renders correctly 1`] = ` } > - + > + + - - E - + + E + + - - T - + + T + + - + > + + - - C - + + C + + - + > + + - - E - + + E + + - + > + + Date: Wed, 23 Oct 2024 10:08:02 -0500 Subject: [PATCH 10/19] feat: add live staking data with geo-blocking support (#11891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR replaces mock staking data with actual staking positions for users and integrates the Staking SDK. It also implements geo-blocking based on user location and displays real vault data. Changes made: - Integrated the Staking SDK to fetch and display actual user staking positions - Implemented geo-blocking to restrict access based on region - Updated the UI to display live vault data Additional Context: - Replaced mock data that was previously used in staking-related views - Users will now see their actual staking positions within the app - Geo-blocking ensures compliance with regional restrictions - Vault data is dynamically fetched and displayed in real-time ## **Related issues** Fixes: [STAKE 785](https://consensyssoftware.atlassian.net/jira/software/projects/STAKE/boards/550/backlog?selectedIssue=STAKE-785), [STAKE 806](https://consensyssoftware.atlassian.net/browse/STAKE-806) ## **Manual testing steps** 1. Enable the staking feature flag in js.env locally. 2. Log in as a user with staking positions and verify that the staking section shows live data. 3. Access the staking feature from a geo-blocked region to verify the geo-blocking functionality. 4. Ensure that vault data is correctly displayed based on the user’s chain and staking status. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/4bf1589c-7cda-441f-ab44-05ada1ce80aa ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/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. --- .../UI/AssetOverview/Balance/Balance.tsx | 6 +- .../StakingEarnings/StakingEarnings.test.tsx | 32 --- .../TokenDetails/TokenDetails.tsx | 11 +- .../StakeConfirmationView.test.tsx | 15 +- .../StakeConfirmationView.tsx | 14 +- .../StakeConfirmationView.types.ts | 3 + .../StakeConfirmationView.test.tsx.snap | 8 +- .../StakeInputView/StakeInputView.test.tsx | 28 ++- .../Views/StakeInputView/StakeInputView.tsx | 17 +- .../StakeInputView.test.tsx.snap | 2 +- .../UnstakeInputView.test.tsx | 42 +++- .../UnstakeInputView/UnstakeInputView.tsx | 12 +- .../UnstakeInputView.types.ts | 9 + .../UnstakeInputView.test.tsx.snap | 2 +- .../StakingBalance => __mocks__}/mockData.ts | 35 ++- .../components/EstimatedAnnualRewardsCard.tsx | 19 +- .../StakeButton/StakeButton.test.tsx | 84 +++++++ .../components}/StakeButton/index.tsx | 34 ++- .../StakingBalance/StakingBalance.test.tsx | 87 ++++++- .../StakingBalance/StakingBalance.tsx | 100 +++++--- .../StakingButtons/StakingButtons.tsx | 21 +- .../StakingBalance.test.tsx.snap | 151 +----------- .../ConfirmationFooter.test.tsx | 7 + .../FooterButtonGroup.test.tsx | 12 +- .../FooterButtonGroup/FooterButtonGroup.tsx | 27 ++- .../FooterButtonGroup.test.tsx.snap | 4 +- .../ConfirmationFooter.test.tsx.snap | 2 +- .../RewardsCard/RewardsCard.test.tsx | 8 +- .../RewardsCard/RewardsCard.tsx | 3 +- .../StakingEarnings.styles.tsx | 2 +- .../StakingEarnings/StakingEarnings.test.tsx | 77 +++++++ .../StakingEarnings.test.tsx.snap | 8 +- .../components}/StakingEarnings/index.tsx | 81 ++++--- .../UI/Stake/hooks/useBalance.test.tsx | 109 +++++++++ app/components/UI/Stake/hooks/useBalance.ts | 36 ++- .../UI/Stake/hooks/usePooledStakes.test.tsx | 217 ++++++++++++++++++ .../UI/Stake/hooks/usePooledStakes.ts | 120 ++++++++++ .../UI/Stake/hooks/useStakeContext.ts | 9 +- .../UI/Stake/hooks/useStakingChain.test.tsx | 59 +++++ .../UI/Stake/hooks/useStakingChain.ts | 16 ++ .../Stake/hooks/useStakingEarnings.test.tsx | 101 ++++++++ .../UI/Stake/hooks/useStakingEarnings.ts | 56 +++++ .../hooks/useStakingEligibility.test.tsx | 128 +++++++++++ .../UI/Stake/hooks/useStakingEligibility.ts | 50 ++++ .../UI/Stake/hooks/useStakingInput.ts | 51 ++-- .../UI/Stake/hooks/useVaultData.test.tsx | 105 +++++++++ app/components/UI/Stake/hooks/useVaultData.ts | 59 +++++ .../UI/Stake/sdk/stakeSdkProvider.test.tsx | 10 + .../Tokens/TokenList/TokenListItem/index.tsx | 9 +- .../Tokens/__snapshots__/index.test.tsx.snap | 6 +- app/components/UI/Tokens/index.test.tsx | 42 +++- locales/languages/en.json | 4 +- 52 files changed, 1766 insertions(+), 384 deletions(-) delete mode 100644 app/components/UI/AssetOverview/StakingEarnings/StakingEarnings.test.tsx create mode 100644 app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.types.ts rename app/components/UI/Stake/{components/StakingBalance => __mocks__}/mockData.ts (76%) create mode 100644 app/components/UI/Stake/components/StakeButton/StakeButton.test.tsx rename app/components/UI/{Tokens/TokenList => Stake/components}/StakeButton/index.tsx (74%) rename app/components/UI/{AssetOverview => Stake/components}/StakingEarnings/StakingEarnings.styles.tsx (92%) create mode 100644 app/components/UI/Stake/components/StakingEarnings/StakingEarnings.test.tsx rename app/components/UI/{AssetOverview => Stake/components}/StakingEarnings/__snapshots__/StakingEarnings.test.tsx.snap (98%) rename app/components/UI/{AssetOverview => Stake/components}/StakingEarnings/index.tsx (59%) create mode 100644 app/components/UI/Stake/hooks/useBalance.test.tsx create mode 100644 app/components/UI/Stake/hooks/usePooledStakes.test.tsx create mode 100644 app/components/UI/Stake/hooks/usePooledStakes.ts create mode 100644 app/components/UI/Stake/hooks/useStakingChain.test.tsx create mode 100644 app/components/UI/Stake/hooks/useStakingChain.ts create mode 100644 app/components/UI/Stake/hooks/useStakingEarnings.test.tsx create mode 100644 app/components/UI/Stake/hooks/useStakingEarnings.ts create mode 100644 app/components/UI/Stake/hooks/useStakingEligibility.test.tsx create mode 100644 app/components/UI/Stake/hooks/useStakingEligibility.ts create mode 100644 app/components/UI/Stake/hooks/useVaultData.test.tsx create mode 100644 app/components/UI/Stake/hooks/useVaultData.ts diff --git a/app/components/UI/AssetOverview/Balance/Balance.tsx b/app/components/UI/AssetOverview/Balance/Balance.tsx index 02c89071051..fed53bd539a 100644 --- a/app/components/UI/AssetOverview/Balance/Balance.tsx +++ b/app/components/UI/AssetOverview/Balance/Balance.tsx @@ -34,7 +34,7 @@ interface BalanceProps { secondaryBalance?: string; } -const NetworkBadgeSource = (chainId: string, ticker: string) => { +export const NetworkBadgeSource = (chainId: string, ticker: string) => { const isMainnet = isMainnetByChainId(chainId); const isLineaMainnet = isLineaMainnetByChainId(chainId); @@ -88,7 +88,9 @@ const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => { {asset.name || asset.symbol} - {isPooledStakingFeatureEnabled() && asset?.isETH && } + {isPooledStakingFeatureEnabled() && asset?.isETH && ( + + )} ); }; diff --git a/app/components/UI/AssetOverview/StakingEarnings/StakingEarnings.test.tsx b/app/components/UI/AssetOverview/StakingEarnings/StakingEarnings.test.tsx deleted file mode 100644 index f9a8c51b5a5..00000000000 --- a/app/components/UI/AssetOverview/StakingEarnings/StakingEarnings.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import StakingEarnings from './'; -import renderWithProvider from '../../../../util/test/renderWithProvider'; -import { strings } from '../../../../../locales/i18n'; - -jest.mock('../../Stake/constants', () => ({ - isPooledStakingFeatureEnabled: jest.fn().mockReturnValue(true), -})); - -const mockNavigate = jest.fn(); - -jest.mock('@react-navigation/native', () => { - const actualReactNavigation = jest.requireActual('@react-navigation/native'); - return { - ...actualReactNavigation, - useNavigation: () => ({ - navigate: mockNavigate, - }), - }; -}); - -describe('Staking Earnings', () => { - it('should render correctly', () => { - const { toJSON, getByText } = renderWithProvider(); - - expect(getByText(strings('stake.your_earnings'))).toBeDefined(); - expect(getByText(strings('stake.annual_rate'))).toBeDefined(); - expect(getByText(strings('stake.lifetime_rewards'))).toBeDefined(); - expect(getByText(strings('stake.estimated_annual_earnings'))).toBeDefined(); - expect(toJSON()).toMatchSnapshot(); - }); -}); diff --git a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx index 476ab653953..368e2352d23 100644 --- a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx +++ b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx @@ -1,5 +1,5 @@ import { zeroAddress } from 'ethereumjs-util'; -import React, { useState } from 'react'; +import React from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; import i18n from '../../../../../locales/i18n'; @@ -21,8 +21,8 @@ import Logger from '../../../../util/Logger'; import TokenDetailsList from './TokenDetailsList'; import MarketDetailsList from './MarketDetailsList'; import { TokenI } from '../../Tokens/types'; -import StakingEarnings from '../StakingEarnings'; import { isPooledStakingFeatureEnabled } from '../../Stake/constants'; +import StakingEarnings from '../../Stake/components/StakingEarnings'; export interface TokenDetails { contractAddress: string | null; @@ -52,9 +52,6 @@ const TokenDetails: React.FC = ({ asset }) => { const currentCurrency = useSelector(selectCurrentCurrency); const tokenContractAddress = safeToChecksumAddress(asset.address); - // TEMP: Remove once component has been implemented. - const [hasStakingPositions] = useState(true); - let tokenMetadata; let marketData; @@ -126,9 +123,7 @@ const TokenDetails: React.FC = ({ asset }) => { return ( - {asset.isETH && - hasStakingPositions && - isPooledStakingFeatureEnabled() && } + {asset.isETH && isPooledStakingFeatureEnabled() && } {(asset.isETH || tokenMetadata) && ( )} diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx index a0c36a05079..86c5c82e1e3 100644 --- a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx +++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx @@ -62,12 +62,25 @@ jest.mock('../../hooks/usePoolStakedDeposit', () => ({ }), })); +jest.mock('../../hooks/usePooledStakes', () => ({ + __esModule: true, + default: () => ({ + refreshPooledStakes: jest.fn(), + }), +})); + describe('StakeConfirmationView', () => { it('render matches snapshot', () => { const props: StakeConfirmationViewProps = { route: { key: '1', - params: { amountWei: '3210000000000000', amountFiat: '7.46' }, + params: { + amountWei: '3210000000000000', + amountFiat: '7.46', + annualRewardRate: '2.5%', + annualRewardsETH: '2.5 ETH', + annualRewardsFiat: '$5000', + }, name: 'params', }, }; diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx index 857752feafb..48a7277df07 100644 --- a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx +++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx @@ -9,17 +9,9 @@ import AccountHeaderCard from '../../components/StakingConfirmation/AccountHeade import RewardsCard from '../../components/StakingConfirmation/RewardsCard/RewardsCard'; import ConfirmationFooter from '../../components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter'; import { StakeConfirmationViewProps } from './StakeConfirmationView.types'; -import { MOCK_GET_VAULT_RESPONSE } from '../../components/StakingBalance/mockData'; import { strings } from '../../../../../../locales/i18n'; import { FooterButtonGroupActions } from '../../components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.types'; -const MOCK_REWARD_DATA = { - REWARDS: { - ETH: '0.13 ETH', - FIAT: '$334.93', - }, -}; - const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking'; const StakeConfirmationView = ({ route }: StakeConfirmationViewProps) => { @@ -47,9 +39,9 @@ const StakeConfirmationView = ({ route }: StakeConfirmationViewProps) => { diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts index 8c723135f4f..20214a0fc52 100644 --- a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts +++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts @@ -3,6 +3,9 @@ import { RouteProp } from '@react-navigation/native'; interface StakeConfirmationViewRouteParams { amountWei: string; amountFiat: string; + annualRewardsETH: string; + annualRewardsFiat: string; + annualRewardRate: string; } export interface StakeConfirmationViewProps { diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap b/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap index 9d14c100f63..eedfba7fe99 100644 --- a/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap +++ b/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap @@ -989,7 +989,7 @@ exports[`StakeConfirmationView render matches snapshot 1`] = ` } testID="label" > - 2.8% + 2.5% @@ -1100,7 +1100,7 @@ exports[`StakeConfirmationView render matches snapshot 1`] = ` } } > - $334.93 + $5000 - 0.13 ETH + 2.5 ETH @@ -1415,7 +1415,7 @@ exports[`StakeConfirmationView render matches snapshot 1`] = ` } } > - Confirm + Continue diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx index a44c0971a53..90639128471 100644 --- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx +++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx @@ -8,6 +8,7 @@ import { BN } from 'ethereumjs-util'; import { Stake } from '../../sdk/stakeSdkProvider'; import { ChainId, PooledStakingContract } from '@metamask/stake-sdk'; import { Contract } from 'ethers'; +import { MOCK_GET_VAULT_RESPONSE } from '../../__mocks__/mockData'; function render(Component: React.ComponentType) { return renderScreen( @@ -89,6 +90,31 @@ jest.mock('../../hooks/useBalance', () => ({ }), })); +const mockVaultData = MOCK_GET_VAULT_RESPONSE; +// Mock hooks + +jest.mock('../../hooks/useStakingEligibility', () => ({ + __esModule: true, + default: () => ({ + isEligible: true, + loading: false, + error: null, + refreshPooledStakingEligibility: jest.fn(), + }), +})); + +jest.mock('../../hooks/useVaultData', () => ({ + __esModule: true, + default: () => ({ + vaultData: mockVaultData, + loading: false, + error: null, + refreshVaultData: jest.fn(), + annualRewardRate: '2.5%', + annualRewardRateDecimal: 0.025, + }), +})); + describe('StakeInputView', () => { it('render matches snapshot', () => { render(StakeInputView); @@ -122,7 +148,7 @@ describe('StakeInputView', () => { fireEvent.press(screen.getByText('2')); - expect(screen.getByText('0.052 ETH')).toBeTruthy(); + expect(screen.getByText('0.05 ETH')).toBeTruthy(); }); }); diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx index 0431e67a77f..9351d57b785 100644 --- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx +++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx @@ -41,6 +41,10 @@ const StakeInputView = () => { handleKeypadChange, calculateEstimatedAnnualRewards, estimatedAnnualRewards, + annualRewardsETH, + annualRewardsFiat, + annualRewardRate, + isLoadingVaultData, } = useStakingInputHandlers(balanceWei); const navigateToLearnMoreModal = () => { @@ -55,9 +59,19 @@ const StakeInputView = () => { params: { amountWei: amountWei.toString(), amountFiat: fiatAmount, + annualRewardsETH, + annualRewardsFiat, + annualRewardRate, }, }); - }, [amountWei, fiatAmount, navigation]); + }, [ + navigation, + amountWei, + fiatAmount, + annualRewardsETH, + annualRewardsFiat, + annualRewardRate, + ]); const balanceText = strings('stake.balance'); @@ -101,6 +115,7 @@ const StakeInputView = () => { - 2.6% + 2.5% ({ selectCurrentCurrency: jest.fn(() => 'USD'), })); +const mockVaultData = MOCK_GET_VAULT_RESPONSE; +const mockPooledStakeData = MOCK_GET_POOLED_STAKES_API_RESPONSE.accounts[0]; + +jest.mock('../../hooks/useStakingEligibility', () => ({ + __esModule: true, + default: () => ({ + isEligible: true, + loading: false, + error: null, + refreshPooledStakingEligibility: jest.fn(), + }), +})); + +jest.mock('../../hooks/useVaultData', () => ({ + __esModule: true, + default: () => ({ + vaultData: mockVaultData, + loading: false, + error: null, + annualRewardRate: '2.5%', + annualRewardRateDecimal: 0.025, + }), +})); + +jest.mock('../../hooks/useBalance', () => ({ + __esModule: true, + default: () => ({ + stakedBalanceWei: mockPooledStakeData.assets, + stakedBalanceFiat: MOCK_STAKED_ETH_ASSET.balanceFiat, + }), +})); + describe('UnstakeInputView', () => { it('render matches snapshot', () => { render(UnstakeInputView); @@ -81,7 +118,7 @@ describe('UnstakeInputView', () => { fireEvent.press(screen.getByText('25%')); - expect(screen.getByText('1.14999')).toBeTruthy(); + expect(screen.getByText('1.44783')).toBeTruthy(); }); }); @@ -96,13 +133,14 @@ describe('UnstakeInputView', () => { render(UnstakeInputView); fireEvent.press(screen.getByText('1')); + expect(screen.getByText('Review')).toBeTruthy(); }); it('displays `Not enough ETH` when input exceeds balance', () => { render(UnstakeInputView); - fireEvent.press(screen.getByText('6')); + fireEvent.press(screen.getByText('8')); expect(screen.queryAllByText('Not enough ETH')).toHaveLength(2); }); }); diff --git a/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx b/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx index c109940d3a5..8adb580eade 100644 --- a/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx +++ b/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx @@ -19,13 +19,14 @@ import { View } from 'react-native'; import useStakingInputHandlers from '../../hooks/useStakingInput'; import styleSheet from './UnstakeInputView.styles'; import InputDisplay from '../../components/InputDisplay'; +import useBalance from '../../hooks/useBalance'; const UnstakeInputView = () => { const title = strings('stake.unstake_eth'); const navigation = useNavigation(); const { styles, theme } = useStyles(styleSheet, {}); - const stakeBalance = '4599964000000000000'; //TODO: Replace with actual balance - STAKE-806 + const { stakedBalanceWei } = useBalance(); const { isEth, @@ -40,10 +41,13 @@ const UnstakeInputView = () => { handleAmountPress, handleKeypadChange, conversionRate, - } = useStakingInputHandlers(new BN(stakeBalance)); + } = useStakingInputHandlers(new BN(stakedBalanceWei)); - const stakeBalanceInEth = renderFromWei(stakeBalance, 5); - const stakeBalanceFiatNumber = weiToFiatNumber(stakeBalance, conversionRate); + const stakeBalanceInEth = renderFromWei(stakedBalanceWei, 5); + const stakeBalanceFiatNumber = weiToFiatNumber( + stakedBalanceWei, + conversionRate, + ); const stakedBalanceText = strings('stake.staked_balance'); const stakedBalanceValue = isEth diff --git a/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.types.ts b/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.types.ts new file mode 100644 index 00000000000..e58d20f58af --- /dev/null +++ b/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.types.ts @@ -0,0 +1,9 @@ +import { RouteProp } from '@react-navigation/native'; + +interface UnstakeInputViewRouteParams { + stakedBalanceWei: string; +} + +export interface UnstakeInputViewProps { + route: RouteProp<{ params: UnstakeInputViewRouteParams }, 'params'>; +} diff --git a/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap b/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap index 5e7927b0b5c..0e1816f6621 100644 --- a/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap +++ b/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap @@ -386,7 +386,7 @@ exports[`UnstakeInputView render matches snapshot 1`] = ` > Staked balance : - 4.59996 ETH + 5.79133 ETH StyleSheet.create({ @@ -42,12 +43,14 @@ const createStyles = (colors: Colors) => interface EstimatedAnnualRewardsCardProps { estimatedAnnualRewards: string; + isLoading?: boolean; onIconPress: () => void; } const EstimatedAnnualRewardsCard = ({ estimatedAnnualRewards, onIconPress, + isLoading = false, }: EstimatedAnnualRewardsCardProps) => { const { colors } = useTheme(); const styles = createStyles(colors); @@ -67,9 +70,19 @@ const EstimatedAnnualRewardsCard = ({ - - {estimatedAnnualRewards} - + {isLoading ? ( + + + + ) : ( + + {estimatedAnnualRewards} + + )} { + const actualReactNavigation = jest.requireActual('@react-navigation/native'); + return { + ...actualReactNavigation, + useNavigation: () => ({ + navigate: mockNavigate, + }), + }; +}); + +jest.mock('../../constants', () => ({ + isPooledStakingFeatureEnabled: jest.fn().mockReturnValue(true), +})); + +jest.mock('../../../../hooks/useMetrics', () => ({ + MetaMetricsEvents: { + STAKE_BUTTON_CLICKED: 'Stake Button Clicked', + }, + useMetrics: () => ({ + trackEvent: jest.fn(), + }), +})); + +jest.mock('../../../../../core/Engine', () => ({ + context: { + NetworkController: { + getNetworkClientById: () => ({ + configuration: { + chainId: '0x1', + rpcUrl: 'https://mainnet.infura.io/v3', + ticker: 'ETH', + type: 'custom', + }, + }), + findNetworkClientIdByChainId: () => 'mainnet', + }, + }, +})); + +jest.mock('../../hooks/useStakingEligibility', () => ({ + __esModule: true, + default: () => ({ + isEligible: true, + loading: false, + error: null, + refreshPooledStakingEligibility: jest.fn(), + }), +})); + +const renderComponent = () => + renderWithProvider(); + +describe('StakeButton', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders correctly', () => { + const { getByTestId } = renderComponent(); + expect(getByTestId(WalletViewSelectorsIDs.STAKE_BUTTON)).toBeDefined(); + }); + + it('navigates to Stake Input screen when stake button is pressed and user is eligible', async () => { + const { getByTestId } = renderComponent(); + + fireEvent.press(getByTestId(WalletViewSelectorsIDs.STAKE_BUTTON)); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith('StakeScreens', { + screen: Routes.STAKING.STAKE, + }); + }); + }); +}); diff --git a/app/components/UI/Tokens/TokenList/StakeButton/index.tsx b/app/components/UI/Stake/components/StakeButton/index.tsx similarity index 74% rename from app/components/UI/Tokens/TokenList/StakeButton/index.tsx rename to app/components/UI/Stake/components/StakeButton/index.tsx index b0decefd026..7a92f67d3cb 100644 --- a/app/components/UI/Tokens/TokenList/StakeButton/index.tsx +++ b/app/components/UI/Stake/components/StakeButton/index.tsx @@ -1,14 +1,11 @@ import React from 'react'; -import { TokenI, BrowserTab } from '../../types'; +import { TokenI, BrowserTab } from '../../../Tokens/types'; import { useNavigation } from '@react-navigation/native'; -import { isPooledStakingFeatureEnabled } from '../../../Stake/constants'; +import { isPooledStakingFeatureEnabled } from '../../constants'; import Routes from '../../../../../constants/navigation/Routes'; import { useSelector } from 'react-redux'; import AppConstants from '../../../../../core/AppConstants'; -import { - MetaMetricsEvents, - useMetrics, -} from '../../../../../components/hooks/useMetrics'; +import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics'; import { getDecimalChainId } from '../../../../../util/networks'; import { selectChainId } from '../../../../../selectors/networkController'; import { Pressable } from 'react-native'; @@ -18,7 +15,7 @@ import Text, { } from '../../../../../component-library/components/Texts/Text'; import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors'; import { useTheme } from '../../../../../util/theme'; -import createStyles from '../../styles'; +import createStyles from '../../../Tokens/styles'; import Icon, { IconColor, IconName, @@ -26,12 +23,13 @@ import Icon, { } from '../../../../../component-library/components/Icons/Icon'; import { strings } from '../../../../../../locales/i18n'; import { RootState } from '../../../../../reducers'; +import useStakingEligibility from '../../hooks/useStakingEligibility'; +import { StakeSDKProvider } from '../../sdk/stakeSdkProvider'; interface StakeButtonProps { asset: TokenI; } - -export const StakeButton = ({ asset }: StakeButtonProps) => { +const StakeButtonContent = ({ asset }: StakeButtonProps) => { const { colors } = useTheme(); const styles = createStyles(colors); const navigation = useNavigation(); @@ -40,8 +38,12 @@ export const StakeButton = ({ asset }: StakeButtonProps) => { const browserTabs = useSelector((state: RootState) => state.browser.tabs); const chainId = useSelector(selectChainId); - const onStakeButtonPress = () => { - if (isPooledStakingFeatureEnabled()) { + const { isEligible, refreshPooledStakingEligibility } = + useStakingEligibility(); + + const onStakeButtonPress = async () => { + await refreshPooledStakingEligibility(); + if (isPooledStakingFeatureEnabled() && isEligible) { navigation.navigate('StakeScreens', { screen: Routes.STAKING.STAKE }); } else { const existingStakeTab = browserTabs.find((tab: BrowserTab) => @@ -82,7 +84,7 @@ export const StakeButton = ({ asset }: StakeButtonProps) => { {' • '} - {`${strings('stake.stake')} `} + {`${strings('stake.earn')} `} { ); }; + +export const StakeButton = (props: StakeButtonProps) => ( + + + +); + +export default StakeButton; diff --git a/app/components/UI/Stake/components/StakingBalance/StakingBalance.test.tsx b/app/components/UI/Stake/components/StakingBalance/StakingBalance.test.tsx index 2aae28643bf..e02cd932b65 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingBalance.test.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingBalance.test.tsx @@ -5,6 +5,11 @@ import StakingBalance from './StakingBalance'; import { strings } from '../../../../../../locales/i18n'; import Routes from '../../../../../constants/navigation/Routes'; import { Image } from 'react-native'; +import { + MOCK_GET_POOLED_STAKES_API_RESPONSE, + MOCK_GET_VAULT_RESPONSE, + MOCK_STAKED_ETH_ASSET, +} from '../../__mocks__/mockData'; jest.mock('../../../../hooks/useIpfsGateway', () => jest.fn()); @@ -24,20 +29,92 @@ jest.mock('@react-navigation/native', () => { }; }); +const mockPooledStakeData = MOCK_GET_POOLED_STAKES_API_RESPONSE.accounts[0]; +const mockExchangeRate = MOCK_GET_POOLED_STAKES_API_RESPONSE.exchangeRate; + +const mockVaultData = MOCK_GET_VAULT_RESPONSE; +// Mock hooks +jest.mock('../../hooks/usePooledStakes', () => ({ + __esModule: true, + default: () => ({ + pooledStakesData: mockPooledStakeData, + exchangeRate: mockExchangeRate, + loading: false, + error: null, + refreshPooledStakes: jest.fn(), + hasStakedPositions: true, + hasEthToUnstake: true, + hasNeverStaked: false, + hasRewards: true, + hasRewardsOnly: false, + }), +})); + +jest.mock('../../hooks/useStakingEligibility', () => ({ + __esModule: true, + default: () => ({ + isEligible: true, + loading: false, + error: null, + refreshPooledStakingEligibility: jest.fn(), + }), +})); + +jest.mock('../../hooks/useVaultData', () => ({ + __esModule: true, + default: () => ({ + vaultData: mockVaultData, + loading: false, + error: null, + annualRewardRate: '2.5%', + annualRewardRateDecimal: 0.025, + }), +})); + +jest.mock('../../hooks/useBalance', () => ({ + __esModule: true, + default: () => ({ + stakedBalanceWei: MOCK_STAKED_ETH_ASSET.balance, + stakedBalanceFiat: MOCK_STAKED_ETH_ASSET.balanceFiat, + }), +})); + +jest.mock('../../../../../core/Engine', () => ({ + context: { + NetworkController: { + getNetworkClientById: () => ({ + configuration: { + chainId: '0x1', + rpcUrl: 'https://mainnet.infura.io/v3', + ticker: 'ETH', + type: 'custom', + }, + }), + findNetworkClientIdByChainId: () => 'mainnet', + }, + }, +})); + afterEach(() => { jest.clearAllMocks(); }); describe('StakingBalance', () => { - beforeEach(() => jest.resetAllMocks()); + beforeEach(() => { + jest.resetAllMocks(); + }); it('render matches snapshot', () => { - const { toJSON } = renderWithProvider(); + const { toJSON } = renderWithProvider( + , + ); expect(toJSON()).toMatchSnapshot(); }); it('redirects to StakeInputView on stake button click', () => { - const { getByText } = renderWithProvider(); + const { getByText } = renderWithProvider( + , + ); fireEvent.press(getByText(strings('stake.stake_more'))); @@ -48,7 +125,9 @@ describe('StakingBalance', () => { }); it('redirects to UnstakeInputView on unstake button click', () => { - const { getByText } = renderWithProvider(); + const { getByText } = renderWithProvider( + , + ); fireEvent.press(getByText(strings('stake.unstake'))); diff --git a/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx b/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx index 8884688694b..e70630ab901 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import Badge, { BadgeVariant, } from '../../../../../component-library/components/Badges/Badge'; @@ -11,7 +11,6 @@ import AssetElement from '../../../AssetElement'; import NetworkMainAssetLogo from '../../../NetworkMainAssetLogo'; import { selectNetworkName } from '../../../../../selectors/networkInfos'; import { useSelector } from 'react-redux'; -import images from '../../../../../images/image-icons'; import styleSheet from './StakingBalance.styles'; import { View } from 'react-native'; import StakingButtons from './StakingButtons/StakingButtons'; @@ -35,28 +34,48 @@ import { } from '../../utils/value'; import { multiplyValueByPowerOfTen } from '../../utils/bignumber'; import StakingCta from './StakingCta/StakingCta'; -import { - MOCK_GET_POOLED_STAKES_API_RESPONSE, - MOCK_GET_VAULT_RESPONSE, - MOCK_STAKED_ETH_ASSET, -} from './mockData'; +import useStakingEligibility from '../../hooks/useStakingEligibility'; +import useStakingChain from '../../hooks/useStakingChain'; +import usePooledStakes from '../../hooks/usePooledStakes'; +import useVaultData from '../../hooks/useVaultData'; +import { StakeSDKProvider } from '../../sdk/stakeSdkProvider'; +import type { TokenI } from '../../../Tokens/types'; +import useBalance from '../../hooks/useBalance'; +import { NetworkBadgeSource } from '../../../AssetOverview/Balance/Balance'; +import { selectChainId } from '../../../../../selectors/networkController'; -const StakingBalance = () => { - const { styles } = useStyles(styleSheet, {}); +export interface StakingBalanceProps { + asset: TokenI; +} +const StakingBalanceContent = ({ asset }: StakingBalanceProps) => { + const { styles } = useStyles(styleSheet, {}); + const chainId = useSelector(selectChainId); const networkName = useSelector(selectNetworkName); - const [isGeoBlocked] = useState(false); - const [hasStakedPositions] = useState(false); + const { isEligible: isEligibleForPooledStaking } = useStakingEligibility(); - const { unstakingRequests, claimableRequests } = useMemo( - () => - filterExitRequests( - MOCK_GET_POOLED_STAKES_API_RESPONSE.accounts[0].exitRequests, - MOCK_GET_POOLED_STAKES_API_RESPONSE.exchangeRate, - ), - [], - ); + const { isStakingSupportedChain } = useStakingChain(); + + const { + pooledStakesData, + exchangeRate, + hasStakedPositions, + hasEthToUnstake, + isLoadingPooledStakesData, + } = usePooledStakes(); + const { vaultData } = useVaultData(); + const annualRewardRate = vaultData?.apy || ''; + + const { + formattedStakedBalanceETH: stakedBalanceETH, + formattedStakedBalanceFiat: stakedBalanceFiat, + } = useBalance(); + + const { unstakingRequests, claimableRequests } = useMemo(() => { + const exitRequests = pooledStakesData?.exitRequests ?? []; + return filterExitRequests(exitRequests, exchangeRate); + }, [pooledStakesData, exchangeRate]); const claimableEth = useMemo( () => @@ -72,20 +91,24 @@ const StakingBalance = () => { const hasClaimableEth = !!Number(claimableEth); + if (!isStakingSupportedChain || isLoadingPooledStakesData) { + return <>; + } + return ( - {Boolean(MOCK_STAKED_ETH_ASSET.balance) && !isGeoBlocked && ( + {hasStakedPositions && isEligibleForPooledStaking && ( } @@ -93,13 +116,13 @@ const StakingBalance = () => { - {MOCK_STAKED_ETH_ASSET.name || MOCK_STAKED_ETH_ASSET.symbol} + {strings('stake.staked_ethereum')} )} - {isGeoBlocked ? ( + {!isEligibleForPooledStaking ? ( { {!hasStakedPositions && ( )} - + )} @@ -156,4 +180,10 @@ const StakingBalance = () => { ); }; +export const StakingBalance = ({ asset }: StakingBalanceProps) => ( + + + +); + export default StakingBalance; diff --git a/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx b/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx index 51ba3ea15f9..2cec44d4baf 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import Button, { ButtonVariants, } from '../../../../../../component-library/components/Buttons/Button'; @@ -9,16 +9,23 @@ import styleSheet from './StakingButtons.styles'; import { useNavigation } from '@react-navigation/native'; import Routes from '../../../../../../constants/navigation/Routes'; -interface StakingButtonsProps extends Pick {} +interface StakingButtonsProps extends Pick { + hasStakedPositions: boolean; + hasEthToUnstake: boolean; +} -const StakingButtons = ({ style }: StakingButtonsProps) => { - const [hasStakedPosition] = useState(true); - const [hasEthToUnstake] = useState(true); +const StakingButtons = ({ + style, + hasStakedPositions, + hasEthToUnstake, +}: StakingButtonsProps) => { const { navigate } = useNavigation(); const { styles } = useStyles(styleSheet, {}); const onUnstakePress = () => - navigate('StakeScreens', { screen: Routes.STAKING.UNSTAKE }); + navigate('StakeScreens', { + screen: Routes.STAKING.UNSTAKE, + }); const onStakePress = () => navigate('StakeScreens', { screen: Routes.STAKING.STAKE }); @@ -37,7 +44,7 @@ const StakingButtons = ({ style }: StakingButtonsProps) => { style={styles.balanceActionButton} variant={ButtonVariants.Secondary} label={ - hasStakedPosition + hasStakedPositions ? strings('stake.stake_more') : strings('stake.stake') } diff --git a/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap b/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap index ee2fbde051b..c161fdb50d6 100644 --- a/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap +++ b/app/components/UI/Stake/components/StakingBalance/__snapshots__/StakingBalance.test.tsx.snap @@ -200,40 +200,7 @@ exports[`StakingBalance render matches snapshot 1`] = ` "flex": 1, } } - > - - $13,292.20 - - - 4.9999 ETH - - + /> - - - Stake ETH and earn - - - - Stake your ETH with MetaMask Pool and earn - - - 2.9% - - - annually. - - - - Learn more. - - - - ({ }), })); +jest.mock('../../../hooks/usePooledStakes', () => ({ + __esModule: true, + default: () => ({ + refreshPooledStakes: jest.fn(), + }), +})); + describe('ConfirmationFooter', () => { it('render matches snapshot', () => { const props: ConfirmationFooterProps = { diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx index ffaec56ae87..509c2054dcb 100644 --- a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx +++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx @@ -53,6 +53,13 @@ jest.mock('../../../../hooks/usePoolStakedDeposit', () => ({ }), })); +jest.mock('../../../../hooks/usePooledStakes', () => ({ + __esModule: true, + default: () => ({ + refreshPooledStakes: jest.fn(), + }), +})); + describe('FooterButtonGroup', () => { beforeEach(() => { jest.resetAllMocks(); @@ -70,7 +77,7 @@ describe('FooterButtonGroup', () => { ); expect(getByText(strings('stake.cancel'))).toBeDefined(); - expect(getByText(strings('stake.confirm'))).toBeDefined(); + expect(getByText(strings('stake.continue'))).toBeDefined(); expect(toJSON()).toMatchSnapshot(); }); @@ -89,8 +96,7 @@ describe('FooterButtonGroup', () => { fireEvent.press(getByText(strings('stake.cancel'))); - expect(mockNavigate).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenCalledWith('Asset'); + expect(mockGoBack).toHaveBeenCalledTimes(1); expect(toJSON()).toMatchSnapshot(); }); diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx index 00464468daa..5a98d66c48b 100644 --- a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx +++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx @@ -22,15 +22,18 @@ import { FooterButtonGroupProps, } from './FooterButtonGroup.types'; import Routes from '../../../../../../../constants/navigation/Routes'; +import usePooledStakes from '../../../../hooks/usePooledStakes'; const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { const { styles } = useStyles(styleSheet, {}); - const { navigate } = useNavigation(); + const navigation = useNavigation(); + const { navigate } = navigation; const activeAccount = useSelector(selectSelectedInternalAccount); const { attemptDepositTransaction } = usePoolStakedDeposit(); + const { refreshPooledStakes } = usePooledStakes(); const handleStake = async () => { if (!activeAccount?.address) return; @@ -51,19 +54,17 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { ({ transactionMeta }) => transactionMeta.id === transactionId, ); - // Engine.controllerMessenger.subscribeOnceIf( - // 'TransactionController:transactionConfirmed', - // () => { - // TODO: Call refreshPooledStakes(); - // refreshPooledStakes(); - // }, - // (transactionMeta) => transactionMeta.id === transactionId, - // ); + Engine.controllerMessenger.subscribeOnceIf( + 'TransactionController:transactionConfirmed', + () => { + refreshPooledStakes(); + }, + (transactionMeta) => transactionMeta.id === transactionId, + ); }; const handleConfirmation = () => { if (action === FooterButtonGroupActions.STAKE) return handleStake(); - // TODO: Add handler (STAKE-803) }; return ( @@ -78,12 +79,14 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { variant={ButtonVariants.Secondary} width={ButtonWidthTypes.Full} size={ButtonSize.Lg} - onPress={() => navigate('Asset')} + onPress={() => { + navigation.goBack(); + }} />