From a70915f1120cf43d9f17bb8dc98514303f846336 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:36:12 +0100 Subject: [PATCH 1/2] fix: Bump smart-transactions-controller to ^16.0.1 (#12847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Release notes for 16.0.1: https://github.com/MetaMask/smart-transactions-controller/releases/tag/v16.0.1 ## **Related issues** Fixes: ## **Manual testing steps** 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. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2eeeb68a5a5..0c94bf43d3d 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "@metamask/selected-network-controller": "^19.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", - "@metamask/smart-transactions-controller": "^16.0.0", + "@metamask/smart-transactions-controller": "^16.0.1", "@metamask/snaps-controllers": "^9.15.0", "@metamask/snaps-execution-environments": "^6.10.0", "@metamask/snaps-rpc-methods": "^11.7.0", diff --git a/yarn.lock b/yarn.lock index 0634bd37f44..b2aa515ace3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5211,10 +5211,10 @@ resolved "https://registry.yarnpkg.com/@metamask/slip44/-/slip44-4.1.0.tgz#6f2702de7ba64dad3ab6586ea3ac4e5647804b0a" integrity sha512-RQ2MJO0X3QLnJo0rFlb83h2tNAkqqx/VNOPLc3/S2CvY3/cXy3UAEw/xRM/475BeAAkWI93yiIn/FoGUy3E0Ig== -"@metamask/smart-transactions-controller@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@metamask/smart-transactions-controller/-/smart-transactions-controller-16.0.0.tgz#d5f26e3f25945dc695c7e7152f8ab4c9ffa85ac9" - integrity sha512-NfX4yvWlB5MQvkpp+1hsInom1+f0D+xK6b3n/csGJgsDuTWXIS+C3hdYBMS5bpZIrjobFRBG1LH+YQBBsndPHg== +"@metamask/smart-transactions-controller@^16.0.1": + version "16.0.1" + resolved "https://registry.yarnpkg.com/@metamask/smart-transactions-controller/-/smart-transactions-controller-16.0.1.tgz#daf658e98b22f5bf57dd960a6d5d5836b0f40d34" + integrity sha512-ZKvBd0pMiZn6baVDVYmf9NG7HS8dWuyVz8dOVZ8ddYAGY9mBvsf9VldqHmUdAqzGqew89uLpeJbAnCYbmc4nXg== dependencies: "@babel/runtime" "^7.24.1" "@ethereumjs/tx" "^5.2.1" From 4449dd5ed26607edaedb80e985f3c52f30127539 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 8 Jan 2025 08:48:57 -0700 Subject: [PATCH 2/2] chore: Bump `@metamask/swaps-controller` to 12.0.0 (#12378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This version upgrades `@metamask/swaps-controller` so that it is less reliant on the global network. Specifically: - The `setChainId` and `setProvider` methods have been removed from SwapsController. - The `fetchGasFeeEstimates` and `fetchEstimatedMultiLayerL1Fee` SwapsController constructor options are now expected to take a `networkClientId`. - The SwapsController constructor no longer takes a `chainId` option. - `startFetchAndSetQuotes`, `fetchTokenWithCache`, `fetchTopAssetsWithCache`, and `fetchAggregatorMetadataWithCache` now take a `networkClientId`. - The `fetchParamsMetaData` SwapsController state property now includes a `networkClientId`. - The chain cache in SwapsController state will now automatically be updated whenever the network has changed. See full changelog here: https://github.com/MetaMask/swaps-controller/blob/main/CHANGELOG.md#1200 At the moment, the global network client ID is still passed into SwapsController whenever it is used, but now that can be changed to use a dapp-level network client ID without needing to update SwapsController. ## Related issues Fixes #12470. Also see: - https://github.com/MetaMask/swaps-controller/pull/347 - https://github.com/MetaMask/swaps-controller/issues/204 ## Manual testing steps - Tap on the Swap button - Select a destination token and proceed - You should see new quotes - Ideally, you should be able to complete the swaps flow and create a transaction ## Screenshots/Recordings The Swaps flow should work exactly like it does now. Here is a video demonstrating fetching Swaps quotes: https://github.com/user-attachments/assets/62a490c9-32c4-49a2-886d-136328761658 ## 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. --- .../UI/AssetOverview/AssetOverview.tsx | 16 +- app/components/UI/Swaps/QuotesView.js | 14 +- app/components/UI/Swaps/QuotesView.test.ts | 10 +- .../__snapshots__/QuotesView.test.ts.snap | 2 +- .../__snapshots__/index.test.tsx.snap | 1719 +++++++++++++++++ .../components/LoadingAnimation/index.js | 12 +- .../LoadingAnimation/index.test.tsx | 24 + app/components/UI/Swaps/index.js | 23 +- app/components/UI/Swaps/index.test.tsx | 48 + app/components/UI/Swaps/utils/index.js | 3 + app/core/Engine/Engine.test.ts | 19 +- app/core/Engine/Engine.ts | 14 +- app/reducers/swaps/index.js | 42 +- app/reducers/swaps/swaps.test.ts | 68 + .../logs/__snapshots__/index.test.ts.snap | 1 + app/util/networks/engineNetworkUtils.ts | 14 +- app/util/test/initial-background-state.json | 3 +- package.json | 2 +- yarn.lock | 8 +- 19 files changed, 1967 insertions(+), 75 deletions(-) create mode 100644 app/components/UI/Swaps/components/LoadingAnimation/__snapshots__/index.test.tsx.snap create mode 100644 app/components/UI/Swaps/components/LoadingAnimation/index.test.tsx create mode 100644 app/components/UI/Swaps/index.test.tsx diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index cbcd738dca6..1ceae3ed380 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -11,8 +11,9 @@ import AppConstants from '../../../core/AppConstants'; import Engine from '../../../core/Engine'; import { selectChainId, - selectTicker, selectNativeCurrencyByChainId, + selectSelectedNetworkClientId, + selectTicker, } from '../../../selectors/networkController'; import { selectConversionRate, @@ -58,7 +59,7 @@ import Routes from '../../../constants/navigation/Routes'; import TokenDetails from './TokenDetails'; import { RootState } from '../../../reducers'; import useGoToBridge from '../Bridge/utils/useGoToBridge'; -import SwapsController, { swapsUtils } from '@metamask/swaps-controller'; +import { swapsUtils } from '@metamask/swaps-controller'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { getDecimalChainId, @@ -114,6 +115,7 @@ const AssetOverview: React.FC = ({ ? (asset.chainId as Hex) : selectedChainId; const ticker = isPortfolioViewEnabled() ? nativeCurrency : selectedTicker; + const selectedNetworkClientId = useSelector(selectSelectedNetworkClientId); let currentAddress: Hex; @@ -136,12 +138,12 @@ const AssetOverview: React.FC = ({ const dispatch = useDispatch(); useEffect(() => { - const { SwapsController: SwapsControllerFromEngine } = Engine.context as { - SwapsController: SwapsController; - }; + const { SwapsController } = Engine.context; const fetchTokenWithCache = async () => { try { - await SwapsControllerFromEngine.fetchTokenWithCache(); + await SwapsController.fetchTokenWithCache({ + networkClientId: selectedNetworkClientId, + }); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { @@ -152,7 +154,7 @@ const AssetOverview: React.FC = ({ } }; fetchTokenWithCache(); - }, []); + }, [selectedNetworkClientId]); const onReceive = () => { navigation.navigate(Routes.QR_TAB_SWITCHER, { diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 9d58c556bec..9273641eabb 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -8,7 +8,7 @@ import { TouchableOpacity, Linking, } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import IonicIcon from 'react-native-vector-icons/Ionicons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import BigNumber from 'bignumber.js'; @@ -96,7 +96,7 @@ import { import { selectChainId, selectIsEIP1559Network, - selectNetworkClientId, + selectSelectedNetworkClientId, selectTicker, } from '../../../selectors/networkController'; import { @@ -304,6 +304,7 @@ async function resetAndStartPolling({ destinationToken, sourceAmount, walletAddress, + networkClientId, }) { if (!sourceToken || !destinationToken) { return; @@ -316,6 +317,7 @@ async function resetAndStartPolling({ destinationToken, sourceAmount, walletAddress, + networkClientId, }); await SwapsController.stopPollingAndResetState(); await SwapsController.startFetchAndSetQuotes( @@ -762,6 +764,8 @@ function SwapsQuotesView({ } }, [error, navigation]); + const selectedNetworkClientId = useSelector(selectSelectedNetworkClientId); + const handleRetryFetchQuotes = useCallback(() => { if (error?.key === swapsUtils.SwapsError.QUOTES_EXPIRED_ERROR) { navigation.setParams({ leftAction: strings('navigation.back') }); @@ -776,6 +780,7 @@ function SwapsQuotesView({ destinationToken, sourceAmount, walletAddress: selectedAddress, + networkClientId: selectedNetworkClientId, }); } else { navigation.pop(); @@ -788,6 +793,7 @@ function SwapsQuotesView({ sourceAmount, selectedAddress, navigation, + selectedNetworkClientId, ]); const updateSwapsTransactions = useCallback( @@ -1412,6 +1418,7 @@ function SwapsQuotesView({ destinationToken, sourceAmount, walletAddress: selectedAddress, + networkClientId: selectedNetworkClientId, }); return () => { @@ -1425,6 +1432,7 @@ function SwapsQuotesView({ slippage, sourceAmount, sourceToken.address, + selectedNetworkClientId, ]); /** selectedQuote alert effect */ @@ -2403,7 +2411,7 @@ SwapsQuotesView.propTypes = { const mapStateToProps = (state) => ({ accounts: selectAccounts(state), chainId: selectChainId(state), - networkClientId: selectNetworkClientId(state), + networkClientId: selectSelectedNetworkClientId(state), ticker: selectTicker(state), balances: selectContractBalances(state), selectedAddress: selectSelectedInternalAccountFormattedAddress(state), diff --git a/app/components/UI/Swaps/QuotesView.test.ts b/app/components/UI/Swaps/QuotesView.test.ts index eafafb20e98..eac7a44da30 100644 --- a/app/components/UI/Swaps/QuotesView.test.ts +++ b/app/components/UI/Swaps/QuotesView.test.ts @@ -17,6 +17,7 @@ import { } from '../../../util/test/accountsControllerTestUtils'; import { SwapsViewSelectors } from '../../../../e2e/selectors/swaps/SwapsView.selectors'; import Engine from '../../../core/Engine'; +import { RpcEndpointType } from '@metamask/network-controller'; jest.mock('../../../core/Engine', () => ({ context: { @@ -203,7 +204,14 @@ const mockInitialState: DeepPartial = { defaultRpcEndpointIndex: 0, name: 'Ethereum Mainnet', nativeCurrency: 'ETH', - rpcEndpoints: [], + rpcEndpoints: [ + { + name: 'Ethereum Mainnet', + networkClientId: 'mainnet', + type: RpcEndpointType.Infura, + url: 'https://mainnet.infura.io/v3/{infuraProjectId}', + }, + ], }, }, }, diff --git a/app/components/UI/Swaps/__snapshots__/QuotesView.test.ts.snap b/app/components/UI/Swaps/__snapshots__/QuotesView.test.ts.snap index 658bd7b1498..b7df5af1c32 100644 --- a/app/components/UI/Swaps/__snapshots__/QuotesView.test.ts.snap +++ b/app/components/UI/Swaps/__snapshots__/QuotesView.test.ts.snap @@ -197,7 +197,7 @@ exports[`QuotesView should render quote screen 1`] = ` } } > - Private Network + Ethereum Main Network diff --git a/app/components/UI/Swaps/components/LoadingAnimation/__snapshots__/index.test.tsx.snap b/app/components/UI/Swaps/components/LoadingAnimation/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..76d124104e2 --- /dev/null +++ b/app/components/UI/Swaps/components/LoadingAnimation/__snapshots__/index.test.tsx.snap @@ -0,0 +1,1719 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LoadingAnimation renders 1`] = ` + + + + + Starting... + + + + + + + + + + + + --- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", + } + } + style={ + [ + { + "backgroundColor": "#ffffff", + "flex": 1, + }, + undefined, + ] + } + /> + + + +`; diff --git a/app/components/UI/Swaps/components/LoadingAnimation/index.js b/app/components/UI/Swaps/components/LoadingAnimation/index.js index ec6a1363364..9b282d42517 100644 --- a/app/components/UI/Swaps/components/LoadingAnimation/index.js +++ b/app/components/UI/Swaps/components/LoadingAnimation/index.js @@ -5,8 +5,10 @@ import React, { useRef, useState, } from 'react'; +import { useSelector } from 'react-redux'; import { Animated, View, StyleSheet, Image } from 'react-native'; import PropTypes from 'prop-types'; +import { selectSelectedNetworkClientId } from '../../../../../selectors/networkController'; import Engine from '../../../../../core/Engine'; import Logger from '../../../../../util/Logger'; import Device from '../../../../../util/device'; @@ -129,6 +131,8 @@ function LoadingAnimation({ const [renderLogos, setRenderLogos] = useState(false); const [currentQuoteIndex, setCurrentQuoteIndex] = useState(0); + const selectedNetworkClientId = useSelector(selectSelectedNetworkClientId); + /* References */ const foxRef = useRef(); const foxHeadPan = useRef(new Animated.ValueXY(0, 0)).current; @@ -317,9 +321,11 @@ function LoadingAnimation({ return; } if (!aggregatorMetadata) { + const { SwapsController } = Engine.context; try { - const { SwapsController } = Engine.context; - await SwapsController.fetchAggregatorMetadataWithCache(); + await SwapsController.fetchAggregatorMetadataWithCache({ + networkClientId: selectedNetworkClientId, + }); } catch (error) { Logger.error( error, @@ -337,7 +343,7 @@ function LoadingAnimation({ setShouldStart(true); } })(); - }, [aggregatorMetadata, hasStarted]); + }, [aggregatorMetadata, hasStarted, selectedNetworkClientId]); /* Delay the logos rendering to avoid navigation transition lag */ useEffect(() => { diff --git a/app/components/UI/Swaps/components/LoadingAnimation/index.test.tsx b/app/components/UI/Swaps/components/LoadingAnimation/index.test.tsx new file mode 100644 index 00000000000..d0f72eb0ad1 --- /dev/null +++ b/app/components/UI/Swaps/components/LoadingAnimation/index.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import renderWithProvider, { + DeepPartial, +} from '../../../../../util/test/renderWithProvider'; +import LoadingAnimation from './'; +import { backgroundState } from '../../../../../util/test/initial-root-state'; +import { RootState } from '../../../../../reducers'; + +const mockInitialState: DeepPartial = { + engine: { + backgroundState: { + ...backgroundState, + }, + }, +}; + +describe('LoadingAnimation', () => { + it('renders', () => { + const wrapper = renderWithProvider(, { + state: mockInitialState, + }); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/app/components/UI/Swaps/index.js b/app/components/UI/Swaps/index.js index 8d189c21d8f..5808688e3eb 100644 --- a/app/components/UI/Swaps/index.js +++ b/app/components/UI/Swaps/index.js @@ -70,6 +70,7 @@ import { selectChainId, selectNetworkConfigurations, selectProviderConfig, + selectSelectedNetworkClientId, } from '../../../selectors/networkController'; import { selectConversionRate, @@ -184,6 +185,7 @@ function SwapsAmountView({ accounts, selectedAddress, chainId, + selectedNetworkClientId, providerConfig, networkConfigurations, balances, @@ -297,8 +299,12 @@ function SwapsAmountView({ (async () => { const { SwapsController } = Engine.context; try { - await SwapsController.fetchAggregatorMetadataWithCache(); - await SwapsController.fetchTopAssetsWithCache(); + await SwapsController.fetchAggregatorMetadataWithCache({ + networkClientId: selectedNetworkClientId, + }); + await SwapsController.fetchTopAssetsWithCache({ + networkClientId: selectedNetworkClientId, + }); } catch (error) { Logger.error( error, @@ -306,7 +312,7 @@ function SwapsAmountView({ ); } })(); - }, []); + }, [selectedNetworkClientId]); useEffect(() => { (async () => { @@ -320,7 +326,9 @@ function SwapsAmountView({ setInitialLoadingTokens(true); } setLoadingTokens(true); - await SwapsController.fetchTokenWithCache(); + await SwapsController.fetchTokenWithCache({ + networkClientId: selectedNetworkClientId, + }); setLoadingTokens(false); setInitialLoadingTokens(false); } catch (error) { @@ -333,7 +341,7 @@ function SwapsAmountView({ setInitialLoadingTokens(false); } })(); - }, [swapsControllerTokens, swapsTokens]); + }, [swapsControllerTokens, swapsTokens, selectedNetworkClientId]); const canSetAnInitialSourceToken = !isSourceSet && @@ -986,6 +994,10 @@ SwapsAmountView.propTypes = { * Chain Id */ chainId: PropTypes.string, + /** + * Selected network client ID + */ + selectedNetworkClientId: PropTypes.string, /** * Network configurations */ @@ -1008,6 +1020,7 @@ const mapStateToProps = (state) => ({ providerConfig: selectProviderConfig(state), networkConfigurations: selectNetworkConfigurations(state), chainId: selectChainId(state), + selectedNetworkClientId: selectSelectedNetworkClientId(state), tokensWithBalance: swapsTokensWithBalanceSelector(state), tokensTopAssets: swapsTopAssetsSelector(state), }); diff --git a/app/components/UI/Swaps/index.test.tsx b/app/components/UI/Swaps/index.test.tsx new file mode 100644 index 00000000000..d7ccfc1b9bd --- /dev/null +++ b/app/components/UI/Swaps/index.test.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import renderWithProvider, { + DeepPartial, +} from '../../../util/test/renderWithProvider'; +import SwapsAmountView from './'; +import { backgroundState } from '../../../util/test/initial-root-state'; +import { RootState } from '../../../reducers'; +import { QuoteViewSelectorIDs } from '../../../../e2e/selectors/swaps/QuoteView.selectors'; + +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useNavigation: () => ({ + setOptions: jest.fn(), + pop: jest.fn(), + navigate: jest.fn(), + }), + useRoute: () => ({}), + }; +}); + +jest.mock('../../../core/Engine', () => ({ + context: { + SwapsController: { + fetchAggregatorMetadataWithCache: jest.fn(), + fetchTopAssetsWithCache: jest.fn(), + fetchTokenWithCache: jest.fn(), + }, + }, +})); + +const mockInitialState: DeepPartial = { + engine: { + backgroundState: { + ...backgroundState, + }, + }, +}; + +describe('SwapsAmountView', () => { + it('renders', async () => { + const { getByTestId } = renderWithProvider(, { + state: mockInitialState, + }); + expect(getByTestId(QuoteViewSelectorIDs.SOURCE_TOKEN)).toBeDefined(); + }); +}); diff --git a/app/components/UI/Swaps/utils/index.js b/app/components/UI/Swaps/utils/index.js index 665562c3f55..d9d41edd236 100644 --- a/app/components/UI/Swaps/utils/index.js +++ b/app/components/UI/Swaps/utils/index.js @@ -118,6 +118,7 @@ export function getQuotesNavigationsParams(route) { * @param {object} options.destinationToken destinationToken object from tokens API * @param {string} sourceAmount Amount in minimal token units of sourceToken to be swapped * @param {string} fromAddress Current address attempting to swap + * @param {string} networkClientId Current network client ID */ export function getFetchParams({ slippage = 1, @@ -125,6 +126,7 @@ export function getFetchParams({ destinationToken, sourceAmount, walletAddress, + networkClientId, }) { return { slippage, @@ -135,6 +137,7 @@ export function getFetchParams({ metaData: { sourceTokenInfo: sourceToken, destinationTokenInfo: destinationToken, + networkClientId, }, }; } diff --git a/app/core/Engine/Engine.test.ts b/app/core/Engine/Engine.test.ts index 5af5d8965c8..a261e484232 100644 --- a/app/core/Engine/Engine.test.ts +++ b/app/core/Engine/Engine.test.ts @@ -8,7 +8,6 @@ import { mockNetworkState } from '../../util/test/network'; import MetaMetrics from '../Analytics/MetaMetrics'; import { store } from '../../store'; import { MetaMetricsEvents } from '../Analytics'; -import { NetworkState } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; import { TransactionMeta } from '@metamask/transaction-controller'; import { RootState } from '../../reducers'; @@ -132,18 +131,12 @@ describe('Engine', () => { [selectedAddress]: { balance: (ethBalance * 1e18).toString() }, }, }, - NetworkController: { - state: { - ...mockNetworkState({ - chainId: '0x1', - id: '0x1', - nickname: 'mainnet', - ticker: 'ETH', - }), - }, - // TODO(dbrans): Investigate why the shape of the NetworkController state in this - // test is {state: NetworkState} instead of just NetworkState. - } as unknown as NetworkState, + NetworkController: mockNetworkState({ + chainId: '0x1', + id: '0x1', + nickname: 'mainnet', + ticker: 'ETH', + }), CurrencyRateController: { currencyRates: { [ticker]: { diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 95c23de7ba2..a586b2c2393 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -1404,16 +1404,12 @@ export class Engine { // allowedActions: [ // 'GasFeeController:getEIP1559GasFeeEstimates', // ], - allowedActions: [ - 'NetworkController:findNetworkClientIdByChainId', - 'NetworkController:getNetworkClientById', - ], - allowedEvents: [], + allowedActions: ['NetworkController:getNetworkClientById'], + allowedEvents: ['NetworkController:networkDidChange'], }), pollCountLimit: AppConstants.SWAPS.POLL_COUNT_LIMIT, // TODO: Remove once GasFeeController exports this action type fetchGasFeeEstimates: () => gasFeeController.fetchGasFeeEstimates(), - // @ts-expect-error TODO: Resolve mismatch between gas fee and swaps controller types fetchEstimatedMultiLayerL1Fee, }), GasFeeController: gasFeeController, @@ -1675,7 +1671,7 @@ export class Engine { } configureControllersOnNetworkChange() { - const { AccountTrackerController, NetworkController, SwapsController } = + const { AccountTrackerController, NetworkController } = this.context; const { provider } = NetworkController.getProviderAndBlockTracker(); @@ -1685,10 +1681,6 @@ export class Engine { } provider.sendAsync = provider.sendAsync.bind(provider); - SwapsController.setProvider(provider, { - chainId: getGlobalChainId(NetworkController), - pollCountLimit: AppConstants.SWAPS.POLL_COUNT_LIMIT, - }); AccountTrackerController.refresh(); } diff --git a/app/reducers/swaps/index.js b/app/reducers/swaps/index.js index 04a57225652..09cfc620d24 100644 --- a/app/reducers/swaps/index.js +++ b/app/reducers/swaps/index.js @@ -2,13 +2,13 @@ import { createSelector } from 'reselect'; import { isMainnetByChainId } from '../../util/networks'; import { safeToChecksumAddress } from '../../util/address'; import { toLowerCaseEquals } from '../../util/general'; -import Engine from '../../core/Engine'; import { lte } from '../../util/lodash'; import { selectChainId } from '../../selectors/networkController'; import { selectAllTokens, selectTokens, } from '../../selectors/tokensController'; +import { selectTokenList } from '../../selectors/tokenListController'; import { selectContractBalances } from '../../selectors/tokenBalancesController'; import { getChainFeatureFlags, getSwapsLiveness } from './utils'; import { allowedTestnetChainIds } from '../../components/UI/Swaps/utils'; @@ -39,15 +39,12 @@ export const setSwapsHasOnboarded = (hasOnboarded) => ({ // * Functions -function addMetadata(chainId, tokens) { +function addMetadata(chainId, tokens, tokenList) { if (!isMainnetByChainId(chainId)) { return tokens; } return tokens.map((token) => { - const tokenMetadata = - Engine.context.TokenListController.state.tokenList[ - safeToChecksumAddress(token.address) - ]; + const tokenMetadata = tokenList[safeToChecksumAddress(token.address)]; if (tokenMetadata) { return { ...token, name: tokenMetadata.name }; } @@ -235,12 +232,13 @@ const swapsControllerAndUserTokensMultichain = createSelector( export const swapsTokensSelector = createSelector( chainIdSelector, swapsControllerAndUserTokens, - (chainId, tokens) => { + selectTokenList, + (chainId, tokens, tokenList) => { if (!tokens) { return []; } - return addMetadata(chainId, tokens); + return addMetadata(chainId, tokens, tokenList); }, ); @@ -253,13 +251,17 @@ const topAssets = (state) => */ export const swapsTokensObjectSelector = createSelector( swapsControllerAndUserTokens, - (tokens) => - tokens?.length > 0 - ? tokens.reduce( - (acc, token) => ({ ...acc, [token.address]: undefined }), - {}, - ) - : {}, + (tokens) => { + if (!tokens || tokens.length === 0) { + return {}; + } + + const result = {}; + for (const token of tokens) { + result[token.address] = undefined; + } + return result; + } ); /** @@ -288,8 +290,9 @@ export const swapsTokensMultiChainObjectSelector = createSelector( export const swapsTokensWithBalanceSelector = createSelector( chainIdSelector, swapsControllerAndUserTokens, + selectTokenList, selectContractBalances, - (chainId, tokens, balances) => { + (chainId, tokens, tokenList, balances) => { if (!tokens) { return []; } @@ -321,7 +324,7 @@ export const swapsTokensWithBalanceSelector = createSelector( 0, Math.max(tokensWithBalance.length, MAX_TOKENS_WITH_BALANCE), ); - return addMetadata(chainId, result); + return addMetadata(chainId, result, tokenList); }, ); @@ -332,8 +335,9 @@ export const swapsTokensWithBalanceSelector = createSelector( export const swapsTopAssetsSelector = createSelector( chainIdSelector, swapsControllerAndUserTokens, + selectTokenList, topAssets, - (chainId, tokens, topAssets) => { + (chainId, tokens, tokenList, topAssets) => { if (!topAssets || !tokens) { return []; } @@ -342,7 +346,7 @@ export const swapsTopAssetsSelector = createSelector( tokens?.find((token) => toLowerCaseEquals(token.address, address)), ) .filter(Boolean); - return addMetadata(chainId, result); + return addMetadata(chainId, result, tokenList); }, ); diff --git a/app/reducers/swaps/swaps.test.ts b/app/reducers/swaps/swaps.test.ts index f8bfdeae4e9..be4c7292d96 100644 --- a/app/reducers/swaps/swaps.test.ts +++ b/app/reducers/swaps/swaps.test.ts @@ -6,8 +6,13 @@ import reducer, { SWAPS_SET_LIVENESS, SWAPS_SET_HAS_ONBOARDED, swapsSmartTxFlagEnabled, + swapsTokensObjectSelector, } from './index'; import { NetworkClientType } from '@metamask/network-controller'; +// eslint-disable-next-line import/no-namespace +import * as tokensControllerSelectors from '../../selectors/tokensController'; + +jest.mock('../../selectors/tokensController'); const emptyAction = { type: null }; @@ -321,6 +326,69 @@ describe('swaps reducer', () => { }); }); + describe('swapsTokensObjectSelector', () => { + it('should return a object that returns an object combining TokensController and SwapsController tokens where each key is an address and each value is undefined', () => { + jest.spyOn(tokensControllerSelectors, 'selectTokens').mockReturnValue([ + { + address: '0x0000000000000000000000000000000000000010', + symbol: 'TOKEN1', + decimals: 1, + aggregators: [], + }, + { + address: '0x0000000000000000000000000000000000000011', + symbol: 'TOKEN2', + decimals: 2, + aggregators: [], + }, + ]); + const state = { + engine: { + backgroundState: { + SwapsController: { + tokens: [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'SWAPS-TOKEN1', + decimals: 1, + occurrences: 10, + iconUrl: 'https://some.token.icon.url/1', + }, + { + address: '0x0000000000000000000000000000000000000001', + symbol: 'SWAPS-TOKEN2', + decimals: 2, + occurrences: 20, + iconUrl: 'https://some.token.icon.url/2', + }, + ], + }, + }, + }, + }; + expect(swapsTokensObjectSelector(state)).toStrictEqual({ + '0x0000000000000000000000000000000000000000': undefined, + '0x0000000000000000000000000000000000000001': undefined, + '0x0000000000000000000000000000000000000010': undefined, + '0x0000000000000000000000000000000000000011': undefined, + }); + }); + + it('should return an empty object if there are no Swaps tokens or user tokens', () => { + jest.spyOn(tokensControllerSelectors, 'selectTokens').mockReturnValue([]); + const state = { + engine: { + backgroundState: { + SwapsController: { + tokens: [], + }, + }, + }, + }; + expect(swapsTokensObjectSelector(state)).toStrictEqual({}); + }); + }); + it('should set has onboarded', () => { const initalState = reducer(undefined, emptyAction); // @ts-ignore diff --git a/app/util/logs/__snapshots__/index.test.ts.snap b/app/util/logs/__snapshots__/index.test.ts.snap index 197b81ca95f..6ee35eccd2d 100644 --- a/app/util/logs/__snapshots__/index.test.ts.snap +++ b/app/util/logs/__snapshots__/index.test.ts.snap @@ -305,6 +305,7 @@ exports[`logs :: generateStateLogs generates a valid json export 1`] = ` "decimals": 0, "symbol": "", }, + "networkClientId": "mainnet", "sourceTokenInfo": { "address": "", "decimals": 0, diff --git a/app/util/networks/engineNetworkUtils.ts b/app/util/networks/engineNetworkUtils.ts index e093fd80e5a..65391429710 100644 --- a/app/util/networks/engineNetworkUtils.ts +++ b/app/util/networks/engineNetworkUtils.ts @@ -1,6 +1,7 @@ import Engine from '../../core/Engine'; import { convertHexToDecimal } from '@metamask/controller-utils'; -import { TransactionMeta } from '@metamask/transaction-controller'; +import { NetworkClientId } from '@metamask/network-controller'; +import { TransactionParams } from '@metamask/transaction-controller'; import { isStrictHexString } from '@metamask/utils'; /** @@ -42,14 +43,15 @@ export function toggleUseSafeChainsListValidation(value: boolean): void { */ export const fetchEstimatedMultiLayerL1Fee = async ( _: unknown, - txMeta: TransactionMeta, + { txParams, networkClientId }: { + txParams: TransactionParams, + networkClientId: NetworkClientId, + } ) => { - const chainId = txMeta.chainId; - const layer1GasFee = await Engine.context.TransactionController.getLayer1GasFee({ - transactionParams: txMeta.txParams, - chainId, + transactionParams: txParams, + networkClientId, }); const layer1GasFeeNoPrefix = layer1GasFee?.startsWith('0x') diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 0191af34f02..b55fbd59d02 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -270,7 +270,8 @@ "decimals": 0, "address": "", "symbol": "" - } + }, + "networkClientId": "mainnet" }, "topAggSavings": null, "aggregatorMetadata": null, diff --git a/package.json b/package.json index 0c94bf43d3d..fb288afa5cb 100644 --- a/package.json +++ b/package.json @@ -206,7 +206,7 @@ "@metamask/snaps-utils": "^8.6.1", "@metamask/stake-sdk": "^0.3.0", "@metamask/swappable-obj-proxy": "^2.1.0", - "@metamask/swaps-controller": "^11.0.0", + "@metamask/swaps-controller": "^12.0.0", "@metamask/transaction-controller": "^42.0.0", "@metamask/utils": "^10.0.1", "@ngraveio/bc-ur": "^1.1.6", diff --git a/yarn.lock b/yarn.lock index b2aa515ace3..3825d237ad7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5369,10 +5369,10 @@ resolved "https://registry.yarnpkg.com/@metamask/swappable-obj-proxy/-/swappable-obj-proxy-2.2.0.tgz#31b8e0ce57e28bf9847b3b24b214996f7748cc99" integrity sha512-0OjVwQtrrPFRGipw64yDUQA0CUXCK161LWCv2KlTTDZD8BKeWSNb0gbnpDI7HvhsJ0gki5gScZj1hF3ShDnBzA== -"@metamask/swaps-controller@^11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-11.0.0.tgz#7f77e4c65addb5f03bcad8c651ca6a5d74f94fd5" - integrity sha512-SdFEIiHWRZcHrkWkyhNUO5/Cr1GjOYv7RFS3D0jaCVhgCz/0yvHlgKAq5iOgS87DVHh1Iv/uFrFswwJyRin6gQ== +"@metamask/swaps-controller@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-12.0.0.tgz#60f256c10906f417225ebc7c99b587cafe6808a6" + integrity sha512-sjpN1iZnKu4BzWeIi7wUiZAa+rX+EA+SCLAmkrBcACrJQhjsKqBA4Wtrw8gI8uZrwwSfTIRFeMB6ksSiHFPzhQ== dependencies: "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0"