From b4567ce0561f6662a195605dc2d69ef8c3536354 Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:35:25 -0700 Subject: [PATCH 01/15] feat: Add ApeChain network and token icons (#11928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Mobile version of: https://github.com/MetaMask/metamask-extension/pull/27974 ## **Related issues** ## **Manual testing steps** Add ApeChain custom network, with APE symbol. Native token should be token icon, network icons should be APE network icon. ## **Screenshots/Recordings** Screenshot 2024-10-21 at 12 26 38 PM Screenshot 2024-10-21 at 12 26 43 PM Screenshot 2024-10-21 at 12 26 53 PM ## **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. --------- Co-authored-by: sahar-fehri --- app/images/ape-network.png | Bin 0 -> 42185 bytes app/images/ape-token.png | Bin 0 -> 51726 bytes app/images/image-icons.js | 2 ++ app/util/networks/customNetworks.tsx | 2 ++ 4 files changed, 4 insertions(+) create mode 100644 app/images/ape-network.png create mode 100644 app/images/ape-token.png diff --git a/app/images/ape-network.png b/app/images/ape-network.png new file mode 100644 index 0000000000000000000000000000000000000000..c2985568768d45b590a29c809d47cf5e8cfa6e6f GIT binary patch literal 42185 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%0AcH|-juePW z*pLOH8V;^v1kr4(*+7)Uno-HoKp0I4quF7!Oc*T~M{5UANjO?X;tGU@|NCt%6oBmq z2L_S9@^||K8l!nZAy%*&+@{Dg1#u3n1-C4ATm^9$wu9Rj55ky1oI8*v$f%~#fWVp} YvTdIvmH07lnh8oQp00i_>zopr007`U!~g&Q literal 0 HcmV?d00001 diff --git a/app/images/ape-token.png b/app/images/ape-token.png new file mode 100644 index 0000000000000000000000000000000000000000..d23724fd1d8a6b28a8f1fc8f4815eca8ff51b833 GIT binary patch literal 51726 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 = { '0xe': require('../../images/flare-mainnet.png'), // Flare Mainnet '0x13': require('../../images/songbird.png'), // Songbird Testnet + '0x8157': require('../../images/ape-network.png'), // ApeChain testnet + '0x8173': require('../../images/ape-network.png'), // ApeChain mainnet '0x659': require('../../images/gravity.png'), // Gravity Alpha Mainnet }; From dceb552848a1ff5d4b4b27ed72457c97a09cfa39 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Wed, 30 Oct 2024 23:12:41 +0100 Subject: [PATCH 02/15] chore: upgrade assets-controllers v37 (#11988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Updates assets-controllers to v37 ## **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/Base/RemoteImage/index.js | 37 +- .../Base/RemoteImage/index.test.tsx | 18 +- .../__snapshots__/BuildQuote.test.tsx.snap | 4 +- .../StakeConfirmationView.test.tsx.snap | 2 +- .../UnstakeConfirmationView.test.tsx.snap | 2 +- .../StakingBalance.test.tsx.snap | 2 +- .../TokenValueStack.test.tsx.snap | 2 +- .../Tokens/__snapshots__/index.test.tsx.snap | 6 +- .../Badge/__snapshots__/index.test.tsx.snap | 2 +- app/core/Engine.test.ts | 8 +- app/core/Engine.ts | 94 +++-- app/core/EngineService/EngineService.ts | 1 - package.json | 2 +- ...tamask+preferences-controller+13.0.3.patch | 13 - ...ets-controllers++multiformats+13.3.0.patch | 12 + ...@metamask+assets-controllers+37.0.0.patch} | 334 +++++++++--------- yarn.lock | 53 ++- 17 files changed, 310 insertions(+), 282 deletions(-) delete mode 100644 patches/@metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch create mode 100644 patches/@metamask+assets-controllers++multiformats+13.3.0.patch rename patches/{@metamask+assets-controllers+36.0.0.patch => @metamask+assets-controllers+37.0.0.patch} (70%) diff --git a/app/components/Base/RemoteImage/index.js b/app/components/Base/RemoteImage/index.js index 12e7d1721b6..5c62258dc27 100644 --- a/app/components/Base/RemoteImage/index.js +++ b/app/components/Base/RemoteImage/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Image, @@ -67,23 +67,36 @@ const RemoteImage = (props) => { const chainId = useSelector(selectChainId); const ticker = useSelector(selectTicker); const networkName = useSelector(selectNetworkName); - const resolvedIpfsUrl = useMemo(() => { - try { - const url = new URL(props.source.uri); - if (url.protocol !== 'ipfs:') return false; - const ipfsUrl = getFormattedIpfsUrl(ipfsGateway, props.source.uri, false); - return ipfsUrl; - } catch { - return false; - } - }, [props.source.uri, ipfsGateway]); + const [resolvedIpfsUrl, setResolvedIpfsUrl] = useState(false); - const uri = resolvedIpfsUrl || source.uri; + const uri = + resolvedIpfsUrl || + (source.uri === undefined || source.uri?.startsWith('ipfs') + ? '' + : source.uri); const onError = ({ nativeEvent: { error } }) => setError(error); const [dimensions, setDimensions] = useState(null); + useEffect(() => { + resolveIpfsUrl(); + async function resolveIpfsUrl() { + try { + const url = new URL(props.source.uri); + if (url.protocol !== 'ipfs:') setResolvedIpfsUrl(false); + const ipfsUrl = await getFormattedIpfsUrl( + ipfsGateway, + props.source.uri, + false, + ); + setResolvedIpfsUrl(ipfsUrl); + } catch (err) { + setResolvedIpfsUrl(false); + } + } + }, [props.source.uri, ipfsGateway]); + useEffect(() => { const calculateImageDimensions = (imageWidth, imageHeight) => { const deviceWidth = Dimensions.get('window').width; diff --git a/app/components/Base/RemoteImage/index.test.tsx b/app/components/Base/RemoteImage/index.test.tsx index 65691852ebe..4ea9f0beaa0 100644 --- a/app/components/Base/RemoteImage/index.test.tsx +++ b/app/components/Base/RemoteImage/index.test.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import RemoteImage from './'; +import { getFormattedIpfsUrl } from '@metamask/assets-controllers'; +import { act, render } from '@testing-library/react-native'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -11,6 +13,12 @@ jest.mock('react-redux', () => ({ jest.mock('../../../components/hooks/useIpfsGateway', () => jest.fn()); +jest.mock('@metamask/assets-controllers', () => ({ + getFormattedIpfsUrl: jest.fn(), +})); + +const mockGetFormattedIpfsUrl = getFormattedIpfsUrl as jest.Mock; + describe('RemoteImage', () => { it('should render svg correctly', () => { const wrapper = shallow( @@ -34,14 +42,18 @@ describe('RemoteImage', () => { expect(wrapper).toMatchSnapshot(); }); - it('should render ipfs sources', () => { - const wrapper = shallow( + it('should render ipfs sources', async () => { + const testIpfsUri = 'ipfs://QmeE94srcYV9WwJb1p42eM4zncdLUai2N9zmMxxukoEQ23'; + mockGetFormattedIpfsUrl.mockResolvedValue(testIpfsUri); + const wrapper = render( , ); + // eslint-disable-next-line no-empty-function + await act(async () => {}); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap b/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap index d8474b6cb32..c8fc6d56cfe 100644 --- a/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap +++ b/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap @@ -8717,7 +8717,7 @@ exports[`BuildQuote View renders correctly 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ @@ -11839,7 +11839,7 @@ exports[`BuildQuote View renders correctly 2`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ 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 ab1c216fd7a..32849f83ce2 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 @@ -45,7 +45,7 @@ exports[`StakeConfirmationView render matches snapshot 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ diff --git a/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap b/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap index 2b82484bc70..8bcfb049259 100644 --- a/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap +++ b/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap @@ -45,7 +45,7 @@ exports[`UnstakeConfirmationView render matches snapshot 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ 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 53039c1fdf3..efb6cfa67f9 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 @@ -36,7 +36,7 @@ exports[`StakingBalance render matches snapshot 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ diff --git a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap index 32f3cf0ccfc..70902df7688 100644 --- a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap +++ b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap @@ -33,7 +33,7 @@ exports[`TokenValueStack render matches snapshot 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ diff --git a/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap b/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap index e9e7f0f4b15..4968a992595 100644 --- a/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap @@ -549,7 +549,7 @@ exports[`Tokens should hide zero balance tokens when setting is on 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ @@ -1795,7 +1795,7 @@ exports[`Tokens should render correctly 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ @@ -3059,7 +3059,7 @@ exports[`Tokens should show all balance tokens when hideZeroBalanceTokens settin onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ diff --git a/app/components/Views/Notifications/Details/Badge/__snapshots__/index.test.tsx.snap b/app/components/Views/Notifications/Details/Badge/__snapshots__/index.test.tsx.snap index 053e08832c7..efdc00300ff 100644 --- a/app/components/Views/Notifications/Details/Badge/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Notifications/Details/Badge/__snapshots__/index.test.tsx.snap @@ -21,7 +21,7 @@ exports[`NotificationBadge should renders correctly 1`] = ` onLoadEnd={[Function]} source={ { - "uri": undefined, + "uri": "", } } style={ diff --git a/app/core/Engine.test.ts b/app/core/Engine.test.ts index 835365d6fbb..8c05d1a7979 100644 --- a/app/core/Engine.test.ts +++ b/app/core/Engine.test.ts @@ -71,7 +71,13 @@ describe('Engine', () => { const engine = Engine.init({}); const initialBackgroundState = engine.datamodel.state; - expect(initialBackgroundState).toStrictEqual(backgroundState); + // AssetsContractController is stateless in v37 resulting in an undefined state + const newBackgroundState = { + ...backgroundState, + AssetsContractController: undefined, + }; + + expect(initialBackgroundState).toStrictEqual(newBackgroundState); }); it('setSelectedAccount throws an error if no account exists for the given address', () => { diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 5dc722ab2b3..a5abb765362 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -28,6 +28,13 @@ import { TokenListControllerActions, TokenListControllerEvents, TokenBalancesControllerState, + AssetsContractControllerGetERC20BalanceOfAction, + AssetsContractControllerGetERC721AssetNameAction, + AssetsContractControllerGetERC721AssetSymbolAction, + AssetsContractControllerGetERC721TokenURIAction, + AssetsContractControllerGetERC721OwnerOfAction, + AssetsContractControllerGetERC1155BalanceOfAction, + AssetsContractControllerGetERC1155TokenURIAction, } from '@metamask/assets-controllers'; ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { AppState } from 'react-native'; @@ -308,7 +315,14 @@ type GlobalActions = | TokensControllerActions | TokenListControllerActions | SelectedNetworkControllerActions - | SmartTransactionsControllerActions; + | SmartTransactionsControllerActions + | AssetsContractControllerGetERC20BalanceOfAction + | AssetsContractControllerGetERC721AssetNameAction + | AssetsContractControllerGetERC721AssetSymbolAction + | AssetsContractControllerGetERC721TokenURIAction + | AssetsContractControllerGetERC721OwnerOfAction + | AssetsContractControllerGetERC1155BalanceOfAction + | AssetsContractControllerGetERC1155TokenURIAction; type GlobalEvents = | AddressBookControllerEvents @@ -502,19 +516,6 @@ export class Engine { ) { this.controllerMessenger = new ExtendedControllerMessenger(); - /** - * Subscribes a listener to the state change events of Preferences Controller. - * - * @param listener - The callback function to execute when the state changes. - */ - const onPreferencesStateChange = ( - listener: (preferencesState: PreferencesState) => void, - ) => { - const eventName = `PreferencesController:stateChange`; - - this.controllerMessenger.subscribe(eventName, listener); - }; - const approvalController = new ApprovalController({ messenger: this.controllerMessenger.getRestricted({ name: 'ApprovalController', @@ -570,18 +571,23 @@ export class Engine { networkController.initializeProvider(); const assetsContractController = new AssetsContractController({ - onPreferencesStateChange, - onNetworkDidChange: (listener) => - this.controllerMessenger.subscribe( - AppConstants.NETWORK_DID_CHANGE_EVENT, - // @ts-expect-error TODO: Resolve bump the assets controller version. - listener, - ), + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. + messenger: this.controllerMessenger.getRestricted({ + name: 'AssetsContractController', + allowedActions: [ + 'NetworkController:getNetworkClientById', + 'NetworkController:getNetworkConfigurationByNetworkClientId', + 'NetworkController:getSelectedNetworkClient', + 'NetworkController:getState', + ], + allowedEvents: [ + 'PreferencesController:stateChange', + 'NetworkController:networkDidChange', + ], + }), chainId: networkController.getNetworkClientById( networkController?.state.selectedNetworkClientId, ).configuration.chainId, - getNetworkClientById: - networkController.getNetworkClientById.bind(networkController), }); const accountsControllerMessenger: AccountsControllerMessenger = this.controllerMessenger.getRestricted({ @@ -623,6 +629,12 @@ export class Engine { `${networkController.name}:getNetworkClientById`, 'AccountsController:getAccount', 'AccountsController:getSelectedAccount', + 'AssetsContractController:getERC721AssetName', + 'AssetsContractController:getERC721AssetSymbol', + 'AssetsContractController:getERC721TokenURI', + 'AssetsContractController:getERC721OwnerOf', + 'AssetsContractController:getERC1155BalanceOf', + 'AssetsContractController:getERC1155TokenURI', ], allowedEvents: [ 'PreferencesController:stateChange', @@ -630,25 +642,6 @@ export class Engine { 'AccountsController:selectedEvmAccountChange', ], }), - - getERC721AssetName: assetsContractController.getERC721AssetName.bind( - assetsContractController, - ), - getERC721AssetSymbol: assetsContractController.getERC721AssetSymbol.bind( - assetsContractController, - ), - getERC721TokenURI: assetsContractController.getERC721TokenURI.bind( - assetsContractController, - ), - getERC721OwnerOf: assetsContractController.getERC721OwnerOf.bind( - assetsContractController, - ), - getERC1155BalanceOf: assetsContractController.getERC1155BalanceOf.bind( - assetsContractController, - ), - getERC1155TokenURI: assetsContractController.getERC1155TokenURI.bind( - assetsContractController, - ), }); const loggingController = new LoggingController({ @@ -1509,12 +1502,12 @@ export class Engine { // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'TokenBalancesController', - allowedActions: ['AccountsController:getSelectedAccount'], + allowedActions: [ + 'AccountsController:getSelectedAccount', + 'AssetsContractController:getERC20BalanceOf', + ], allowedEvents: ['TokensController:stateChange'], }), - getERC20BalanceOf: assetsContractController.getERC20BalanceOf.bind( - assetsContractController, - ), interval: 180000, tokens: [ ...tokensController.state.tokens, @@ -1863,12 +1856,8 @@ export class Engine { } configureControllersOnNetworkChange() { - const { - AccountTrackerController, - AssetsContractController, - NetworkController, - SwapsController, - } = this.context; + const { AccountTrackerController, NetworkController, SwapsController } = + this.context; const { provider } = NetworkController.getProviderAndBlockTracker(); // Skip configuration if this is called before the provider is initialized @@ -1876,7 +1865,6 @@ export class Engine { return; } provider.sendAsync = provider.sendAsync.bind(provider); - AssetsContractController.configure({ provider }); SwapsController.configure({ provider, diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index 010a251c822..74a063fa767 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -53,7 +53,6 @@ class EngineService { name: 'AddressBookController', key: `${engine.context.AddressBookController.name}:stateChange`, }, - { name: 'AssetsContractController' }, { name: 'NftController', key: 'NftController:stateChange' }, { name: 'TokensController', diff --git a/package.json b/package.json index 97cf8529b13..b9c11bcd519 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": "^36.0.0", + "@metamask/assets-controllers": "^37.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++@metamask+preferences-controller+13.0.3.patch b/patches/@metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch deleted file mode 100644 index c45e6f5109a..00000000000 --- a/patches/@metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts b/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts -index 04a9d6f..391652d 100644 ---- a/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts -+++ b/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts -@@ -65,7 +65,7 @@ export type PreferencesState = { - /** - * Controls whether the OpenSea API is used - */ -- openSeaEnabled: boolean; -+ displayNftMedia: boolean; - /** - * Controls whether "security alerts" are enabled - */ diff --git a/patches/@metamask+assets-controllers++multiformats+13.3.0.patch b/patches/@metamask+assets-controllers++multiformats+13.3.0.patch new file mode 100644 index 00000000000..dd14e2b2e28 --- /dev/null +++ b/patches/@metamask+assets-controllers++multiformats+13.3.0.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/@metamask/assets-controllers/node_modules/multiformats/package.json b/node_modules/@metamask/assets-controllers/node_modules/multiformats/package.json +index 79fd9b9..662d4c7 100644 +--- a/node_modules/@metamask/assets-controllers/node_modules/multiformats/package.json ++++ b/node_modules/@metamask/assets-controllers/node_modules/multiformats/package.json +@@ -22,6 +22,7 @@ + "multiformats" + ], + "type": "module", ++ "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { diff --git a/patches/@metamask+assets-controllers+36.0.0.patch b/patches/@metamask+assets-controllers+37.0.0.patch similarity index 70% rename from patches/@metamask+assets-controllers+36.0.0.patch rename to patches/@metamask+assets-controllers+37.0.0.patch index af8d00e058a..9c930d9807d 100644 --- a/patches/@metamask+assets-controllers+36.0.0.patch +++ b/patches/@metamask+assets-controllers+37.0.0.patch @@ -1,35 +1,13 @@ -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); - } -+ -+ /** -+ * 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-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 +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-2TZK6YZA.js b/node_modules/@metamask/assets-controllers/dist/chunk-2TZK6YZA.js +index 1d6c20d..abf5f3b 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-2TZK6YZA.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-2TZK6YZA.js @@ -46,7 +46,7 @@ var getDefaultNftControllerState = () => ({ allNfts: {}, ignoredNfts: [] }); --var _mutex, _selectedAccountId, _chainId, _ipfsGateway, _openSeaEnabled, _useIpfsSubdomains, _isIpfsGatewayEnabled, _getERC721AssetName, _getERC721AssetSymbol, _getERC721TokenURI, _getERC721OwnerOf, _getERC1155BalanceOf, _getERC1155TokenURI, _onNftAdded, _onNetworkControllerNetworkDidChange, onNetworkControllerNetworkDidChange_fn, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _onSelectedAccountChange, onSelectedAccountChange_fn, _updateNestedNftState, updateNestedNftState_fn, _getNftInformationFromApi, getNftInformationFromApi_fn, _getNftInformationFromTokenURI, getNftInformationFromTokenURI_fn, _getNftURIAndStandard, getNftURIAndStandard_fn, _getNftInformation, getNftInformation_fn, _getNftContractInformationFromContract, getNftContractInformationFromContract_fn, _getNftContractInformation, getNftContractInformation_fn, _addIndividualNft, addIndividualNft_fn, _addNftContract, addNftContract_fn, _removeAndIgnoreIndividualNft, removeAndIgnoreIndividualNft_fn, _removeIndividualNft, removeIndividualNft_fn, _removeNftContract, removeNftContract_fn, _validateWatchNft, validateWatchNft_fn, _getCorrectChainId, getCorrectChainId_fn, _getAddressOrSelectedAddress, getAddressOrSelectedAddress_fn, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn; -+var _mutex, _selectedAccountId, _chainId, _ipfsGateway, _displayNftMedia, _useIpfsSubdomains, _isIpfsGatewayEnabled, _getERC721AssetName, _getERC721AssetSymbol, _getERC721TokenURI, _getERC721OwnerOf, _getERC1155BalanceOf, _getERC1155TokenURI, _onNftAdded, _onNetworkControllerNetworkDidChange, onNetworkControllerNetworkDidChange_fn, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _onSelectedAccountChange, onSelectedAccountChange_fn, _updateNestedNftState, updateNestedNftState_fn, _getNftInformationFromApi, getNftInformationFromApi_fn, _getNftInformationFromTokenURI, getNftInformationFromTokenURI_fn, _getNftURIAndStandard, getNftURIAndStandard_fn, _getNftInformation, getNftInformation_fn, _getNftContractInformationFromContract, getNftContractInformationFromContract_fn, _getNftContractInformation, getNftContractInformation_fn, _addIndividualNft, addIndividualNft_fn, _addNftContract, addNftContract_fn, _removeAndIgnoreIndividualNft, removeAndIgnoreIndividualNft_fn, _removeIndividualNft, removeIndividualNft_fn, _removeNftContract, removeNftContract_fn, _validateWatchNft, validateWatchNft_fn, _getCorrectChainId, getCorrectChainId_fn, _getAddressOrSelectedAddress, getAddressOrSelectedAddress_fn, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn; +-var _mutex, _selectedAccountId, _chainId, _ipfsGateway, _openSeaEnabled, _useIpfsSubdomains, _isIpfsGatewayEnabled, _onNftAdded, _onNetworkControllerNetworkDidChange, onNetworkControllerNetworkDidChange_fn, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _onSelectedAccountChange, onSelectedAccountChange_fn, _updateNestedNftState, updateNestedNftState_fn, _getNftCollectionApi, getNftCollectionApi_fn, _getNftInformationFromApi, getNftInformationFromApi_fn, _getNftInformationFromTokenURI, getNftInformationFromTokenURI_fn, _getNftURIAndStandard, getNftURIAndStandard_fn, _getNftInformation, getNftInformation_fn, _getNftContractInformationFromContract, getNftContractInformationFromContract_fn, _getNftContractInformation, getNftContractInformation_fn, _addIndividualNft, addIndividualNft_fn, _addNftContract, addNftContract_fn, _removeAndIgnoreIndividualNft, removeAndIgnoreIndividualNft_fn, _removeIndividualNft, removeIndividualNft_fn, _removeNftContract, removeNftContract_fn, _validateWatchNft, validateWatchNft_fn, _getCorrectChainId, getCorrectChainId_fn, _getAddressOrSelectedAddress, getAddressOrSelectedAddress_fn, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn; ++var _mutex, _selectedAccountId, _chainId, _ipfsGateway, _displayNftMedia, _useIpfsSubdomains, _isIpfsGatewayEnabled, _onNftAdded, _onNetworkControllerNetworkDidChange, onNetworkControllerNetworkDidChange_fn, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _onSelectedAccountChange, onSelectedAccountChange_fn, _updateNestedNftState, updateNestedNftState_fn, _getNftCollectionApi, getNftCollectionApi_fn, _getNftInformationFromApi, getNftInformationFromApi_fn, _getNftInformationFromTokenURI, getNftInformationFromTokenURI_fn, _getNftURIAndStandard, getNftURIAndStandard_fn, _getNftInformation, getNftInformation_fn, _getNftContractInformationFromContract, getNftContractInformationFromContract_fn, _getNftContractInformation, getNftContractInformation_fn, _addIndividualNft, addIndividualNft_fn, _addNftContract, addNftContract_fn, _removeAndIgnoreIndividualNft, removeAndIgnoreIndividualNft_fn, _removeIndividualNft, removeIndividualNft_fn, _removeNftContract, removeNftContract_fn, _validateWatchNft, validateWatchNft_fn, _getCorrectChainId, getCorrectChainId_fn, _getAddressOrSelectedAddress, getAddressOrSelectedAddress_fn, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn; var NftController = class extends _basecontroller.BaseController { /** * Creates an NftController instance. @@ -41,8 +19,8 @@ index a9f6736..ecd98e7 100644 + * @param options.displayNftMedia - Controls whether the OpenSea API is used. * @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. -@@ -71,7 +71,7 @@ var NftController = class extends _basecontroller.BaseController { + * @param options.onNftAdded - Callback that is called when an NFT is added. Currently used pass data +@@ -65,7 +65,7 @@ var NftController = class extends _basecontroller.BaseController { constructor({ chainId: initialChainId, ipfsGateway = _controllerutils.IPFS_DEFAULT_GATEWAY_URL, @@ -50,8 +28,8 @@ index a9f6736..ecd98e7 100644 + displayNftMedia = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, - getERC721AssetName, -@@ -103,7 +103,7 @@ var NftController = class extends _basecontroller.BaseController { + onNftAdded, +@@ -91,7 +91,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. @@ -60,7 +38,7 @@ index a9f6736..ecd98e7 100644 * @param preferencesState.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. */ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onPreferencesControllerStateChange); -@@ -239,7 +239,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -228,7 +228,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); @@ -68,8 +46,8 @@ index a9f6736..ecd98e7 100644 + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _displayNftMedia, void 0); _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); -@@ -254,7 +254,7 @@ var NftController = class extends _basecontroller.BaseController { + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onNftAdded, void 0); +@@ -237,7 +237,7 @@ var NftController = class extends _basecontroller.BaseController { ).id); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _chainId, initialChainId); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _ipfsGateway, ipfsGateway); @@ -77,8 +55,8 @@ index a9f6736..ecd98e7 100644 + _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _displayNftMedia, displayNftMedia); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _useIpfsSubdomains, useIpfsSubdomains); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _isIpfsGatewayEnabled, isIpfsGatewayEnabled); - _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getERC721AssetName, getERC721AssetName); -@@ -281,6 +281,19 @@ var NftController = class extends _basecontroller.BaseController { + _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _onNftAdded, onNftAdded); +@@ -258,6 +258,20 @@ var NftController = class extends _basecontroller.BaseController { _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _onSelectedAccountChange, onSelectedAccountChange_fn).bind(this) ); } @@ -94,11 +72,12 @@ index a9f6736..ecd98e7 100644 + state.ignoredNfts = []; + }); + } -+ ++ ++ getNftApi() { return `${_controllerutils.NFT_API_BASE_URL}/tokens`; } -@@ -799,7 +812,7 @@ _mutex = new WeakMap(); +@@ -806,7 +820,7 @@ _mutex = new WeakMap(); _selectedAccountId = new WeakMap(); _chainId = new WeakMap(); _ipfsGateway = new WeakMap(); @@ -106,8 +85,8 @@ index a9f6736..ecd98e7 100644 +_displayNftMedia = new WeakMap(); _useIpfsSubdomains = new WeakMap(); _isIpfsGatewayEnabled = new WeakMap(); - _getERC721AssetName = new WeakMap(); -@@ -824,7 +837,7 @@ onNetworkControllerNetworkDidChange_fn = function({ + _onNftAdded = new WeakMap(); +@@ -825,7 +839,7 @@ onNetworkControllerNetworkDidChange_fn = function({ _onPreferencesControllerStateChange = new WeakSet(); onPreferencesControllerStateChange_fn = async function({ ipfsGateway, @@ -116,7 +95,7 @@ index a9f6736..ecd98e7 100644 isIpfsGatewayEnabled }) { const selectedAccount = this.messagingSystem.call( -@@ -832,9 +845,9 @@ onPreferencesControllerStateChange_fn = async function({ +@@ -833,9 +847,9 @@ onPreferencesControllerStateChange_fn = async function({ ); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _selectedAccountId, selectedAccount.id); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _ipfsGateway, ipfsGateway); @@ -128,7 +107,7 @@ index a9f6736..ecd98e7 100644 if (needsUpdateNftMetadata && selectedAccount) { await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn).call(this, selectedAccount); } -@@ -843,7 +856,7 @@ _onSelectedAccountChange = new WeakSet(); +@@ -844,7 +858,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); @@ -137,7 +116,7 @@ index a9f6736..ecd98e7 100644 if (needsUpdateNftMetadata) { await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateNftUpdateForAccount, updateNftUpdateForAccount_fn).call(this, internalAccount); } -@@ -900,7 +913,8 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { +@@ -905,7 +919,8 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { name: null, description: null, image: null, @@ -147,7 +126,7 @@ index a9f6736..ecd98e7 100644 }; } const { -@@ -961,7 +975,7 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -966,7 +981,7 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw tokenURI: tokenURI ?? null }; } @@ -156,7 +135,7 @@ index a9f6736..ecd98e7 100644 if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { return { image: null, -@@ -969,7 +983,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -974,7 +989,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw description: null, standard: standard || null, favorite: false, @@ -166,7 +145,7 @@ index a9f6736..ecd98e7 100644 }; } if (hasIpfsTokenURI) { -@@ -1010,7 +1025,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -1015,7 +1031,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw description: null, standard: standard || null, favorite: false, @@ -176,7 +155,7 @@ index a9f6736..ecd98e7 100644 }; } }; -@@ -1041,10 +1057,21 @@ getNftInformation_fn = async function(contractAddress, tokenId, networkClientId) +@@ -1056,10 +1073,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) ), @@ -199,7 +178,7 @@ index a9f6736..ecd98e7 100644 return { ...nftApiMetadata, name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null, -@@ -1161,7 +1188,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont +@@ -1184,7 +1212,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont symbol: nftContract.symbol, tokenId: tokenId.toString(), standard: nftMetadata.standard, @@ -209,34 +188,13 @@ index a9f6736..ecd98e7 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..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() { - contractBalances: {} - }; - } --var _handle, _getERC20BalanceOf, _interval, _tokens, _disabled; -+var _handle, _getERC20BalanceOf, _interval, _tokens, _disabled, _updateInProgress; - var TokenBalancesController = class extends _basecontroller.BaseController { - /** - * Construct a Token Balances Controller. -@@ -54,9 +54,11 @@ var TokenBalancesController = class extends _basecontroller.BaseController { - _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _interval, void 0); - _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _tokens, void 0); - _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _disabled, void 0); -+ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _updateInProgress, void 0); - _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _disabled, disabled); - _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _interval, interval); - _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _tokens, tokens); -+ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _updateInProgress, false); - this.messagingSystem.subscribe( - "TokensController:stateChange", - ({ tokens: newTokens, detectedTokens }) => { -@@ -67,6 +69,17 @@ var TokenBalancesController = class extends _basecontroller.BaseController { - _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getERC20BalanceOf, getERC20BalanceOf); - this.poll(); +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-ADJ3IFJH.js b/node_modules/@metamask/assets-controllers/dist/chunk-ADJ3IFJH.js +index fb25753..88208d5 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-ADJ3IFJH.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-ADJ3IFJH.js +@@ -189,6 +189,21 @@ var TokensController = class extends _basecontroller.BaseController { + } + ); } + + /** @@ -245,59 +203,29 @@ index 995ec6b..7222a8a 100644 + */ + reset() { + this.update((state) => { -+ state.contractBalances = {}; ++ state.allTokens = {}; ++ state.allIgnoredTokens = {}; ++ state.ignoredTokens = []; ++ state.tokens = []; + }); + } -+ ++ ++ /** - * Allows controller to update tracked tokens contract balances. - */ -@@ -107,20 +120,27 @@ var TokenBalancesController = class extends _basecontroller.BaseController { - "AccountsController:getSelectedAccount" - ); - const newContractBalances = {}; -- for (const token of _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _tokens)) { -+ const balancePromises = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _tokens).map((token) => { - const { address } = token; -- try { -- const balance = await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getERC20BalanceOf).call(this, address, selectedInternalAccount.address); -- newContractBalances[address] = _controllerutils.toHex.call(void 0, balance); -- token.hasBalanceError = false; -- } catch (error) { -- newContractBalances[address] = _controllerutils.toHex.call(void 0, 0); -- token.hasBalanceError = true; -- } -- } -+ return _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getERC20BalanceOf).call(this, address, selectedInternalAccount.address).then((balance) => { -+ newContractBalances[address] = _controllerutils.toHex.call(void 0, balance); -+ token = { -+ ...token, -+ hasBalanceError: false -+ }; -+ }).catch((error) => { -+ newContractBalances[address] = _controllerutils.toHex.call(void 0, 0); -+ token = { -+ ...token, -+ hasBalanceError: true -+ }; -+ }); -+ }); -+ await Promise.all(balancePromises); - this.update((state) => { - state.contractBalances = newContractBalances; - }); -+ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _updateInProgress, false); - } - }; - _handle = new WeakMap(); -@@ -128,6 +148,7 @@ _getERC20BalanceOf = new WeakMap(); - _interval = new WeakMap(); - _tokens = new WeakMap(); - _disabled = new WeakMap(); -+_updateInProgress = new WeakMap(); - var TokenBalancesController_default = TokenBalancesController; - - + * Adds a token to the stored token list. + * +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-GHG3DOUK.js b/node_modules/@metamask/assets-controllers/dist/chunk-GHG3DOUK.js +index ec5a116..a82c449 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-GHG3DOUK.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-GHG3DOUK.js +@@ -72,6 +72,7 @@ var AssetsContractController = class { + * @returns Hex chain ID. + */ + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getCorrectChainId); ++ this.name = name; + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _provider, void 0); + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _ipfsGateway, void 0); + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _chainId, void 0); 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 @@ -351,13 +279,56 @@ 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 { - } +diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-X2HRAVDO.js b/node_modules/@metamask/assets-controllers/dist/chunk-X2HRAVDO.js +index 2688318..fa7521e 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-X2HRAVDO.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-X2HRAVDO.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); + } ++ ++ /** ++ * 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-YGGUAMHV.js b/node_modules/@metamask/assets-controllers/dist/chunk-YGGUAMHV.js +index 6550aec..b46080e 100644 +--- a/node_modules/@metamask/assets-controllers/dist/chunk-YGGUAMHV.js ++++ b/node_modules/@metamask/assets-controllers/dist/chunk-YGGUAMHV.js +@@ -17,7 +17,7 @@ function getDefaultTokenBalancesState() { + contractBalances: {} + }; + } +-var _handle, _interval, _tokens, _disabled; ++var _handle, _interval, _tokens, _disabled, _updateInProgress; + var TokenBalancesController = class extends _basecontroller.BaseController { + /** + * Construct a Token Balances Controller. +@@ -49,9 +49,11 @@ var TokenBalancesController = class extends _basecontroller.BaseController { + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _interval, void 0); + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _tokens, void 0); + _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _disabled, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _updateInProgress, void 0); + _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _disabled, disabled); + _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _interval, interval); + _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _tokens, tokens); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _updateInProgress, false); + this.messagingSystem.subscribe( + "TokensController:stateChange", + ({ tokens: newTokens, detectedTokens }) => { +@@ -61,6 +63,18 @@ var TokenBalancesController = class extends _basecontroller.BaseController { ); + this.poll(); } + + /** @@ -366,18 +337,78 @@ index baaf7d0..cfefb60 100644 + */ + reset() { + this.update((state) => { -+ state.allTokens = {}; -+ state.allIgnoredTokens = {}; -+ state.ignoredTokens = []; -+ state.tokens = []; ++ state.contractBalances = {}; + }); + } ++ + /** - * Adds a token to the stored token list. - * + * Allows controller to update tracked tokens contract balances. + */ +@@ -101,30 +115,38 @@ var TokenBalancesController = class extends _basecontroller.BaseController { + "AccountsController:getSelectedAccount" + ); + const newContractBalances = {}; +- for (const token of _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _tokens)) { ++ const balancePromises = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _tokens).map((token) => { + const { address } = token; +- try { +- const balance = await this.messagingSystem.call( ++ return this.messagingSystem.call( + "AssetsContractController:getERC20BalanceOf", + address, + selectedInternalAccount.address +- ); +- newContractBalances[address] = _controllerutils.toHex.call(void 0, balance); +- token.hasBalanceError = false; +- } catch (error) { +- newContractBalances[address] = _controllerutils.toHex.call(void 0, 0); +- token.hasBalanceError = true; +- } +- } ++ ).then((balance) => { ++ newContractBalances[address] = _controllerutils.toHex.call(void 0, balance); ++ token = { ++ ...token, ++ hasBalanceError: false ++ }; ++ }).catch((error) => { ++ newContractBalances[address] = _controllerutils.toHex.call(void 0, 0); ++ token = { ++ ...token, ++ hasBalanceError: true ++ }; ++ }); ++ }); ++ await Promise.all(balancePromises); + this.update((state) => { + state.contractBalances = newContractBalances; + }); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _updateInProgress, false); + } + }; + _handle = new WeakMap(); + _interval = new WeakMap(); + _tokens = new WeakMap(); + _disabled = new WeakMap(); ++_updateInProgress = new WeakMap(); + var TokenBalancesController_default = TokenBalancesController; + + +diff --git a/node_modules/@metamask/assets-controllers/dist/types/AssetsContractController.d.ts b/node_modules/@metamask/assets-controllers/dist/types/AssetsContractController.d.ts +index 6d364e4..33fa36f 100644 +--- a/node_modules/@metamask/assets-controllers/dist/types/AssetsContractController.d.ts ++++ b/node_modules/@metamask/assets-controllers/dist/types/AssetsContractController.d.ts +@@ -103,6 +103,7 @@ export type AssetsContractControllerMessenger = RestrictedControllerMessenger; _requestApproval(suggestedNftMeta: SuggestedNftMeta): Promise; + /** + * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO @@ -402,10 +433,10 @@ index a69c32d..077e2db 100644 //# sourceMappingURL=NftController.d.ts.map \ No newline at end of file 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 +index 09a5fe0..ce64d87 100644 --- a/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts +++ b/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts -@@ -79,6 +79,11 @@ export declare class TokenBalancesController extends BaseController; @@ -452,16 +483,3 @@ index a3eb08b..9bbc823 100644 export default TokensController; //# sourceMappingURL=TokensController.d.ts.map \ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/types/index.d.ts b/node_modules/@metamask/assets-controllers/dist/types/index.d.ts -index 2a42816..f0d02dc 100644 ---- a/node_modules/@metamask/assets-controllers/dist/types/index.d.ts -+++ b/node_modules/@metamask/assets-controllers/dist/types/index.d.ts -@@ -6,7 +6,7 @@ export type { NftControllerState, NftControllerMessenger, NftControllerActions, - export { getDefaultNftControllerState, NftController } from './NftController'; - export type { NftDetectionControllerMessenger, ApiNft, ApiNftContract, ApiNftLastSale, ApiNftCreator, ReservoirResponse, TokensResponse, BlockaidResultType, Blockaid, Market, TokenResponse, TopBid, LastSale, FeeBreakdown, Attributes, Collection, Royalties, Ownership, FloorAsk, Price, Metadata, } from './NftDetectionController'; - export { NftDetectionController } from './NftDetectionController'; --export type { TokenBalancesControllerMessenger, TokenBalancesControllerActions, TokenBalancesControllerGetStateAction, TokenBalancesControllerEvents, TokenBalancesControllerStateChangeEvent, } from './TokenBalancesController'; -+export type { TokenBalancesControllerMessenger, TokenBalancesControllerActions, TokenBalancesControllerGetStateAction, TokenBalancesControllerEvents, TokenBalancesControllerStateChangeEvent, TokenBalancesControllerState, } from './TokenBalancesController'; - export { TokenBalancesController } from './TokenBalancesController'; - export type { TokenDetectionControllerMessenger, TokenDetectionControllerActions, TokenDetectionControllerGetStateAction, TokenDetectionControllerEvents, TokenDetectionControllerStateChangeEvent, } from './TokenDetectionController'; - export { TokenDetectionController } from './TokenDetectionController'; diff --git a/yarn.lock b/yarn.lock index a7730cef374..2734f622b77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4294,7 +4294,7 @@ "@metamask/utils" "^3.4.1" superstruct "^1.0.3" -"@metamask/abi-utils@^2.0.2", "@metamask/abi-utils@^2.0.4": +"@metamask/abi-utils@^2.0.3", "@metamask/abi-utils@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@metamask/abi-utils/-/abi-utils-2.0.4.tgz#20908c1d910f7a17a89fdf5778a5c59d5cb8b8be" integrity sha512-StnIgUB75x7a7AgUhiaUZDpCsqGp7VkNnZh2XivXkJ6mPkE83U8ARGQj5MbRis7VJY8BC5V1AbB1fjdh0hupPQ== @@ -4328,7 +4328,7 @@ "@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", "@metamask/approval-controller@^7.1.0": +"@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== @@ -4338,37 +4338,33 @@ "@metamask/utils" "^9.1.0" nanoid "^3.1.31" -"@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== +"@metamask/assets-controllers@^37.0.0": + version "37.0.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-37.0.0.tgz#89ab8794118066892f5da1370b4051c0e850b204" + integrity sha512-84rF+Bg65ocPP0MWHJQgWchsqnFXARck60SirulSVuYMPA2chwe276r/qe1ZtLlMxQASROCRNayjAR4VK/Aj5Q== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/address" "^5.7.0" "@ethersproject/bignumber" "^5.7.0" "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0" - "@metamask/abi-utils" "^2.0.2" - "@metamask/accounts-controller" "^17.2.0" - "@metamask/approval-controller" "^7.0.1" - "@metamask/base-controller" "^6.0.1" + "@metamask/abi-utils" "^2.0.3" + "@metamask/base-controller" "^6.0.2" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.0.1" + "@metamask/controller-utils" "^11.0.2" "@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/polling-controller" "^9.0.1" "@metamask/rpc-errors" "^6.3.1" - "@metamask/utils" "^9.0.0" + "@metamask/utils" "^9.1.0" "@types/bn.js" "^5.1.5" "@types/uuid" "^8.3.0" async-mutex "^0.5.0" bn.js "^5.2.1" cockatiel "^3.1.2" + immer "^9.0.6" lodash "^4.17.21" - multiformats "^9.5.2" + multiformats "^13.1.0" single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" @@ -4396,7 +4392,7 @@ "@metamask/utils" "^8.3.0" immer "^9.0.6" -"@metamask/base-controller@^6.0.0", "@metamask/base-controller@^6.0.1", "@metamask/base-controller@^6.0.2": +"@metamask/base-controller@^6.0.0", "@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== @@ -4446,7 +4442,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.1", "@metamask/controller-utils@^11.0.2", "@metamask/controller-utils@^11.3.0": +"@metamask/controller-utils@^11.0.0", "@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== @@ -4905,7 +4901,7 @@ bech32 "^2.0.0" uuid "^9.0.1" -"@metamask/keyring-controller@^17.1.0", "@metamask/keyring-controller@^17.2.1", "@metamask/keyring-controller@^17.2.2": +"@metamask/keyring-controller@^17.2.1", "@metamask/keyring-controller@^17.2.2": version "17.2.2" resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-17.2.2.tgz#944bc693305b4a6e4f1e73739a82c4bc6573dd9a" integrity sha512-Shqk0ybcTPrHQLlBJ1V+InuYC7nD3/a6Ws0XCcBCOfkLTXvtSooKIWBioK83XlHMHkfsM6+bySxSqXJVgJvBZw== @@ -5193,7 +5189,7 @@ fast-json-stable-stringify "^2.1.0" uuid "^8.3.2" -"@metamask/polling-controller@^9.0.0": +"@metamask/polling-controller@^9.0.1": version "9.0.1" resolved "https://registry.yarnpkg.com/@metamask/polling-controller/-/polling-controller-9.0.1.tgz#3d72ab925bace25f5f6b2c5209777708b1fc168e" integrity sha512-GmTqEA7RlJ6fO2G15HW1hUVay1m+CQAsWJ835rU8Udz8FmIBTWPQVRMITq0bQexTHi18YZHgm1sx0iwrPscS3Q== @@ -5236,14 +5232,6 @@ "@metamask/base-controller" "^5.0.2" "@metamask/controller-utils" "^9.1.0" -"@metamask/preferences-controller@^13.0.0": - version "13.0.3" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-13.0.3.tgz#b38dcda227436e935442989e2be5652316d29f8a" - integrity sha512-gNl+sTzvKFRj80h+fLNHGh72LCowBX1LvjZXzqHoTP4CZiHvMCO1vsQZY0Tboh7en9kwah1OdbQVzLRCgOsSIA== - dependencies: - "@metamask/base-controller" "^7.0.1" - "@metamask/controller-utils" "^11.3.0" - "@metamask/profile-sync-controller@^0.9.7": version "0.9.7" resolved "https://registry.yarnpkg.com/@metamask/profile-sync-controller/-/profile-sync-controller-0.9.7.tgz#d5e78cb8004f0dcb8637410bb8b54911e8f2c0a7" @@ -22680,7 +22668,12 @@ multicodec@^1.0.0: buffer "^5.6.0" varint "^5.0.0" -multiformats@^9.4.2, multiformats@^9.5.2: +multiformats@^13.1.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-13.3.0.tgz#1f5188bc7c4fe08ff829ae1c18dc33409042fb71" + integrity sha512-CBiqvsufgmpo01VT5ze94O+uc+Pbf6f/sThlvWss0sBZmAOu6GQn5usrYV2sf2mr17FWYc0rO8c/CNe2T90QAA== + +multiformats@^9.4.2: version "9.9.0" resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== From 1cf60a3f73b1fb08eef08dd304c73521d7fe4e12 Mon Sep 17 00:00:00 2001 From: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com> Date: Thu, 31 Oct 2024 00:33:40 +0100 Subject: [PATCH 03/15] chore: bump `@metamask/preferences-controller` from `^11.0.0` to `^13.1.0` (#12055) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR bumps `@metamask/preferences-controller` from `^11.0.0` to `^13.1.0` ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/11043 ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/4ff18a32-90af-4b2c-b962-79ce04da1919 ## **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** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. Co-authored-by: sahar-fehri --- app/core/Engine.ts | 1 - app/util/test/initial-background-state.json | 3 - package.json | 2 +- ...tamask+preferences-controller+11.0.0.patch | 299 ------------------ ...tamask+preferences-controller+13.1.0.patch | 229 ++++++++++++++ yarn.lock | 35 +- 6 files changed, 236 insertions(+), 333 deletions(-) delete mode 100644 patches/@metamask+preferences-controller+11.0.0.patch create mode 100644 patches/@metamask+preferences-controller+13.1.0.patch diff --git a/app/core/Engine.ts b/app/core/Engine.ts index a5abb765362..3d72ab1a406 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -530,7 +530,6 @@ export class Engine { }); const preferencesController = new PreferencesController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'PreferencesController', allowedActions: [], diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 93bcf8fd60a..890cb61480e 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -165,9 +165,6 @@ "displayNftMedia": true, "securityAlertsEnabled": true, "isMultiAccountBalancesEnabled": true, - "disabledRpcMethodPreferences": { - "eth_sign": false - }, "showMultiRpcModal": false, "showTestNetworks": false, "showIncomingTransactions": { diff --git a/package.json b/package.json index b9c11bcd519..c6ac4f12fbc 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.32.0", - "@metamask/preferences-controller": "^11.0.0", + "@metamask/preferences-controller": "^13.1.0", "@metamask/profile-sync-controller": "^0.9.7", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-button": "^3.0.0", diff --git a/patches/@metamask+preferences-controller+11.0.0.patch b/patches/@metamask+preferences-controller+11.0.0.patch deleted file mode 100644 index 6e711d24199..00000000000 --- a/patches/@metamask+preferences-controller+11.0.0.patch +++ /dev/null @@ -1,299 +0,0 @@ -diff --git a/node_modules/@metamask/preferences-controller/dist/chunk-FSWGV6H6.js b/node_modules/@metamask/preferences-controller/dist/chunk-FSWGV6H6.js -index 30e985c..8dabed3 100644 ---- a/node_modules/@metamask/preferences-controller/dist/chunk-FSWGV6H6.js -+++ b/node_modules/@metamask/preferences-controller/dist/chunk-FSWGV6H6.js -@@ -17,13 +17,19 @@ var metadata = { - isIpfsGatewayEnabled: { persist: true, anonymous: true }, - isMultiAccountBalancesEnabled: { persist: true, anonymous: true }, - lostIdentities: { persist: true, anonymous: false }, -- openSeaEnabled: { persist: true, anonymous: true }, -+ displayNftMedia: { persist: true, anonymous: true }, -+ useSafeChainsListValidation: { persist: true, anonymous: true }, - securityAlertsEnabled: { persist: true, anonymous: true }, - selectedAddress: { persist: true, anonymous: false }, - showTestNetworks: { persist: true, anonymous: true }, - showIncomingTransactions: { persist: true, anonymous: true }, - useNftDetection: { persist: true, anonymous: true }, -- useTokenDetection: { persist: true, anonymous: true } -+ useTokenDetection: { persist: true, anonymous: true }, -+ smartTransactionsOptInStatus: { persist: true, anonymous: true }, -+ useTransactionSimulations: { persist: true, anonymous: true }, -+ showMultiRpcModal: { persist: false, anonymous: false }, -+ tokenSortConfig: { persist: true, anonymous: false }, -+ privacyMode: { persist: true, anonymous: true }, - }; - var name = "PreferencesController"; - function getDefaultPreferencesState() { -@@ -37,7 +43,7 @@ function getDefaultPreferencesState() { - isIpfsGatewayEnabled: true, - isMultiAccountBalancesEnabled: true, - lostIdentities: {}, -- openSeaEnabled: false, -+ displayNftMedia: false, - securityAlertsEnabled: false, - selectedAddress: "", - showIncomingTransactions: { -@@ -64,7 +70,17 @@ function getDefaultPreferencesState() { - }, - showTestNetworks: false, - useNftDetection: false, -- useTokenDetection: true -+ useTokenDetection: true, -+ useSafeChainsListValidation: true, -+ smartTransactionsOptInStatus: false, -+ useTransactionSimulations: true, -+ showMultiRpcModal: false, -+ tokenSortConfig: { -+ key: "tokenFiatBalance", -+ order: 'dsc', -+ sortCallback: 'stringNumeric', -+ }, -+ privacyMode: false, - }; - } - var _syncIdentities, syncIdentities_fn; -@@ -213,9 +229,9 @@ var PreferencesController = class extends _basecontroller.BaseController { - * @param useNftDetection - Boolean indicating user preference on NFT detection. - */ - setUseNftDetection(useNftDetection) { -- if (useNftDetection && !this.state.openSeaEnabled) { -+ if (useNftDetection && !this.state.displayNftMedia) { - throw new Error( -- "useNftDetection cannot be enabled if openSeaEnabled is false" -+ "useNftDetection cannot be enabled if displayNftMedia is false" - ); - } - this.update((state) => { -@@ -223,18 +239,33 @@ var PreferencesController = class extends _basecontroller.BaseController { - }); - } - /** -- * Toggle the opensea enabled setting. -+ * Toggle the display nft media enabled setting. - * -- * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. -+ * @param displayNftMedia - Boolean indicating user preference on using web2 third parties. - */ -- setOpenSeaEnabled(openSeaEnabled) { -+ setDisplayNftMedia(displayNftMedia) { - this.update((state) => { -- state.openSeaEnabled = openSeaEnabled; -- if (!openSeaEnabled) { -+ state.displayNftMedia = displayNftMedia; -+ if (!displayNftMedia) { - state.useNftDetection = false; - } - }); - } -+ -+ /** -+ * Toggle the use safe chains list validation. -+ * -+ * @param useSafeChainsListValidation - Boolean indicating user preference on using chainid.network third part to check safe networks. -+ */ -+ setUseSafeChainsListValidation(useSafeChainsListValidation) { -+ this.update((state) => { -+ state.useSafeChainsListValidation = useSafeChainsListValidation; -+ if (!useSafeChainsListValidation) { -+ state.useSafeChainsListValidation = false; -+ } -+ }) -+ } -+ - /** - * Toggle the security alert enabled setting. - * -@@ -245,6 +276,43 @@ var PreferencesController = class extends _basecontroller.BaseController { - state.securityAlertsEnabled = securityAlertsEnabled; - }); - } -+ -+ /** -+ * Toggle multi rpc migration modal. -+ * -+ * @param showMultiRpcModal - Boolean indicating if the multi rpc modal will be displayed or not. -+ */ -+ setShowMultiRpcModal(showMultiRpcModal) { -+ this.update((state) => { -+ state.showMultiRpcModal = showMultiRpcModal; -+ if (showMultiRpcModal) { -+ state.showMultiRpcModal = false; -+ } -+ }); -+ } -+ -+ /** -+ * A setter for the user to opt into smart transactions -+ * -+ * @param smartTransactionsOptInStatus - true to opt into smart transactions -+ */ -+ setSmartTransactionsOptInStatus(smartTransactionsOptInStatus) { -+ this.update((state) => { -+ state.smartTransactionsOptInStatus = smartTransactionsOptInStatus; -+ }); -+ } -+ -+ /** -+ * A setter for the user to opt into transaction simulations -+ * -+ * @param useTransactionSimulations - true to opt into transaction simulations -+ */ -+ setUseTransactionSimulations(useTransactionSimulations) { -+ this.update((state) => { -+ state.useTransactionSimulations = useTransactionSimulations; -+ }); -+ } -+ - /** - * A setter for the user preferences to enable/disable rpc methods. - * -@@ -307,6 +375,26 @@ var PreferencesController = class extends _basecontroller.BaseController { - }); - } - } -+ -+ /** -+ * Set the token sort configuration setting. -+ * -+ * @param tokenSortConfig - Object describing token sort configuration. -+ */ -+ setTokenSortConfig(tokenSortConfig) { -+ this.update((state) => { -+ state.tokenSortConfig = tokenSortConfig; -+ }); -+ } -+ -+ /** -+ * A setter for the user hide balance and assets -+ * -+ * @param privacyMode - true to hide balance and assets -+ */ -+ setPrivacyMode(privacyMode) { -+ this.update((state) => { state.privacyMode = privacyMode; }); -+ } - }; - _syncIdentities = new WeakSet(); - syncIdentities_fn = function(addresses) { -diff --git a/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts b/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts -index 7e3ba15..dda7eb7 100644 ---- a/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts -+++ b/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts -@@ -69,9 +69,10 @@ export type PreferencesState = { - [address: string]: Identity; - }; - /** -- * Controls whether the OpenSea API is used -+ * Controls whether the web2 third parties are used - */ -- openSeaEnabled: boolean; -+ displayNftMedia: boolean; -+ useSafeChainsListValidation: boolean, - /** - * Controls whether "security alerts" are enabled - */ -@@ -98,6 +99,26 @@ export type PreferencesState = { - * Controls whether token detection is enabled - */ - useTokenDetection: boolean; -+ /** -+ * Controls whether smart transactions are opted into -+ */ -+ smartTransactionsOptInStatus: boolean; -+ /** -+ * Controls whether transaction simulations are opted into -+ */ -+ useTransactionSimulations: boolean; -+ /** -+ * Controls whether Multi rpc modal is displayed or not -+ */ -+ showMultiRpcModal: boolean; -+ /** -+ * Controls the token sort configuration -+ */ -+ tokenSortConfig: Record; -+ /** -+ * Controls whether balance and assets are hidden or not -+ */ -+ privacyMode: boolean; - }; - declare const name = "PreferencesController"; - export type PreferencesControllerGetStateAction = ControllerGetStateAction; -@@ -121,7 +142,7 @@ export declare function getDefaultPreferencesState(): { - isIpfsGatewayEnabled: boolean; - isMultiAccountBalancesEnabled: boolean; - lostIdentities: {}; -- openSeaEnabled: boolean; -+ displayNftMedia: boolean; - securityAlertsEnabled: boolean; - selectedAddress: string; - showIncomingTransactions: { -@@ -149,6 +170,11 @@ export declare function getDefaultPreferencesState(): { - showTestNetworks: boolean; - useNftDetection: boolean; - useTokenDetection: boolean; -+ smartTransactionsOptInStatus: boolean; -+ useTransactionSimulations: boolean; -+ showMultiRpcModal: boolean; -+ tokenSortConfig: Record; -+ privacyMode: boolean; - }; - /** - * Controller that stores shared settings and exposes convenience methods -@@ -217,11 +243,16 @@ export declare class PreferencesController extends BaseController): void; -+ /** -+ * Toggle the balance and assets hidden setting. -+ * -+ * @param privacyMode - Boolean indicating user preference on hiding balance and assets. -+ */ -+ setPrivacyMode(privacyMode: boolean): void; - } - export default PreferencesController; - //# sourceMappingURL=PreferencesController.d.ts.map -\ No newline at end of file diff --git a/patches/@metamask+preferences-controller+13.1.0.patch b/patches/@metamask+preferences-controller+13.1.0.patch new file mode 100644 index 00000000000..1ad700b6326 --- /dev/null +++ b/patches/@metamask+preferences-controller+13.1.0.patch @@ -0,0 +1,229 @@ +diff --git a/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs b/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs +index 97182f2..634de2e 100644 +--- a/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs ++++ b/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs +@@ -17,7 +17,7 @@ const metadata = { + isIpfsGatewayEnabled: { persist: true, anonymous: true }, + isMultiAccountBalancesEnabled: { persist: true, anonymous: true }, + lostIdentities: { persist: true, anonymous: false }, +- openSeaEnabled: { persist: true, anonymous: true }, ++ displayNftMedia: { persist: true, anonymous: true }, + securityAlertsEnabled: { persist: true, anonymous: true }, + selectedAddress: { persist: true, anonymous: false }, + showTestNetworks: { persist: true, anonymous: true }, +@@ -26,6 +26,10 @@ const metadata = { + useTokenDetection: { persist: true, anonymous: true }, + smartTransactionsOptInStatus: { persist: true, anonymous: false }, + useTransactionSimulations: { persist: true, anonymous: true }, ++ useSafeChainsListValidation: { persist: true, anonymous: true }, ++ showMultiRpcModal: { persist: false, anonymous: false }, ++ tokenSortConfig: { persist: true, anonymous: false }, ++ privacyMode: { persist: true, anonymous: true }, + }; + const name = 'PreferencesController'; + /** +@@ -41,7 +45,7 @@ function getDefaultPreferencesState() { + isIpfsGatewayEnabled: true, + isMultiAccountBalancesEnabled: true, + lostIdentities: {}, +- openSeaEnabled: false, ++ displayNftMedia: false, + securityAlertsEnabled: false, + selectedAddress: '', + showIncomingTransactions: { +@@ -71,6 +75,14 @@ function getDefaultPreferencesState() { + useTokenDetection: true, + smartTransactionsOptInStatus: false, + useTransactionSimulations: true, ++ useSafeChainsListValidation: true, ++ showMultiRpcModal: false, ++ tokenSortConfig: { ++ key: 'tokenFiatBalance', ++ order: 'dsc', ++ sortCallback: 'stringNumeric' ++ }, ++ privacyMode: false, + }; + } + exports.getDefaultPreferencesState = getDefaultPreferencesState; +@@ -209,22 +221,22 @@ class PreferencesController extends base_controller_1.BaseController { + * @param useNftDetection - Boolean indicating user preference on NFT detection. + */ + setUseNftDetection(useNftDetection) { +- if (useNftDetection && !this.state.openSeaEnabled) { +- throw new Error('useNftDetection cannot be enabled if openSeaEnabled is false'); ++ if (useNftDetection && !this.state.displayNftMedia) { ++ throw new Error('useNftDetection cannot be enabled if displayNftMedia is false'); + } + this.update((state) => { + state.useNftDetection = useNftDetection; + }); + } + /** +- * Toggle the opensea enabled setting. ++ * Toggle the display nft media enabled setting. + * +- * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. ++ * @param displayNftMedia - Boolean indicating user preference on using web2 third parties. + */ +- setOpenSeaEnabled(openSeaEnabled) { ++ setDisplayNftMedia(displayNftMedia) { + this.update((state) => { +- state.openSeaEnabled = openSeaEnabled; +- if (!openSeaEnabled) { ++ state.displayNftMedia = displayNftMedia; ++ if (!displayNftMedia) { + state.useNftDetection = false; + } + }); +@@ -305,6 +317,49 @@ class PreferencesController extends base_controller_1.BaseController { + state.useTransactionSimulations = useTransactionSimulations; + }); + } ++ /** ++ * Toggle the use safe chains list validation. ++ * ++ * @param useSafeChainsListValidation - Boolean indicating user preference on using chainid.network third part to check safe networks. ++ */ ++ setUseSafeChainsListValidation(useSafeChainsListValidation) { ++ this.update((state) => { ++ state.useSafeChainsListValidation = useSafeChainsListValidation; ++ }); ++ } ++ /** ++ * Toggle multi rpc migration modal. ++ * ++ * @param showMultiRpcModal - Boolean indicating if the multi rpc modal will be displayed or not. ++ */ ++ setShowMultiRpcModal(showMultiRpcModal) { ++ this.update((state) => { ++ state.showMultiRpcModal = showMultiRpcModal; ++ if (showMultiRpcModal) { ++ state.showMultiRpcModal = false; ++ } ++ }); ++ } ++ /** ++ * Set the token sort configuration setting. ++ * ++ * @param tokenSortConfig - Object describing token sort configuration. ++ */ ++ setTokenSortConfig(tokenSortConfig) { ++ this.update((state) => { ++ state.tokenSortConfig = tokenSortConfig; ++ }); ++ } ++ /** ++ * A setter for the user preferences to enable/disable privacy mode. ++ * ++ * @param privacyMode - true to enable privacy mode, false to disable it. ++ */ ++ setPrivacyMode(privacyMode) { ++ this.update((state) => { ++ state.privacyMode = privacyMode; ++ }); ++ } + } + exports.PreferencesController = PreferencesController; + _PreferencesController_instances = new WeakSet(), _PreferencesController_syncIdentities = function _PreferencesController_syncIdentities(addresses) { +diff --git a/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts b/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts +index 04a9d6f..77a9548 100644 +--- a/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts ++++ b/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts +@@ -65,7 +65,7 @@ export type PreferencesState = { + /** + * Controls whether the OpenSea API is used + */ +- openSeaEnabled: boolean; ++ displayNftMedia: boolean; + /** + * Controls whether "security alerts" are enabled + */ +@@ -100,6 +100,22 @@ export type PreferencesState = { + * Controls whether transaction simulations are enabled + */ + useTransactionSimulations: boolean; ++ /** ++ * Controls whether to use the safe chains list validation ++ */ ++ useSafeChainsListValidation: boolean; ++ /** ++ * Controls whether Multi rpc modal is displayed or not ++ */ ++ showMultiRpcModal: boolean; ++ /** ++ * Controls whether Multi rpc modal is displayed or not ++ */ ++ tokenSortConfig: Record; ++ /** ++ * Controls whether balance and assets are hidden or not ++ */ ++ privacyMode: boolean; + }; + declare const name = "PreferencesController"; + export type PreferencesControllerGetStateAction = ControllerGetStateAction; +@@ -120,7 +136,7 @@ export declare function getDefaultPreferencesState(): { + isIpfsGatewayEnabled: boolean; + isMultiAccountBalancesEnabled: boolean; + lostIdentities: {}; +- openSeaEnabled: boolean; ++ displayNftMedia: boolean; + securityAlertsEnabled: boolean; + selectedAddress: string; + showIncomingTransactions: { +@@ -150,6 +166,10 @@ export declare function getDefaultPreferencesState(): { + useTokenDetection: boolean; + smartTransactionsOptInStatus: boolean; + useTransactionSimulations: boolean; ++ useSafeChainsListValidation: boolean; ++ showMultiRpcModal: boolean; ++ tokenSortConfig: Record; ++ privacyMode: boolean; + }; + /** + * Controller that stores shared settings and exposes convenience methods +@@ -218,11 +238,11 @@ export declare class PreferencesController extends BaseController): void; ++ /** ++ * A setter for the user preferences to enable/disable privacy mode. ++ * ++ * @param privacyMode - true to enable privacy mode, false to disable it. ++ */ ++ setPrivacyMode(privacyMode: boolean): void; + } + export default PreferencesController; + //# sourceMappingURL=PreferencesController.d.cts.map diff --git a/yarn.lock b/yarn.lock index 2734f622b77..9ffae1c415c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4384,14 +4384,6 @@ "@metamask/utils" "^8.3.0" immer "^9.0.6" -"@metamask/base-controller@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-5.0.2.tgz#ab3584f67d9f2ff80958df21558e61650074e565" - integrity sha512-izOaXXnLz9OXbdika0ZvIDf24pgsWNPI02Lm0E4eMU61ICpV78bzQB7YyIbMtF6MWnItw1RnX9jN6zNEmp5pdA== - dependencies: - "@metamask/utils" "^8.3.0" - immer "^9.0.6" - "@metamask/base-controller@^6.0.0", "@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" @@ -4470,21 +4462,6 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" -"@metamask/controller-utils@^9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-9.1.0.tgz#436ff37d339df3f4b0f31458881c6f1b1002c945" - integrity sha512-17XQhyhR1bC7NjQHJF2KhxStVeoFW8liQ/Z526cI3uVcKOgYRxxDwBiRGs+xzv9XAm7f1W73W83wnb8fcBxlxg== - dependencies: - "@ethereumjs/util" "^8.1.0" - "@metamask/eth-query" "^4.0.0" - "@metamask/ethjs-unit" "^0.3.0" - "@metamask/utils" "^8.3.0" - "@spruceid/siwe-parser" "2.1.0" - "@types/bn.js" "^5.1.5" - bn.js "^5.2.1" - eth-ens-namehash "^2.0.8" - fast-deep-equal "^3.1.3" - "@metamask/design-tokens@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-4.0.0.tgz#3aad7e4da21c279374668e179c0b055d93aa0552" @@ -5224,13 +5201,13 @@ eslint-plugin-n "^16.6.2" json-rpc-random-id "^1.0.1" -"@metamask/preferences-controller@^11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-11.0.0.tgz#56a74c932d4577fe7c97eb3ace4d8bc3219b6b1b" - integrity sha512-ntHdjUuEB5cRoCc3GaQb5pzStvmi3BuvgIhwgEwLiogiBAQlbRJR6wczbp0qpGDyIkdWB2DDl/xi/s4bBtKDxw== +"@metamask/preferences-controller@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-13.1.0.tgz#e407f6a0879d5fa4178caa0bdd4e310d178f0b34" + integrity sha512-fRURU9ZJaztJ+dPq+DrLbEcddhAoRDO5poagb+T8cFmIDxvTA1OKR1Bz8mgn91xMTuWMg95M+bPnb5dbRbRViw== dependencies: - "@metamask/base-controller" "^5.0.2" - "@metamask/controller-utils" "^9.1.0" + "@metamask/base-controller" "^7.0.1" + "@metamask/controller-utils" "^11.3.0" "@metamask/profile-sync-controller@^0.9.7": version "0.9.7" From 23d311ca48e273c3aa2a63532ee881cf5fa75200 Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Thu, 31 Oct 2024 12:42:16 +0100 Subject: [PATCH 04/15] feat: Add re-simulation feature (#12107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR cherry-picks https://github.com/MetaMask/metamask-mobile/pull/12073 **Quality gate failures:** These are expected as we discussed - all those failures will be addressed in the https://github.com/MetaMask/MetaMask-planning/issues/3584 **Node version update:** It's a side effect of the bump to `7.0.1` of` @metamask/rpc-errors`. https://github.com/MetaMask/rpc-errors/blob/main/package.json#L92 ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3380 ## **Manual testing steps** ## **Screenshots/Recordings** This feature already tested and released. Please see the original picked PR for recording. ### **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. --------- Co-authored-by: Nico MASSART --- .nvmrc | 2 +- app/components/Nav/App/index.js | 5 + .../ChangeInSimulationModal.test.tsx | 73 +++ .../ChangeInSimulationModal.tsx | 93 ++++ .../ChangeInSimulationModal.test.tsx.snap | 170 +++++++ .../__snapshots__/index.test.tsx.snap | 478 ++++++++++++++++-- .../Views/confirmations/Approval/index.js | 47 +- .../confirmations/Approval/index.test.tsx | 57 +-- .../ApproveView/Approve/index.js | 36 +- .../confirmations/SendFlow/Confirm/index.js | 21 + app/constants/navigation/Routes.ts | 1 + app/core/Engine.ts | 1 + .../smart-transactions/smart-publish-hook.ts | 1 + locales/languages/en.json | 6 + package.json | 4 +- yarn.lock | 62 ++- 16 files changed, 962 insertions(+), 95 deletions(-) create mode 100644 app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.test.tsx create mode 100644 app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.tsx create mode 100644 app/components/Views/ChangeInSimulationModal/__snapshots__/ChangeInSimulationModal.test.tsx.snap diff --git a/.nvmrc b/.nvmrc index 48b14e6b2b5..3516580bbbc 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.14.0 +20.17.0 diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 1b79af4b2b7..2965348a569 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -124,6 +124,7 @@ import NftOptions from '../../../components/Views/NftOptions'; import ShowTokenIdSheet from '../../../components/Views/ShowTokenIdSheet'; import OriginSpamModal from '../../Views/OriginSpamModal/OriginSpamModal'; import { isNetworkUiRedesignEnabled } from '../../../util/networks/isNetworkUiRedesignEnabled'; +import ChangeInSimulationModal from '../../Views/ChangeInSimulationModal/ChangeInSimulationModal'; import TooltipModal from '../../../components/Views/TooltipModal'; ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { SnapsExecutionWebView } from '../../../lib/snaps'; @@ -502,6 +503,10 @@ const RootModalFlow = () => ( name={Routes.SHEET.ORIGIN_SPAM_MODAL} component={OriginSpamModal} /> + ); diff --git a/app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.test.tsx b/app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.test.tsx new file mode 100644 index 00000000000..abced2cc0c7 --- /dev/null +++ b/app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.test.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { fireEvent } from '@testing-library/react-native'; + +import renderWithProvider, { + DeepPartial, +} from '../../../util/test/renderWithProvider'; +import { backgroundState } from '../../../util/test/initial-root-state'; +import ChangeInSimulationModal, { + PROCEED_BUTTON_TEST_ID, + REJECT_BUTTON_TEST_ID, +} from './ChangeInSimulationModal'; +import { RootState } from '../../../reducers'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: () => jest.fn(), +})); + +jest.mock( + '../../../component-library/components/BottomSheets/BottomSheet', + () => + ({ children }: { children: React.ReactElement }) => + <>{children}, +); + +const NAVIGATION_PARAMS_MOCK = { + params: { + onProceed: jest.fn(), + onReject: jest.fn(), + }, +}; + +const mockInitialState: DeepPartial = { + engine: { + backgroundState: { + ...backgroundState, + }, + }, +}; + +describe('ChangeInSimulationModal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('render matches snapshot', () => { + const { toJSON } = renderWithProvider( + , + { + state: mockInitialState, + }, + ); + expect(toJSON()).toMatchSnapshot(); + }); + + it('calls onProceed and onReject callbacks', () => { + const mockOnReject = jest.fn(); + const mockOnProceed = jest.fn(); + const wrapper = renderWithProvider( + , + { + state: mockInitialState, + }, + ); + fireEvent.press(wrapper.getByTestId(PROCEED_BUTTON_TEST_ID)); + expect(mockOnProceed).toHaveBeenCalledTimes(1); + + fireEvent.press(wrapper.getByTestId(REJECT_BUTTON_TEST_ID)); + expect(mockOnReject).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.tsx b/app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.tsx new file mode 100644 index 00000000000..ccd7d604f47 --- /dev/null +++ b/app/components/Views/ChangeInSimulationModal/ChangeInSimulationModal.tsx @@ -0,0 +1,93 @@ +import React, { useCallback, useRef } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { strings } from '../../../../locales/i18n'; +import BottomSheet, { + BottomSheetRef, +} from '../../../component-library/components/BottomSheets/BottomSheet'; +import Button from '../../../component-library/components/Buttons/Button/Button'; +import Icon, { + IconSize, + IconName, + IconColor, +} from '../../../component-library/components/Icons/Icon'; +import { + ButtonSize, + ButtonVariants, + ButtonWidthTypes, +} from '../../../component-library/components/Buttons/Button'; +import SheetHeader from '../../../component-library/components/Sheet/SheetHeader'; +import Text from '../../../component-library/components/Texts/Text'; + +export const PROCEED_BUTTON_TEST_ID = 'proceed-button'; +export const REJECT_BUTTON_TEST_ID = 'reject-button'; + +const createStyles = () => + StyleSheet.create({ + buttonsWrapper: { + alignSelf: 'stretch', + flexDirection: 'column', + gap: 16, + paddingTop: 24, + }, + wrapper: { + alignItems: 'center', + padding: 16, + }, + description: { + textAlign: 'center', + }, + }); + +const ChangeInSimulationModal = ({ + route, +}: { + route: { params: { onProceed: () => void; onReject: () => void } }; +}) => { + const styles = createStyles(); + const sheetRef = useRef(null); + const { onProceed, onReject } = route.params; + + const handleProceed = useCallback(() => { + sheetRef.current?.onCloseBottomSheet(); + onProceed(); + }, [onProceed, sheetRef]); + + const handleReject = useCallback(() => { + sheetRef.current?.onCloseBottomSheet(); + onReject(); + }, [onReject, sheetRef]); + + return ( + + + + + {strings('change_in_simulation_modal.description')} + +