From 998faa39c62314aa76ff60ce94687a70ea78d02c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 14 Aug 2024 15:36:26 -0700 Subject: [PATCH 01/29] Adding Chain Permissions --- app/core/Engine.ts | 4 ++ app/core/Permissions/constants.ts | 1 + app/core/Permissions/specifications.js | 85 +++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 6ad2dec9370..751bbc26d73 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -978,6 +978,10 @@ class Engine { caveatSpecifications: getCaveatSpecifications({ getInternalAccounts: accountsController.listAccounts.bind(accountsController), + findNetworkClientIdByChainId: + networkController.findNetworkClientIdByChainId.bind( + networkController, + ), }), // @ts-expect-error Typecast permissionType from getPermissionSpecifications to be of type PermissionType.RestrictedMethod permissionSpecifications: { diff --git a/app/core/Permissions/constants.ts b/app/core/Permissions/constants.ts index 4ee3ab20c49..95f22e51597 100644 --- a/app/core/Permissions/constants.ts +++ b/app/core/Permissions/constants.ts @@ -1,5 +1,6 @@ export const CaveatTypes = Object.freeze({ restrictReturnedAccounts: 'restrictReturnedAccounts', + restrictNetworkSwitching: 'restrictNetworkSwitching', }); export const RestrictedMethods = Object.freeze({ diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index e20588dc9b7..9a844237119 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -23,6 +23,7 @@ import { CaveatTypes, RestrictedMethods } from './constants'; */ const PermissionKeys = Object.freeze({ ...RestrictedMethods, + permittedChains: 'permittedChains', }); /** @@ -34,6 +35,10 @@ const CaveatFactories = Object.freeze({ type: CaveatTypes.restrictReturnedAccounts, value: accounts, }), + [CaveatTypes.restrictNetworkSwitching]: (chainIds) => ({ + type: CaveatTypes.restrictNetworkSwitching, + value: chainIds, + }), }); /** @@ -52,9 +57,13 @@ const CaveatFactories = Object.freeze({ * * @param {{ * getInternalAccounts: () => import('@metamask/keyring-api').InternalAccount[], + * findNetworkClientIdByChainId: () => import('@metamask/network-controller').NetworkClient, * }} options - Options bag. */ -export const getCaveatSpecifications = ({ getInternalAccounts }) => ({ +export const getCaveatSpecifications = ({ + getInternalAccounts, + findNetworkClientIdByChainId, +}) => ({ [CaveatTypes.restrictReturnedAccounts]: { type: CaveatTypes.restrictReturnedAccounts, @@ -74,6 +83,16 @@ export const getCaveatSpecifications = ({ getInternalAccounts }) => ({ validator: (caveat, _origin, _target) => validateCaveatAccounts(caveat.value, getInternalAccounts), }, + [CaveatTypes.restrictNetworkSwitching]: { + type: CaveatTypes.restrictNetworkSwitching, + validator: (caveat, _origin, _target) => + validateCaveatNetworks(caveat.value, findNetworkClientIdByChainId), + merger: (leftValue, rightValue) => { + const newValue = Array.from(new Set([...leftValue, ...rightValue])); + const diff = newValue.filter((value) => !leftValue.includes(value)); + return [newValue, diff]; + }, + }, ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) ...snapsCaveatsSpecifications, ...snapsEndowmentCaveatSpecifications, @@ -187,6 +206,40 @@ export const getPermissionSpecifications = ({ } }, }, + [PermissionKeys.permittedChains]: { + permissionType: PermissionType.Endowment, + targetName: PermissionKeys.permittedChains, + allowedCaveats: [CaveatTypes.restrictNetworkSwitching], + factory: (permissionOptions, requestData) => { + if (!requestData.approvedChainIds) { + throw new Error( + `${PermissionKeys.permittedChains}: No approved networks specified.`, + ); + } + + return constructPermission({ + ...permissionOptions, + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]( + requestData.approvedChainIds, + ), + ], + }); + }, + endowmentGetter: async (_getterOptions) => undefined, + validator: (permission, _origin, _target) => { + const { caveats } = permission; + if ( + !caveats || + caveats.length !== 1 || + caveats[0].type !== CaveatTypes.restrictNetworkSwitching + ) { + throw new Error( + `${PermissionKeys.permittedChains} error: Invalid caveats. There must be a single caveat of type "${CaveatTypes.restrictNetworkSwitching}".`, + ); + } + }, + }, }); /** @@ -226,6 +279,36 @@ function validateCaveatAccounts(accounts, getInternalAccounts) { }); } +/** + * Validates the networks associated with a caveat. Ensures that + * the networks value is an array of valid chain IDs. + * + * @param {string[]} chainIdsForCaveat - The list of chain IDs to validate. + * @param {function(string): string} findNetworkClientIdByChainId - Function to find network client ID by chain ID. + * @throws {Error} If the chainIdsForCaveat is not a non-empty array of valid chain IDs. + */ +function validateCaveatNetworks( + chainIdsForCaveat, + findNetworkClientIdByChainId, +) { + if (!Array.isArray(chainIdsForCaveat) || chainIdsForCaveat.length === 0) { + throw new Error( + `${PermissionKeys.permittedChains} error: Expected non-empty array of chainIds.`, + ); + } + + chainIdsForCaveat.forEach((chainId) => { + try { + findNetworkClientIdByChainId(chainId); + } catch (e) { + console.error(e); + throw new Error( + `${PermissionKeys.permittedChains} error: Received unrecognized chainId: "${chainId}". Please try adding the network first via wallet_addEthereumChain.`, + ); + } + }); +} + /** * All unrestricted methods recognized by the PermissionController. * Unrestricted methods are ignored by the permission system, but every From 1f2776a9bac9fec815cec9a456895ec241ff1507 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 15:27:35 -0500 Subject: [PATCH 02/29] gluing --- app/core/Permissions/specifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index 9a844237119..fd353add546 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -57,7 +57,7 @@ const CaveatFactories = Object.freeze({ * * @param {{ * getInternalAccounts: () => import('@metamask/keyring-api').InternalAccount[], - * findNetworkClientIdByChainId: () => import('@metamask/network-controller').NetworkClient, + * findNetworkClientIdByChainId: (chainId: `0x${string}`) => string, * }} options - Options bag. */ export const getCaveatSpecifications = ({ From 295cae8be0ce2de878398c0e272c1afc26ef252a Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Sep 2024 16:23:30 -0500 Subject: [PATCH 03/29] refactor switchEthereumChain + addEthereumChain handlers --- .../RPCMethods/lib/ethereum-chain-utils.js | 244 ++++++++++++++ .../RPCMethods/wallet_addEthereumChain.js | 306 ++++++------------ .../RPCMethods/wallet_switchEthereumChain.js | 110 ++----- 3 files changed, 357 insertions(+), 303 deletions(-) create mode 100644 app/core/RPCMethods/lib/ethereum-chain-utils.js diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js new file mode 100644 index 00000000000..72e77c07798 --- /dev/null +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -0,0 +1,244 @@ +import { rpcErrors } from '@metamask/rpc-errors'; +import validUrl from 'valid-url'; +import { isSafeChainId } from '@metamask/controller-utils'; +import { jsonRpcRequest } from '../../../util/jsonRpcRequest'; +import { + getDecimalChainId, + isPrefixedFormattedHexString, + getDefaultNetworkByChainId, +} from '../../../util/networks'; + +const EVM_NATIVE_TOKEN_DECIMALS = 18; + +export function validateChainId(chainId) { + const _chainId = typeof chainId === 'string' && chainId.toLowerCase(); + + if (!isPrefixedFormattedHexString(_chainId)) { + throw rpcErrors.invalidParams( + `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\n${chainId}`, + ); + } + + if (!isSafeChainId(_chainId)) { + throw rpcErrors.invalidParams( + `Invalid chain ID "${_chainId}": numerical value greater than max safe value. Received:\n${chainId}`, + ); + } + + return _chainId; +} + +export function validateAddEthereumChainParams(params) { + if (!params || typeof params !== 'object') { + throw rpcErrors.invalidParams({ + message: `Expected single, object parameter. Received:\n${JSON.stringify( + params, + )}`, + }); + } + + const { + chainId, + chainName: rawChainName = null, + blockExplorerUrls = null, + nativeCurrency = null, + rpcUrls, + } = params; + + const allowedKeys = { + chainId: true, + chainName: true, + blockExplorerUrls: true, + nativeCurrency: true, + rpcUrls: true, + iconUrls: true, + }; + + const extraKeys = Object.keys(params).filter((key) => !allowedKeys[key]); + if (extraKeys.length) { + throw rpcErrors.invalidParams( + `Received unexpected keys on object parameter. Unsupported keys:\n${extraKeys}`, + ); + } + + const _chainId = validateChainId(chainId); + const firstValidRPCUrl = validateRpcUrls(rpcUrls); + const firstValidBlockExplorerUrl = + validateBlockExplorerUrls(blockExplorerUrls); + const chainName = validateChainName(rawChainName); + const ticker = validateNativeCurrency(nativeCurrency); + + return { + chainId: _chainId, + chainName, + firstValidRPCUrl, + firstValidBlockExplorerUrl, + ticker, + }; +} + +function validateRpcUrls(rpcUrls) { + const dirtyFirstValidRPCUrl = Array.isArray(rpcUrls) + ? rpcUrls.find((rpcUrl) => validUrl.isHttpsUri(rpcUrl)) + : null; + + const firstValidRPCUrl = dirtyFirstValidRPCUrl + ? dirtyFirstValidRPCUrl.replace(/([^/])\/+$/g, '$1') + : dirtyFirstValidRPCUrl; + + if (!firstValidRPCUrl) { + throw rpcErrors.invalidParams( + `Expected an array with at least one valid string HTTPS url 'rpcUrls', Received:\n${rpcUrls}`, + ); + } + + return firstValidRPCUrl; +} + +function validateBlockExplorerUrls(blockExplorerUrls) { + const firstValidBlockExplorerUrl = + blockExplorerUrls !== null && Array.isArray(blockExplorerUrls) + ? blockExplorerUrls.find((blockExplorerUrl) => + validUrl.isHttpsUri(blockExplorerUrl), + ) + : null; + + if (blockExplorerUrls !== null && !firstValidBlockExplorerUrl) { + throw rpcErrors.invalidParams( + `Expected null or array with at least one valid string HTTPS URL 'blockExplorerUrl'. Received: ${blockExplorerUrls}`, + ); + } + + return firstValidBlockExplorerUrl; +} + +function validateChainName(rawChainName) { + if (typeof rawChainName !== 'string' || !rawChainName) { + throw rpcErrors.invalidParams({ + message: `Expected non-empty string 'chainName'. Received:\n${rawChainName}`, + }); + } + return rawChainName.length > 100 + ? rawChainName.substring(0, 100) + : rawChainName; +} + +function validateNativeCurrency(nativeCurrency) { + if (nativeCurrency !== null) { + if (typeof nativeCurrency !== 'object' || Array.isArray(nativeCurrency)) { + throw rpcErrors.invalidParams({ + message: `Expected null or object 'nativeCurrency'. Received:\n${nativeCurrency}`, + }); + } + if (nativeCurrency.decimals !== EVM_NATIVE_TOKEN_DECIMALS) { + throw rpcErrors.invalidParams({ + message: `Expected the number 18 for 'nativeCurrency.decimals' when 'nativeCurrency' is provided. Received: ${nativeCurrency.decimals}`, + }); + } + + if (!nativeCurrency.symbol || typeof nativeCurrency.symbol !== 'string') { + throw rpcErrors.invalidParams({ + message: `Expected a string 'nativeCurrency.symbol'. Received: ${nativeCurrency.symbol}`, + }); + } + } + const ticker = nativeCurrency?.symbol || 'ETH'; + + if (typeof ticker !== 'string' || ticker.length < 2 || ticker.length > 6) { + throw rpcErrors.invalidParams({ + message: `Expected 2-6 character string 'nativeCurrency.symbol'. Received:\n${ticker}`, + }); + } + + return ticker; +} + +export async function validateRpcEndpoint(rpcUrl, chainId) { + try { + const endpointChainId = await jsonRpcRequest(rpcUrl, 'eth_chainId'); + if (chainId !== endpointChainId) { + throw rpcErrors.invalidParams({ + message: `Chain ID returned by RPC URL ${rpcUrl} does not match ${chainId}`, + data: { chainId: endpointChainId }, + }); + } + } catch (err) { + throw rpcErrors.internal({ + message: `Request for method 'eth_chainId on ${rpcUrl} failed`, + data: { networkErr: err }, + }); + } +} + +export function findExistingNetwork(chainId, networkConfigurations) { + const existingNetworkDefault = getDefaultNetworkByChainId(chainId); + const existingEntry = Object.entries(networkConfigurations).find( + ([, networkConfiguration]) => networkConfiguration.chainId === chainId, + ); + + return existingEntry || existingNetworkDefault; +} + +export async function switchToNetwork({ + network, + chainId, + controllers, + requestUserApproval, + analytics, + origin, + isAddNetworkFlow = false, +}) { + const { + CurrencyRateController, + NetworkController, + PermissionController, + SelectedNetworkController, + } = controllers; + + let networkConfigurationId, networkConfiguration; + if (Array.isArray(network)) { + [networkConfigurationId, networkConfiguration] = network; + } else { + networkConfiguration = network; + } + + const requestData = { + rpcUrl: networkConfiguration.rpcUrl, + chainId, + chainName: networkConfiguration.nickname || networkConfiguration.shortName, + ticker: networkConfiguration.ticker || 'ETH', + }; + + const analyticsParams = { + chain_id: getDecimalChainId(chainId), + source: 'Custom Network API', + symbol: networkConfiguration?.ticker || 'ETH', + ...analytics, + }; + + const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; + + await requestUserApproval({ + type: 'SWITCH_ETHEREUM_CHAIN', + requestData: { ...requestData, type: requestModalType }, + }); + + const originHasAccountsPermission = PermissionController.hasPermission( + origin, + 'eth_accounts', + ); + + if (process.env.MULTICHAIN_V1 && originHasAccountsPermission) { + SelectedNetworkController.setNetworkClientIdForDomain( + origin, + networkConfigurationId || networkConfiguration.networkType, + ); + } else { + CurrencyRateController.updateExchangeRate(requestData.ticker); + NetworkController.setActiveNetwork( + networkConfigurationId || networkConfiguration.networkType, + ); + } + + return analyticsParams; +} diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index bd9568a2326..851ed5f1dd3 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -1,22 +1,19 @@ import { InteractionManager } from 'react-native'; -import validUrl from 'valid-url'; -import { ChainId, isSafeChainId } from '@metamask/controller-utils'; -import { jsonRpcRequest } from '../../util/jsonRpcRequest'; +import { ChainId } from '@metamask/controller-utils'; import Engine from '../Engine'; import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; -import { - getDecimalChainId, - isPrefixedFormattedHexString, -} from '../../util/networks'; import { MetaMetricsEvents, MetaMetrics } from '../../core/Analytics'; -import { - selectChainId, - selectNetworkConfigurations, -} from '../../selectors/networkController'; +import { selectNetworkConfigurations } from '../../selectors/networkController'; import { store } from '../../store'; import checkSafeNetwork from './networkChecker.util'; - -const EVM_NATIVE_TOKEN_DECIMALS = 18; +import { RestrictedMethods } from '../Permissions/constants'; +import { + validateAddEthereumChainParams, + validateRpcEndpoint, + findExistingNetwork, + switchToNetwork, +} from './lib/ethereum-chain-utils'; +import { getDecimalChainId } from '../../util/networks'; const waitForInteraction = async () => new Promise((resolve) => { @@ -33,132 +30,48 @@ const wallet_addEthereumChain = async ({ startApprovalFlow, endApprovalFlow, }) => { - const { CurrencyRateController, NetworkController, ApprovalController } = - Engine.context; - - if (!req.params?.[0] || typeof req.params[0] !== 'object') { - throw rpcErrors.invalidParams({ - message: `Expected single, object parameter. Received:\n${JSON.stringify( - req.params, - )}`, - }); - } + const { + CurrencyRateController, + NetworkController, + ApprovalController, + PermissionController, + SelectedNetworkController, + } = Engine.context; - const params = req.params[0]; + const { origin } = req; + const params = validateAddEthereumChainParams(req.params[0]); const { chainId, - chainName: rawChainName = null, - blockExplorerUrls = null, - nativeCurrency = null, - rpcUrls, + chainName, + firstValidRPCUrl, + firstValidBlockExplorerUrl, + ticker, } = params; - const allowedKeys = { - chainId: true, - chainName: true, - blockExplorerUrls: true, - nativeCurrency: true, - rpcUrls: true, - iconUrls: true, - }; - - const extraKeys = Object.keys(params).filter((key) => !allowedKeys[key]); - if (extraKeys.length) { - throw rpcErrors.invalidParams( - `Received unexpected keys on object parameter. Unsupported keys:\n${extraKeys}`, - ); - } - - const dirtyFirstValidRPCUrl = Array.isArray(rpcUrls) - ? rpcUrls.find((rpcUrl) => validUrl.isHttpsUri(rpcUrl)) - : null; - // Remove trailing slashes - const firstValidRPCUrl = dirtyFirstValidRPCUrl - ? // https://github.com/MetaMask/mobile-planning/issues/1589 - dirtyFirstValidRPCUrl.replace(/([^/])\/+$/g, '$1') - : dirtyFirstValidRPCUrl; - - const firstValidBlockExplorerUrl = - blockExplorerUrls !== null && Array.isArray(blockExplorerUrls) - ? blockExplorerUrls.find((blockExplorerUrl) => - validUrl.isHttpsUri(blockExplorerUrl), - ) - : null; - - if (!firstValidRPCUrl) { - throw rpcErrors.invalidParams( - `Expected an array with at least one valid string HTTPS url 'rpcUrls', Received:\n${rpcUrls}`, - ); - } - - if (blockExplorerUrls !== null && !firstValidBlockExplorerUrl) { - throw rpcErrors.invalidParams( - `Expected null or array with at least one valid string HTTPS URL 'blockExplorerUrl'. Received: ${blockExplorerUrls}`, - ); - } - - const _chainId = typeof chainId === 'string' && chainId.toLowerCase(); - - if (!isPrefixedFormattedHexString(_chainId)) { - throw rpcErrors.invalidParams( - `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\n${chainId}`, - ); - } - - if (!isSafeChainId(_chainId)) { - throw rpcErrors.invalidParams( - `Invalid chain ID "${_chainId}": numerical value greater than max safe value. Received:\n${chainId}`, - ); - } - //TODO: Remove aurora from default chains in @metamask/controller-utils const actualChains = { ...ChainId, aurora: undefined }; - if (Object.values(actualChains).find((value) => value === _chainId)) { + if (Object.values(actualChains).find((value) => value === chainId)) { throw rpcErrors.invalidParams(`May not specify default MetaMask chain.`); } const networkConfigurations = selectNetworkConfigurations(store.getState()); - const existingEntry = Object.entries(networkConfigurations).find( - ([, networkConfiguration]) => networkConfiguration.chainId === _chainId, - ); - - if (existingEntry) { - const [networkConfigurationId, networkConfiguration] = existingEntry; - const currentChainId = selectChainId(store.getState()); - if (currentChainId === _chainId) { - res.result = null; - return; - } - - const analyticsParams = { - chain_id: getDecimalChainId(_chainId), - source: 'Custom Network API', - symbol: networkConfiguration.ticker, - ...analytics, - }; - - try { - await requestUserApproval({ - type: 'SWITCH_ETHEREUM_CHAIN', - requestData: { - rpcUrl: networkConfiguration.rpcUrl, - chainId: _chainId, - chainName: networkConfiguration.nickname, - ticker: networkConfiguration.ticker, - type: 'switch', - }, - }); - } catch (e) { - MetaMetrics.getInstance().trackEvent( - MetaMetricsEvents.NETWORK_REQUEST_REJECTED, - analyticsParams, - ); - throw providerErrors.userRejectedRequest(); - } - - CurrencyRateController.updateExchangeRate(networkConfiguration.ticker); - NetworkController.setActiveNetwork(networkConfigurationId); + const existingNetwork = findExistingNetwork(chainId, networkConfigurations); + + if (existingNetwork) { + const analyticsParams = await switchToNetwork( + existingNetwork, + chainId, + { + CurrencyRateController, + NetworkController, + PermissionController, + SelectedNetworkController, + }, + requestUserApproval, + analytics, + origin, + ); MetaMetrics.getInstance().trackEvent( MetaMetricsEvents.NETWORK_SWITCHED, @@ -169,60 +82,10 @@ const wallet_addEthereumChain = async ({ return; } - let endpointChainId; - - try { - endpointChainId = await jsonRpcRequest(firstValidRPCUrl, 'eth_chainId'); - } catch (err) { - throw rpcErrors.internal({ - message: `Request for method 'eth_chainId on ${firstValidRPCUrl} failed`, - data: { networkErr: err }, - }); - } - - if (_chainId !== endpointChainId) { - throw rpcErrors.invalidParams({ - message: `Chain ID returned by RPC URL ${firstValidRPCUrl} does not match ${_chainId}`, - data: { chainId: endpointChainId }, - }); - } - - if (typeof rawChainName !== 'string' || !rawChainName) { - throw rpcErrors.invalidParams({ - message: `Expected non-empty string 'chainName'. Received:\n${rawChainName}`, - }); - } - const chainName = - rawChainName.length > 100 ? rawChainName.substring(0, 100) : rawChainName; - - if (nativeCurrency !== null) { - if (typeof nativeCurrency !== 'object' || Array.isArray(nativeCurrency)) { - throw rpcErrors.invalidParams({ - message: `Expected null or object 'nativeCurrency'. Received:\n${nativeCurrency}`, - }); - } - if (nativeCurrency.decimals !== EVM_NATIVE_TOKEN_DECIMALS) { - throw rpcErrors.invalidParams({ - message: `Expected the number 18 for 'nativeCurrency.decimals' when 'nativeCurrency' is provided. Received: ${nativeCurrency.decimals}`, - }); - } - - if (!nativeCurrency.symbol || typeof nativeCurrency.symbol !== 'string') { - throw rpcErrors.invalidParams({ - message: `Expected a string 'nativeCurrency.symbol'. Received: ${nativeCurrency.symbol}`, - }); - } - } - const ticker = nativeCurrency?.symbol || 'ETH'; - - if (typeof ticker !== 'string' || ticker.length < 2 || ticker.length > 6) { - throw rpcErrors.invalidParams({ - message: `Expected 2-6 character string 'nativeCurrency.symbol'. Received:\n${ticker}`, - }); - } + await validateRpcEndpoint(firstValidRPCUrl, chainId); const requestData = { - chainId: _chainId, + chainId, blockExplorerUrl: firstValidBlockExplorerUrl, chainName, rpcUrl: firstValidRPCUrl, @@ -230,7 +93,7 @@ const wallet_addEthereumChain = async ({ }; const alerts = await checkSafeNetwork( - getDecimalChainId(_chainId), + getDecimalChainId(chainId), requestData.rpcUrl, requestData.chainName, requestData.ticker, @@ -238,17 +101,12 @@ const wallet_addEthereumChain = async ({ requestData.alerts = alerts; - const analyticsParamsAdd = { - chain_id: getDecimalChainId(_chainId), + MetaMetrics.getInstance().trackEvent(MetaMetricsEvents.NETWORK_REQUESTED, { + chain_id: getDecimalChainId(chainId), source: 'Custom Network API', symbol: ticker, ...analytics, - }; - - MetaMetrics.getInstance().trackEvent( - MetaMetricsEvents.NETWORK_REQUESTED, - analyticsParamsAdd, - ); + }); // Remove all existing approvals, including other add network requests. ApprovalController.clear(providerErrors.userRejectedRequest()); @@ -260,23 +118,16 @@ const wallet_addEthereumChain = async ({ const { id: approvalFlowId } = startApprovalFlow(); try { - try { - await requestUserApproval({ - type: 'ADD_ETHEREUM_CHAIN', - requestData, - }); - } catch (e) { - MetaMetrics.getInstance().trackEvent( - MetaMetricsEvents.NETWORK_REQUEST_REJECTED, - analyticsParamsAdd, - ); - throw providerErrors.userRejectedRequest(); - } + await requestUserApproval({ + type: 'ADD_ETHEREUM_CHAIN', + requestData, + }); + const networkConfigurationId = await NetworkController.upsertNetworkConfiguration( { rpcUrl: firstValidRPCUrl, - chainId: _chainId, + chainId, ticker, nickname: chainName, rpcPrefs: { @@ -284,27 +135,50 @@ const wallet_addEthereumChain = async ({ }, }, { - // Metrics-related properties required, but the metric event is a no-op - // TODO: Use events for controller metric events - referrer: 'ignored', - source: 'ignored', + referrer: 'Custom Network API', + source: 'Custom Network API', }, ); - MetaMetrics.getInstance().trackEvent( - MetaMetricsEvents.NETWORK_ADDED, - analyticsParamsAdd, - ); - - await waitForInteraction(); + MetaMetrics.getInstance().trackEvent(MetaMetricsEvents.NETWORK_ADDED, { + chain_id: getDecimalChainId(chainId), + source: 'Custom Network API', + symbol: ticker, + ...analytics, + }); - await requestUserApproval({ - type: 'SWITCH_ETHEREUM_CHAIN', - requestData: { ...requestData, type: 'new' }, + const network = [networkConfigurationId, requestData]; + + const analyticsParams = await switchToNetwork({ + network, + chainId, + controllers: { + CurrencyRateController, + NetworkController, + PermissionController, + SelectedNetworkController, + }, + requestUserApproval, + analytics, + origin, + isAddNetworkFlow: true, // isAddNetworkFlow }); - CurrencyRateController.updateExchangeRate(ticker); - NetworkController.setActiveNetwork(networkConfigurationId); + MetaMetrics.getInstance().trackEvent( + MetaMetricsEvents.NETWORK_SWITCHED, + analyticsParams, + ); + } catch (error) { + MetaMetrics.getInstance().trackEvent( + MetaMetricsEvents.NETWORK_REQUEST_REJECTED, + { + chain_id: getDecimalChainId(chainId), + source: 'Custom Network API', + symbol: ticker, + ...analytics, + }, + ); + throw error; } finally { endApprovalFlow({ id: approvalFlowId }); } diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.js b/app/core/RPCMethods/wallet_switchEthereumChain.js index c37289085f6..b8a947ce18b 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.js @@ -1,15 +1,13 @@ import Engine from '../Engine'; import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; -import { - getDecimalChainId, - getDefaultNetworkByChainId, - isPrefixedFormattedHexString, -} from '../../util/networks'; import { MetaMetricsEvents, MetaMetrics } from '../../core/Analytics'; import { selectNetworkConfigurations } from '../../selectors/networkController'; import { store } from '../../store'; -import { NetworksTicker, isSafeChainId } from '@metamask/controller-utils'; -import { RestrictedMethods } from '../Permissions/constants'; +import { + validateChainId, + findExistingNetwork, + switchToNetwork, +} from './lib/ethereum-chain-utils'; const wallet_switchEthereumChain = async ({ req, @@ -47,36 +45,18 @@ const wallet_switchEthereumChain = async ({ ); } - const _chainId = typeof chainId === 'string' && chainId.toLowerCase(); - - if (!isPrefixedFormattedHexString(_chainId)) { - throw rpcErrors.invalidParams( - `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\n${chainId}`, - ); - } - - if (!isSafeChainId(_chainId)) { - throw rpcErrors.invalidParams( - `Invalid chain ID "${_chainId}": numerical value greater than max safe value. Received:\n${chainId}`, - ); - } + const _chainId = validateChainId(chainId); const networkConfigurations = selectNetworkConfigurations(store.getState()); + const existingNetwork = findExistingNetwork(_chainId, networkConfigurations); - const existingNetworkDefault = getDefaultNetworkByChainId(_chainId); - const existingEntry = Object.entries(networkConfigurations).find( - ([, networkConfiguration]) => networkConfiguration.chainId === _chainId, - ); - - if (existingEntry || existingNetworkDefault) { + if (existingNetwork) { const currentDomainSelectedNetworkClientId = - Engine.context.SelectedNetworkController.getNetworkClientIdForDomain( - origin, - ); + SelectedNetworkController.getNetworkClientIdForDomain(origin); const { configuration: { chainId: currentDomainSelectedChainId }, - } = Engine.context.NetworkController.getNetworkClientById( + } = NetworkController.getNetworkClientById( currentDomainSelectedNetworkClientId, ) || { configuration: {} }; @@ -85,64 +65,20 @@ const wallet_switchEthereumChain = async ({ return; } - let networkConfigurationId, networkConfiguration; - if (existingEntry) { - [networkConfigurationId, networkConfiguration] = existingEntry; - } - - let requestData; - let analyticsParams = { - chain_id: getDecimalChainId(_chainId), - source: 'Switch Network API', - ...analytics, - }; - if (networkConfiguration) { - requestData = { - rpcUrl: networkConfiguration.rpcUrl, - chainId: _chainId, - chainName: networkConfiguration.nickname, - ticker: networkConfiguration.ticker, - }; - analyticsParams = { - ...analyticsParams, - symbol: networkConfiguration?.ticker, - }; - } else { - requestData = { - chainId: _chainId, - chainColor: existingNetworkDefault.color, - chainName: existingNetworkDefault.shortName, - ticker: 'ETH', - }; - analyticsParams = { - ...analyticsParams, - }; - } - - await requestUserApproval({ - type: 'SWITCH_ETHEREUM_CHAIN', - requestData: { ...requestData, type: 'switch' }, - }); - - const originHasAccountsPermission = PermissionController.hasPermission( + const analyticsParams = await switchToNetwork({ + network: existingNetwork, + chainId: _chainId, + controllers: { + CurrencyRateController, + NetworkController, + PermissionController, + SelectedNetworkController, + }, + requestUserApproval, + analytics, origin, - RestrictedMethods.eth_accounts, - ); - - if (process.env.MULTICHAIN_V1 && originHasAccountsPermission) { - SelectedNetworkController.setNetworkClientIdForDomain( - origin, - networkConfigurationId || existingNetworkDefault.networkType, - ); - } else if (networkConfiguration) { - CurrencyRateController.updateExchangeRate(networkConfiguration.ticker); - NetworkController.setActiveNetwork(networkConfigurationId); - } else { - // TODO we will need to update this so that each network in the NetworksList has its own ticker - // if we ever add networks that don't have ETH as their base currency - CurrencyRateController.updateExchangeRate(NetworksTicker.mainnet); - NetworkController.setActiveNetwork(existingNetworkDefault.networkType); - } + isAddNetworkFlow: false, + }); MetaMetrics.getInstance().trackEvent( MetaMetricsEvents.NETWORK_SWITCHED, From 8e30da4c3d14b72713fbd24f809fa337c8f878a5 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Sep 2024 16:52:07 -0500 Subject: [PATCH 04/29] fix --- app/core/RPCMethods/lib/ethereum-chain-utils.js | 12 +++++------- app/core/RPCMethods/wallet_addEthereumChain.js | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 72e77c07798..27cc63252ae 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -195,17 +195,15 @@ export async function switchToNetwork({ SelectedNetworkController, } = controllers; - let networkConfigurationId, networkConfiguration; - if (Array.isArray(network)) { - [networkConfigurationId, networkConfiguration] = network; - } else { - networkConfiguration = network; - } + const [networkConfigurationId, networkConfiguration] = network; const requestData = { rpcUrl: networkConfiguration.rpcUrl, chainId, - chainName: networkConfiguration.nickname || networkConfiguration.shortName, + chainName: + networkConfiguration.chainName || + networkConfiguration.nickname || + networkConfiguration.shortName, ticker: networkConfiguration.ticker || 'ETH', }; diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index 851ed5f1dd3..c3de4a2ac6b 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -59,10 +59,10 @@ const wallet_addEthereumChain = async ({ const existingNetwork = findExistingNetwork(chainId, networkConfigurations); if (existingNetwork) { - const analyticsParams = await switchToNetwork( - existingNetwork, + const analyticsParams = await switchToNetwork({ + network: existingNetwork, chainId, - { + controllers: { CurrencyRateController, NetworkController, PermissionController, @@ -71,7 +71,7 @@ const wallet_addEthereumChain = async ({ requestUserApproval, analytics, origin, - ); + }); MetaMetrics.getInstance().trackEvent( MetaMetricsEvents.NETWORK_SWITCHED, From a1c738a636c4a45510aa1c65040e73b0ad2e6b5b Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Sep 2024 17:33:19 -0500 Subject: [PATCH 05/29] adding permission logic --- app/core/Permissions/specifications.js | 4 +- .../RPCMethods/lib/ethereum-chain-utils.js | 71 +++++++++++++++++-- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index fd353add546..b1b6a485b3e 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -21,7 +21,7 @@ import { CaveatTypes, RestrictedMethods } from './constants'; * The "keys" of all of permissions recognized by the PermissionController. * Permission keys and names have distinct meanings in the permission system. */ -const PermissionKeys = Object.freeze({ +export const PermissionKeys = Object.freeze({ ...RestrictedMethods, permittedChains: 'permittedChains', }); @@ -30,7 +30,7 @@ const PermissionKeys = Object.freeze({ * Factory functions for all caveat types recognized by the * PermissionController. */ -const CaveatFactories = Object.freeze({ +export const CaveatFactories = Object.freeze({ [CaveatTypes.restrictReturnedAccounts]: (accounts) => ({ type: CaveatTypes.restrictReturnedAccounts, value: accounts, diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 27cc63252ae..38963ad8eae 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -7,6 +7,12 @@ import { isPrefixedFormattedHexString, getDefaultNetworkByChainId, } from '../../../util/networks'; +import { + CaveatFactories, + PermissionKeys, +} from '../../../core/Permissions/specifications'; +import { CaveatTypes } from '../../../core/Permissions/constants'; +import { PermissionDoesNotExistError } from '@metamask/permission-controller'; const EVM_NATIVE_TOKEN_DECIMALS = 18; @@ -195,6 +201,21 @@ export async function switchToNetwork({ SelectedNetworkController, } = controllers; + const getCaveat = ({ target, caveatType }) => { + try { + return PermissionController.getCaveat(origin, target, caveatType); + } catch (e) { + if (e instanceof PermissionDoesNotExistError) { + // suppress expected error in case that the origin + // does not have the target permission yet + } else { + throw e; + } + } + + return undefined; + }; + const [networkConfigurationId, networkConfiguration] = network; const requestData = { @@ -214,12 +235,52 @@ export async function switchToNetwork({ ...analytics, }; - const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; + if (process.env.MULTICHAIN_V1) { + const { value: permissionedChainIds } = + getCaveat({ + target: PermissionKeys.permittedChains, + caveatType: CaveatTypes.restrictNetworkSwitching, + }) ?? {}; + if ( + permissionedChainIds === undefined || + !permissionedChainIds.includes(chainId) + ) { + if (isAddNetworkFlow) { + await PermissionController.grantPermissionsIncremental({ + subject: { origin }, + approvedPermissions: { + [PermissionKeys.permittedChains]: { + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ + chainId, + ]), + ], + }, + }, + }); + } else { + await PermissionController.requestPermissionsIncremental({ + subject: { origin }, + requestedPermissions: { + [PermissionKeys.permittedChains]: { + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ + chainId, + ]), + ], + }, + }, + }); + } + } + } else { + const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; - await requestUserApproval({ - type: 'SWITCH_ETHEREUM_CHAIN', - requestData: { ...requestData, type: requestModalType }, - }); + await requestUserApproval({ + type: 'SWITCH_ETHEREUM_CHAIN', + requestData: { ...requestData, type: requestModalType }, + }); + } const originHasAccountsPermission = PermissionController.hasPermission( origin, From 399938feffa045d9b03bf251abbf37e1e428a385 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Oct 2024 10:08:33 -0500 Subject: [PATCH 06/29] unit tests passing --- .../RPCMethods/lib/ethereum-chain-utils.js | 41 +++++++++++-------- .../RPCMethods/wallet_addEthereumChain.js | 40 ++++++++---------- .../wallet_addEthereumChain.test.js | 3 ++ 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 38963ad8eae..6ce75769977 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -35,7 +35,7 @@ export function validateChainId(chainId) { } export function validateAddEthereumChainParams(params) { - if (!params || typeof params !== 'object') { + if (!params || !params?.[0] || typeof params[0] !== 'object') { throw rpcErrors.invalidParams({ message: `Expected single, object parameter. Received:\n${JSON.stringify( params, @@ -43,13 +43,15 @@ export function validateAddEthereumChainParams(params) { }); } - const { - chainId, - chainName: rawChainName = null, - blockExplorerUrls = null, - nativeCurrency = null, - rpcUrls, - } = params; + const [ + { + chainId, + chainName: rawChainName = null, + blockExplorerUrls = null, + nativeCurrency = null, + rpcUrls, + }, + ] = params; const allowedKeys = { chainId: true, @@ -60,18 +62,21 @@ export function validateAddEthereumChainParams(params) { iconUrls: true, }; - const extraKeys = Object.keys(params).filter((key) => !allowedKeys[key]); + const extraKeys = Object.keys(params[0]).filter((key) => !allowedKeys[key]); if (extraKeys.length) { throw rpcErrors.invalidParams( `Received unexpected keys on object parameter. Unsupported keys:\n${extraKeys}`, ); } - const _chainId = validateChainId(chainId); + const firstValidRPCUrl = validateRpcUrls(rpcUrls); + const firstValidBlockExplorerUrl = validateBlockExplorerUrls(blockExplorerUrls); + const chainName = validateChainName(rawChainName); + const ticker = validateNativeCurrency(nativeCurrency); return { @@ -160,20 +165,21 @@ function validateNativeCurrency(nativeCurrency) { } export async function validateRpcEndpoint(rpcUrl, chainId) { + let endpointChainId; try { - const endpointChainId = await jsonRpcRequest(rpcUrl, 'eth_chainId'); - if (chainId !== endpointChainId) { - throw rpcErrors.invalidParams({ - message: `Chain ID returned by RPC URL ${rpcUrl} does not match ${chainId}`, - data: { chainId: endpointChainId }, - }); - } + endpointChainId = await jsonRpcRequest(rpcUrl, 'eth_chainId'); } catch (err) { throw rpcErrors.internal({ message: `Request for method 'eth_chainId on ${rpcUrl} failed`, data: { networkErr: err }, }); } + if (chainId !== endpointChainId) { + throw rpcErrors.invalidParams({ + message: `Chain ID returned by RPC URL ${rpcUrl} does not match ${chainId}`, + data: { chainId: endpointChainId }, + }); + } } export function findExistingNetwork(chainId, networkConfigurations) { @@ -275,7 +281,6 @@ export async function switchToNetwork({ } } else { const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; - await requestUserApproval({ type: 'SWITCH_ETHEREUM_CHAIN', requestData: { ...requestData, type: requestModalType }, diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index c3de4a2ac6b..8927d2938a6 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -39,8 +39,8 @@ const wallet_addEthereumChain = async ({ } = Engine.context; const { origin } = req; - const params = validateAddEthereumChainParams(req.params[0]); + const params = validateAddEthereumChainParams(req.params); const { chainId, chainName, @@ -81,9 +81,7 @@ const wallet_addEthereumChain = async ({ res.result = null; return; } - await validateRpcEndpoint(firstValidRPCUrl, chainId); - const requestData = { chainId, blockExplorerUrl: firstValidBlockExplorerUrl, @@ -98,7 +96,6 @@ const wallet_addEthereumChain = async ({ requestData.chainName, requestData.ticker, ); - requestData.alerts = alerts; MetaMetrics.getInstance().trackEvent(MetaMetricsEvents.NETWORK_REQUESTED, { @@ -107,22 +104,32 @@ const wallet_addEthereumChain = async ({ symbol: ticker, ...analytics, }); - // Remove all existing approvals, including other add network requests. ApprovalController.clear(providerErrors.userRejectedRequest()); // If existing approval request was an add network request, wait for // it to be rejected and for the corresponding approval flow to be ended. await waitForInteraction(); - const { id: approvalFlowId } = startApprovalFlow(); try { - await requestUserApproval({ - type: 'ADD_ETHEREUM_CHAIN', - requestData, - }); - + try { + await requestUserApproval({ + type: 'ADD_ETHEREUM_CHAIN', + requestData, + }); + } catch (error) { + MetaMetrics.getInstance().trackEvent( + MetaMetricsEvents.NETWORK_REQUEST_REJECTED, + { + chain_id: getDecimalChainId(chainId), + source: 'Custom Network API', + symbol: ticker, + ...analytics, + }, + ); + throw providerErrors.userRejectedRequest(); + } const networkConfigurationId = await NetworkController.upsertNetworkConfiguration( { @@ -168,17 +175,6 @@ const wallet_addEthereumChain = async ({ MetaMetricsEvents.NETWORK_SWITCHED, analyticsParams, ); - } catch (error) { - MetaMetrics.getInstance().trackEvent( - MetaMetricsEvents.NETWORK_REQUEST_REJECTED, - { - chain_id: getDecimalChainId(chainId), - source: 'Custom Network API', - symbol: ticker, - ...analytics, - }, - ); - throw error; } finally { endApprovalFlow({ id: approvalFlowId }); } diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index 3c55474c2e6..f6abd02ce1c 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -26,6 +26,9 @@ jest.mock('../Engine', () => ({ ApprovalController: { clear: jest.fn(), }, + PermissionController: { + hasPermission: jest.fn().mockReturnValue(true), + }, }, })); From 8b82148e13be63e87dc13d78def08af6b89e0160 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Oct 2024 10:18:02 -0500 Subject: [PATCH 07/29] fix test --- app/core/Permissions/specifications.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/core/Permissions/specifications.test.js b/app/core/Permissions/specifications.test.js index ce979964677..9dd34c5d5a4 100644 --- a/app/core/Permissions/specifications.test.js +++ b/app/core/Permissions/specifications.test.js @@ -2,6 +2,7 @@ import { CaveatTypes, RestrictedMethods } from './constants'; import { getCaveatSpecifications, getPermissionSpecifications, + PermissionKeys, unrestrictedMethods, } from './specifications'; import { EthAccountType, EthMethod } from '@metamask/keyring-api'; @@ -22,7 +23,7 @@ describe('PermissionController specifications', () => { describe('caveat specifications', () => { it('getCaveatSpecifications returns the expected specifications object', () => { const caveatSpecifications = getCaveatSpecifications({}); - expect(Object.keys(caveatSpecifications)).toHaveLength(12); + expect(Object.keys(caveatSpecifications)).toHaveLength(13); expect( caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type, ).toStrictEqual(CaveatTypes.restrictReturnedAccounts); @@ -187,10 +188,13 @@ describe('PermissionController specifications', () => { describe('permission specifications', () => { it('getPermissionSpecifications returns the expected specifications object', () => { const permissionSpecifications = getPermissionSpecifications({}); - expect(Object.keys(permissionSpecifications)).toHaveLength(1); + expect(Object.keys(permissionSpecifications)).toHaveLength(2); expect( permissionSpecifications[RestrictedMethods.eth_accounts].targetName, ).toStrictEqual(RestrictedMethods.eth_accounts); + expect( + permissionSpecifications[PermissionKeys.permittedChains].targetName, + ).toStrictEqual(PermissionKeys.permittedChains); }); describe('eth_accounts', () => { From 3af5922f9eb48ff251918c88cdcb23c35af74c52 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Oct 2024 15:13:27 -0500 Subject: [PATCH 08/29] env variable not set correctly --- .../RPCMethods/lib/ethereum-chain-utils.js | 5 ++- .../wallet_addEthereumChain.test.js | 40 +++++++++++++++++++ printout.txt | 3 ++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 printout.txt diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 6ce75769977..4f3ba724ead 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -240,8 +240,9 @@ export async function switchToNetwork({ symbol: networkConfiguration?.ticker || 'ETH', ...analytics, }; - - if (process.env.MULTICHAIN_V1) { + const chainPermissionsEnabled = process.env.CHAIN_PERMISSIONS; + console.log('chainPermissionsEnabled:', chainPermissionsEnabled); + if (chainPermissionsEnabled) { const { value: permissionedChainIds } = getCaveat({ target: PermissionKeys.permittedChains, diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index f6abd02ce1c..0a39f9bb672 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -2,6 +2,8 @@ import { InteractionManager } from 'react-native'; import { providerErrors } from '@metamask/rpc-errors'; import wallet_addEthereumChain from './wallet_addEthereumChain'; import Engine from '../Engine'; +import { CaveatFactories, PermissionKeys } from '../Permissions/specifications'; +import { CaveatTypes } from '../Permissions/constants'; const mockEngine = Engine; @@ -28,6 +30,11 @@ jest.mock('../Engine', () => ({ }, PermissionController: { hasPermission: jest.fn().mockReturnValue(true), + grantPermissionsIncremental: jest.fn(), + requestPermissionsIncremental: jest.fn(), + }, + SelectedNetworkController: { + setNetworkClientIdForDomain: jest.fn(), }, }, })); @@ -310,4 +317,37 @@ describe('RPC Method - wallet_addEthereumChain', () => { expect(Engine.context.ApprovalController.clear).toBeCalledTimes(1); }); }); + + describe('CHAIN_PERMISSIONS is enabled', () => { + // afterAll(() => { + // process.env.CHAIN_PERMISSIONS = undefined; + // }); + it.only('should grant permissions when chain is not already permitted', async () => { + process.env.CHAIN_PERMISSIONS = '1'; + const spyOnGrantPermissionsIncremental = jest.spyOn( + Engine.context.PermissionController, + 'grantPermissionsIncremental', + ); + await wallet_addEthereumChain({ + req: { + params: [correctParams], + }, + ...otherOptions, + }); + + expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledTimes(1); + expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledWith({ + subject: { origin: 'https://example.com' }, + approvedPermissions: { + [PermissionKeys.permittedChains]: { + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ + 'chainId', + ]), + ], + }, + }, + }); + }); + }); }); diff --git a/printout.txt b/printout.txt new file mode 100644 index 00000000000..9f097b7b10c --- /dev/null +++ b/printout.txt @@ -0,0 +1,3 @@ +yarn run v1.22.22 +$ /Users/alex/Documents/metamask/metamask-mobile/node_modules/.bin/jest wallet_AddEthereumChain.test.js +info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. From 8d9ebf31746f036a294a46746e66c9c93bf324c4 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Oct 2024 15:32:21 -0500 Subject: [PATCH 09/29] fixed env variable issue --- app/core/RPCMethods/lib/ethereum-chain-utils.js | 2 +- app/core/RPCMethods/wallet_addEthereumChain.test.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 4f3ba724ead..7abd9d9406a 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -240,7 +240,7 @@ export async function switchToNetwork({ symbol: networkConfiguration?.ticker || 'ETH', ...analytics, }; - const chainPermissionsEnabled = process.env.CHAIN_PERMISSIONS; + const chainPermissionsEnabled = ({ ...process.env })?.CHAIN_PERMISSIONS; console.log('chainPermissionsEnabled:', chainPermissionsEnabled); if (chainPermissionsEnabled) { const { value: permissionedChainIds } = diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index 0a39f9bb672..bc855bab533 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -32,6 +32,7 @@ jest.mock('../Engine', () => ({ hasPermission: jest.fn().mockReturnValue(true), grantPermissionsIncremental: jest.fn(), requestPermissionsIncremental: jest.fn(), + getCaveat: jest.fn(), }, SelectedNetworkController: { setNetworkClientIdForDomain: jest.fn(), From 247540517d0ec70d6f80805cf3ee659e94520783 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Oct 2024 16:49:46 -0500 Subject: [PATCH 10/29] wallet_addEthereumChain tests passing, rename env variables, cleanup --- .js.env.example | 7 +++- app/core/Permissions/specifications.js | 2 + .../RPCMethods/lib/ethereum-chain-utils.js | 8 ++-- .../wallet_addEthereumChain.test.js | 41 +++++++++++++++---- app/selectors/selectedNetworkController.ts | 8 ++-- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/.js.env.example b/.js.env.example index 68e8316f034..63a3261b9a1 100644 --- a/.js.env.example +++ b/.js.env.example @@ -90,7 +90,10 @@ export MM_ENABLE_SETTINGS_PAGE_DEV_OPTIONS="true" # The endpoint used to submit errors and tracing data to Sentry for dev environment. # export MM_SENTRY_DSN_DEV= -# Multichain Feature flag -export MULTICHAIN_V1="" +# Per dapp selected network (Amon Hen) feature flag +export MM_MM_PER_DAPP_SELECTED_NETWORK="" + +export MM_MM_CHAIN_PERMISSIONS="" + #Multichain feature flag specific to UI changes export MM_MULTICHAIN_V1_ENABLED="" diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index b1b6a485b3e..189aecc324f 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -90,6 +90,8 @@ export const getCaveatSpecifications = ({ merger: (leftValue, rightValue) => { const newValue = Array.from(new Set([...leftValue, ...rightValue])); const diff = newValue.filter((value) => !leftValue.includes(value)); + + /** @type {[any, any]} */ return [newValue, diff]; }, }, diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 7abd9d9406a..bafad957677 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -206,7 +206,6 @@ export async function switchToNetwork({ PermissionController, SelectedNetworkController, } = controllers; - const getCaveat = ({ target, caveatType }) => { try { return PermissionController.getCaveat(origin, target, caveatType); @@ -240,9 +239,8 @@ export async function switchToNetwork({ symbol: networkConfiguration?.ticker || 'ETH', ...analytics, }; - const chainPermissionsEnabled = ({ ...process.env })?.CHAIN_PERMISSIONS; - console.log('chainPermissionsEnabled:', chainPermissionsEnabled); - if (chainPermissionsEnabled) { + const chainPermissionsFeatureEnabled = ({ ...process.env })?.MM_CHAIN_PERMISSIONS; + if (chainPermissionsFeatureEnabled) { const { value: permissionedChainIds } = getCaveat({ target: PermissionKeys.permittedChains, @@ -293,7 +291,7 @@ export async function switchToNetwork({ 'eth_accounts', ); - if (process.env.MULTICHAIN_V1 && originHasAccountsPermission) { + if (process.env.MM_PER_DAPP_SELECTED_NETWORK && originHasAccountsPermission) { SelectedNetworkController.setNetworkClientIdForDomain( origin, networkConfigurationId || networkConfiguration.networkType, diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index bc855bab533..d277c8c9312 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -319,12 +319,17 @@ describe('RPC Method - wallet_addEthereumChain', () => { }); }); - describe('CHAIN_PERMISSIONS is enabled', () => { - // afterAll(() => { - // process.env.CHAIN_PERMISSIONS = undefined; - // }); - it.only('should grant permissions when chain is not already permitted', async () => { - process.env.CHAIN_PERMISSIONS = '1'; + describe('MM_CHAIN_PERMISSIONS is enabled', () => { + beforeAll(() => { + process.env.MM_CHAIN_PERMISSIONS = 1; + }); + afterAll(() => { + process.env.MM_CHAIN_PERMISSIONS = undefined; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should grant permissions when chain is not already permitted', async () => { const spyOnGrantPermissionsIncremental = jest.spyOn( Engine.context.PermissionController, 'grantPermissionsIncremental', @@ -332,6 +337,7 @@ describe('RPC Method - wallet_addEthereumChain', () => { await wallet_addEthereumChain({ req: { params: [correctParams], + origin: 'https://example.com', }, ...otherOptions, }); @@ -342,13 +348,30 @@ describe('RPC Method - wallet_addEthereumChain', () => { approvedPermissions: { [PermissionKeys.permittedChains]: { caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ - 'chainId', - ]), + CaveatFactories[CaveatTypes.restrictNetworkSwitching](['0x64']), ], }, }, }); }); + + it('should not grant permissions when chain is already permitted', async () => { + const spyOnGrantPermissionsIncremental = jest.spyOn( + Engine.context.PermissionController, + 'grantPermissionsIncremental', + ); + jest + .spyOn(Engine.context.PermissionController, 'getCaveat') + .mockReturnValue({ value: ['0x64'] }); + await wallet_addEthereumChain({ + req: { + params: [correctParams], + origin: 'https://example.com', + }, + ...otherOptions, + }); + + expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledTimes(0); + }); }); }); diff --git a/app/selectors/selectedNetworkController.ts b/app/selectors/selectedNetworkController.ts index caa8ef11de2..312100a2ea5 100644 --- a/app/selectors/selectedNetworkController.ts +++ b/app/selectors/selectedNetworkController.ts @@ -91,7 +91,7 @@ export const makeSelectNetworkName = () => globalNetworkClientId, hostname, ) => { - if (!hostname || !process.env.MULTICHAIN_V1) return providerNetworkName; + if (!hostname || !process.env.MM_PER_DAPP_SELECTED_NETWORK) return providerNetworkName; const relevantNetworkClientId = domainNetworkClientId || globalNetworkClientId; return ( @@ -118,7 +118,7 @@ export const makeSelectNetworkImageSource = () => globalNetworkClientId, hostname, ) => { - if (!hostname || !process.env.MULTICHAIN_V1) + if (!hostname || !process.env.MM_PER_DAPP_SELECTED_NETWORK) return providerNetworkImageSource; const relevantNetworkClientId = domainNetworkClientId || globalNetworkClientId; @@ -152,7 +152,7 @@ export const makeSelectChainId = () => globalNetworkClientId, hostname, ) => { - if (!hostname || !process.env.MULTICHAIN_V1) { + if (!hostname || !process.env.MM_PER_DAPP_SELECTED_NETWORK) { return providerChainId; } const relevantNetworkClientId = @@ -181,7 +181,7 @@ export const makeSelectRpcUrl = () => globalNetworkClientId, hostname, ) => { - if (!hostname || !process.env.MULTICHAIN_V1) return providerRpcUrl; + if (!hostname || !process.env.MM_PER_DAPP_SELECTED_NETWORK) return providerRpcUrl; const relevantNetworkClientId = domainNetworkClientId || globalNetworkClientId; return networkConfigurations[relevantNetworkClientId]?.rpcUrl; From 6543246990b09c5f88c42329ed68f4514cd5b322 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 8 Oct 2024 10:32:09 -0500 Subject: [PATCH 11/29] add wallet_switchEthereumChain tests --- .../RPCMethods/lib/ethereum-chain-utils.js | 3 +- .../wallet_addEthereumChain.test.js | 2 +- .../RPCMethods/wallet_switchEthereumChain.js | 1 - .../wallet_switchEthereumChain.test.js | 146 ++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index bafad957677..6e756ccfe7d 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -239,7 +239,8 @@ export async function switchToNetwork({ symbol: networkConfiguration?.ticker || 'ETH', ...analytics, }; - const chainPermissionsFeatureEnabled = ({ ...process.env })?.MM_CHAIN_PERMISSIONS; + const chainPermissionsFeatureEnabled = { ...process.env } + ?.MM_CHAIN_PERMISSIONS; if (chainPermissionsFeatureEnabled) { const { value: permissionedChainIds } = getCaveat({ diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index d277c8c9312..fb2495cc81b 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -324,7 +324,7 @@ describe('RPC Method - wallet_addEthereumChain', () => { process.env.MM_CHAIN_PERMISSIONS = 1; }); afterAll(() => { - process.env.MM_CHAIN_PERMISSIONS = undefined; + process.env.MM_CHAIN_PERMISSIONS = 0; }); afterEach(() => { jest.clearAllMocks(); diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.js b/app/core/RPCMethods/wallet_switchEthereumChain.js index b8a947ce18b..7dc69513806 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.js @@ -49,7 +49,6 @@ const wallet_switchEthereumChain = async ({ const networkConfigurations = selectNetworkConfigurations(store.getState()); const existingNetwork = findExistingNetwork(_chainId, networkConfigurations); - if (existingNetwork) { const currentDomainSelectedNetworkClientId = SelectedNetworkController.getNetworkClientIdForDomain(origin); diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.test.js b/app/core/RPCMethods/wallet_switchEthereumChain.test.js index 9f32cdc77de..b3d7b2e8bad 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.test.js @@ -1,4 +1,68 @@ import wallet_switchEthereumChain from './wallet_switchEthereumChain'; +import Engine from '../Engine'; +import { NetworkController } from '@metamask/network-controller'; +import { CaveatFactories, PermissionKeys } from '../Permissions/specifications'; +import { CaveatTypes } from '../Permissions/constants'; + +// jest.mock('@metamask/network-controller');s +jest.mock('../Engine', () => ({ + context: { + NetworkController: { + setActiveNetwork: jest.fn(), + getNetworkClientById: jest.fn(), + }, + CurrencyRateController: { + updateExchangeRate: jest.fn(), + }, + PermissionController: { + hasPermission: jest.fn().mockReturnValue(true), + requestPermissionsIncremental: jest.fn(), + getCaveat: jest.fn(), + }, + SelectedNetworkController: { + setNetworkClientIdForDomain: jest.fn(), + getNetworkClientIdForDomain: jest.fn(), + }, + }, +})); + +jest.mock('../../store', () => ({ + store: { + getState: jest.fn(() => ({ + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'mainnet', + networksMetadata: {}, + networkConfigurations: { + mainnet: { + id: 'mainnet', + rpcUrl: 'https://mainnet.infura.io/v3', + chainId: '0x1', + ticker: 'ETH', + nickname: 'Sepolia network', + rpcPrefs: { + blockExplorerUrl: 'https://etherscan.com', + }, + }, + 'test-network-configuration-id': { + id: 'test-network-configuration-id', + rpcUrl: 'https://gnosis-chain.infura.io/v3', + chainId: '0x64', + ticker: 'ETH', + nickname: 'Gnosis Chain', + rpcPrefs: { + blockExplorerUrl: 'https://gnosisscan.com', + }, + }, + }, + }, + }, + }, + })), + }, +})); + const correctParams = { chainId: '0x1', }; @@ -6,6 +70,7 @@ const correctParams = { const otherOptions = { res: {}, switchCustomNetworkRequest: {}, + requestUserApproval: jest.fn(), }; describe('RPC Method - wallet_switchEthereumChain', () => { @@ -66,4 +131,85 @@ describe('RPC Method - wallet_switchEthereumChain', () => { ); } }); + + describe('MM_CHAIN_PERMISSIONS is enabled', () => { + beforeAll(() => { + process.env.MM_CHAIN_PERMISSIONS = 1; + }); + afterAll(() => { + process.env.MM_CHAIN_PERMISSIONS = 0; + }); + it('should not change network permissions and should switch without user approval when chain is already permitted', async () => { + const spyOnRequestPermissionsIncremental = jest.spyOn( + Engine.context.PermissionController, + 'requestPermissionsIncremental', + ); + jest + .spyOn( + Engine.context.SelectedNetworkController, + 'getNetworkClientIdForDomain', + ) + .mockReturnValue('mainnet'); + jest + .spyOn(Engine.context.NetworkController, 'getNetworkClientById') + .mockReturnValue({ configuration: { chainId: '0x1' } }); + jest + .spyOn(Engine.context.PermissionController, 'getCaveat') + .mockReturnValue({ value: ['0x64'] }); + + const spyOnSetActiveNetwork = jest.spyOn( + Engine.context.NetworkController, + 'setActiveNetwork', + ); + await wallet_switchEthereumChain({ + req: { + params: [{ chainId: '0x64' }], + }, + ...otherOptions, + }); + + expect(spyOnRequestPermissionsIncremental).not.toHaveBeenCalled(); + + expect(otherOptions.requestUserApproval).not.toHaveBeenCalled(); + expect(spyOnSetActiveNetwork).toHaveBeenCalledWith( + 'test-network-configuration-id', + ); + }); + + it('should add network permission and should switch with user approval when requested chain is not permitted', async () => { + const spyOnRequestPermissionsIncremental = jest.spyOn( + Engine.context.PermissionController, + 'requestPermissionsIncremental', + ); + jest + .spyOn( + Engine.context.SelectedNetworkController, + 'getNetworkClientIdForDomain', + ) + .mockReturnValue('mainnet'); + jest + .spyOn(Engine.context.NetworkController, 'getNetworkClientById') + .mockReturnValue({ configuration: { chainId: '0x1' } }); + const spyOnSetActiveNetwork = jest.spyOn( + Engine.context.NetworkController, + 'setActiveNetwork', + ); + jest + .spyOn(Engine.context.PermissionController, 'getCaveat') + .mockReturnValue({ value: [] }); + await wallet_switchEthereumChain({ + req: { + params: [{ chainId: '0x64' }], + }, + ...otherOptions, + }); + + // this request shows a permissions request to the user + // which, if approved, adds an endowmnet:permittedChains permission + expect(spyOnRequestPermissionsIncremental).toHaveBeenCalledTimes(1); + expect(spyOnSetActiveNetwork).toHaveBeenCalledWith( + 'test-network-configuration-id', + ); + }); + }); }); From ea5c2b82c517a42191d5b7c43253b07630fd7282 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 8 Oct 2024 10:46:43 -0500 Subject: [PATCH 12/29] cleanup --- app/core/Permissions/specifications.js | 7 +++++-- app/core/RPCMethods/RPCMethodMiddleware.test.ts | 1 + printout.txt | 3 --- 3 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 printout.txt diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index 189aecc324f..ab00d4422f8 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -87,11 +87,14 @@ export const getCaveatSpecifications = ({ type: CaveatTypes.restrictNetworkSwitching, validator: (caveat, _origin, _target) => validateCaveatNetworks(caveat.value, findNetworkClientIdByChainId), + /** + * @param {any[]} leftValue + * @param {any[]} rightValue + * @returns {[any[], any[]]} + */ merger: (leftValue, rightValue) => { const newValue = Array.from(new Set([...leftValue, ...rightValue])); const diff = newValue.filter((value) => !leftValue.includes(value)); - - /** @type {[any, any]} */ return [newValue, diff]; }, }, diff --git a/app/core/RPCMethods/RPCMethodMiddleware.test.ts b/app/core/RPCMethods/RPCMethodMiddleware.test.ts index c482a2f2f73..2eb48103f33 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.test.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.test.ts @@ -393,6 +393,7 @@ describe('getRpcMethodMiddleware', () => { }), caveatSpecifications: getCaveatSpecifications({ getInternalAccounts: mockGetInternalAccounts, + findNetworkClientIdByChainId: jest.fn(), }), // @ts-expect-error Typecast permissionType from getPermissionSpecifications to be of type PermissionType.RestrictedMethod permissionSpecifications: { diff --git a/printout.txt b/printout.txt deleted file mode 100644 index 9f097b7b10c..00000000000 --- a/printout.txt +++ /dev/null @@ -1,3 +0,0 @@ -yarn run v1.22.22 -$ /Users/alex/Documents/metamask/metamask-mobile/node_modules/.bin/jest wallet_AddEthereumChain.test.js -info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. From 2cb7c1d00fa7483093d346f4354d42a11c235aa9 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 8 Oct 2024 17:18:08 -0500 Subject: [PATCH 13/29] wiring up --- .js.env.example | 4 +- .../SwitchChainApproval.tsx | 1 + app/components/Nav/Main/RootRPCMethodsUI.js | 3 ++ .../PermissionsSummary/PermissionsSummary.tsx | 18 +++++-- .../PermissionsSummary.types.ts | 6 +++ .../UI/SwitchCustomNetwork/index.js | 2 + .../Views/AccountConnect/AccountConnect.tsx | 14 +++++- .../AccountPermissions/AccountPermissions.tsx | 1 + app/core/Permissions/specifications.js | 2 +- .../RPCMethods/lib/ethereum-chain-utils.js | 49 ++++++++++++------- .../RPCMethods/wallet_addEthereumChain.js | 5 +- .../RPCMethods/wallet_switchEthereumChain.js | 12 ++--- app/util/networks/index.js | 3 ++ 13 files changed, 85 insertions(+), 35 deletions(-) diff --git a/.js.env.example b/.js.env.example index 63a3261b9a1..81d9fea44a1 100644 --- a/.js.env.example +++ b/.js.env.example @@ -91,9 +91,9 @@ export MM_ENABLE_SETTINGS_PAGE_DEV_OPTIONS="true" # export MM_SENTRY_DSN_DEV= # Per dapp selected network (Amon Hen) feature flag -export MM_MM_PER_DAPP_SELECTED_NETWORK="" +export MM_PER_DAPP_SELECTED_NETWORK="" -export MM_MM_CHAIN_PERMISSIONS="" +export MM_CHAIN_PERMISSIONS="" #Multichain feature flag specific to UI changes export MM_MULTICHAIN_V1_ENABLED="" diff --git a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx index 9a3310addf9..dfa80235412 100644 --- a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx +++ b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx @@ -14,6 +14,7 @@ const SwitchChainApproval = () => { onReject, } = useApprovalRequest(); + console.log('ALEX LOGGING: approvalRequest', approvalRequest); const dispatch = useDispatch(); const onConfirm = useCallback(() => { diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 086d3c3a61f..068c73d01d9 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -128,6 +128,7 @@ export const useSwapConfirmedEvent = ({ trackSwaps }) => { }; const RootRPCMethodsUI = (props) => { + console.log('ALEX LOGGING: RootRPCMethodsUI', props); const { trackEvent } = useMetrics(); const [transactionModalType, setTransactionModalType] = useState(undefined); const tokenList = useSelector(selectTokenList); @@ -471,6 +472,8 @@ const RootRPCMethodsUI = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + console.log('ALEX LOGGING: transactionModalType', transactionModalType); + return ( diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx index 53538a8a3dd..cbd71063c57 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx @@ -30,8 +30,6 @@ import useSelectedAccount from '../Tabs/TabThumbnail/useSelectedAccount'; import styleSheet from './PermissionsSummary.styles'; import { useStyles } from '../../../component-library/hooks'; import { PermissionsSummaryProps } from './PermissionsSummary.types'; -import { useSelector } from 'react-redux'; -import { selectNetworkName } from '../../../selectors/networkInfos'; import { USER_INTENT } from '../../../constants/permissions'; import Routes from '../../../constants/navigation/Routes'; import ButtonIcon, { @@ -40,6 +38,10 @@ import ButtonIcon, { const PermissionsSummary = ({ currentPageInformation, + customNetworkInformation, + onConfirm, + onCancel, + requestData, onEdit, onEditNetworks, onBack, @@ -54,14 +56,20 @@ const PermissionsSummary = ({ const { styles } = useStyles(styleSheet, { isRenderedAsBottomSheet }); const { navigate } = useNavigation(); const selectedAccount = useSelectedAccount(); - const networkName = useSelector(selectNetworkName); - + console.log('ALEX LOGGING: customNetworkInformation', customNetworkInformation); + console.log('ALEX LOGGING: isNetworkSwitch', isNetworkSwitch); + // const { chainName } = customNetworkInformation; + // TODO get diff from requestData + // TODO get chainName from networkController based on chainId requested + const chainName = 'ALEX LOGGING: chainName'; const confirm = () => { onUserAction?.(USER_INTENT.Confirm); + // onConfirm?.(); }; const cancel = () => { onUserAction?.(USER_INTENT.Cancel); + // onCancel?.(); }; const handleEditAccountsButtonPress = () => { @@ -214,7 +222,7 @@ const PermissionsSummary = ({ {strings('permissions.requesting_for')} - {networkName} + {chainName} diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts index c80d27e198a..c6a97684e56 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts @@ -6,13 +6,19 @@ export interface PermissionsSummaryProps { icon: string | { uri: string }; url: string; }; + requestData: unknown; onEdit?: () => void; onEditNetworks?: () => void; onBack?: () => void; + onCancel?: () => void; + onConfirm?: () => void; onUserAction?: React.Dispatch>; showActionButtons?: boolean; isAlreadyConnected?: boolean; isRenderedAsBottomSheet?: boolean; isDisconnectAllShown?: boolean; isNetworkSwitch?: boolean; + customNetworkInformation: { + chainName: string; + }; } diff --git a/app/components/UI/SwitchCustomNetwork/index.js b/app/components/UI/SwitchCustomNetwork/index.js index a6a55fb526c..b0511cd6061 100644 --- a/app/components/UI/SwitchCustomNetwork/index.js +++ b/app/components/UI/SwitchCustomNetwork/index.js @@ -205,6 +205,8 @@ const SwitchCustomNetwork = ({ /> ); + console.log('ALEX LOGGING: isMultichainVersion1Enabled', isMultichainVersion1Enabled); + return isMultichainVersion1Enabled ? renderPermissionSummary() : renderNetworkSwitchingNotice(); diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 6bb65b3e2d0..30a125b8e7f 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -85,6 +85,13 @@ const AccountConnect = (props: AccountConnectProps) => { const { colors } = useTheme(); const styles = createStyles(); const { hostInfo, permissionRequestId } = props.route.params; + + const permissionDiffMap = hostInfo?.diff?.permissionDiffMap; + console.log( + 'ALEX LOGGING: permissionDiffMap in accountConnect', + permissionDiffMap, + ); + const [isLoading, setIsLoading] = useState(false); const navigation = useNavigation(); const { trackEvent } = useMetrics(); @@ -344,7 +351,7 @@ const AccountConnect = (props: AccountConnectProps) => { }, approvedAccounts: selectedAddresses, }; - + console.log('ALEX LOGGING: request in handleConnect', request.diff); const connectedAccountLength = selectedAddresses.length; const activeAddress = selectedAddresses[0]; const activeAccountName = getAccountNameWithENS({ @@ -564,9 +571,12 @@ const AccountConnect = (props: AccountConnectProps) => { setScreen(AccountConnectScreens.MultiConnectNetworkSelector), onUserAction: setUserIntent, isAlreadyConnected: false, + requestData: { + diff: permissionDiffMap, + }, }; return ; - }, [faviconSource, urlWithProtocol]); + }, [faviconSource, urlWithProtocol, permissionDiffMap]); const renderSingleConnectSelectorScreen = useCallback( () => ( diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index 78a3555fdca..f29f78e9405 100755 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -394,6 +394,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { : navigation.navigate('PermissionsManager'), isRenderedAsBottomSheet, }; + console.log('ALEX LOGGING: renderPermissionsSummaryScreen in accountPermissions', permissionsSummaryProps); return ; }, [faviconSource, urlWithProtocol, isRenderedAsBottomSheet, navigation]); diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index ab00d4422f8..5442cd40cf5 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -23,7 +23,7 @@ import { CaveatTypes, RestrictedMethods } from './constants'; */ export const PermissionKeys = Object.freeze({ ...RestrictedMethods, - permittedChains: 'permittedChains', + permittedChains: 'endowment:permitted-chains', }); /** diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 6e756ccfe7d..80d5158c6f6 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -6,6 +6,7 @@ import { getDecimalChainId, isPrefixedFormattedHexString, getDefaultNetworkByChainId, + isChainPermissionsFeatureEnabled, } from '../../../util/networks'; import { CaveatFactories, @@ -188,7 +189,12 @@ export function findExistingNetwork(chainId, networkConfigurations) { ([, networkConfiguration]) => networkConfiguration.chainId === chainId, ); - return existingEntry || existingNetworkDefault; + return ( + existingEntry || [ + existingNetworkDefault?.networkType, + existingNetworkDefault, + ] + ); } export async function switchToNetwork({ @@ -220,8 +226,7 @@ export async function switchToNetwork({ return undefined; }; - - const [networkConfigurationId, networkConfiguration] = network; + const { networkConfigurationId, networkConfiguration } = network; const requestData = { rpcUrl: networkConfiguration.rpcUrl, @@ -232,15 +237,19 @@ export async function switchToNetwork({ networkConfiguration.shortName, ticker: networkConfiguration.ticker || 'ETH', }; - const analyticsParams = { chain_id: getDecimalChainId(chainId), source: 'Custom Network API', symbol: networkConfiguration?.ticker || 'ETH', ...analytics, }; - const chainPermissionsFeatureEnabled = { ...process.env } - ?.MM_CHAIN_PERMISSIONS; + + // for some reason this extra step is necessary in test environment + const chainPermissionsFeatureEnabled = + { ...process.env }?.NODE_ENV === 'test' + ? { ...process.env }?.MM_CHAIN_PERMISSIONS === '1' + : isChainPermissionsFeatureEnabled; + if (chainPermissionsFeatureEnabled) { const { value: permissionedChainIds } = getCaveat({ @@ -265,21 +274,27 @@ export async function switchToNetwork({ }, }); } else { - await PermissionController.requestPermissionsIncremental({ - subject: { origin }, - requestedPermissions: { - [PermissionKeys.permittedChains]: { - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ - chainId, - ]), - ], + console.log('ALEX LOGGING: requestPermissionsIncremental'); + try { + await PermissionController.requestPermissionsIncremental( + { origin }, + { + [PermissionKeys.permittedChains]: { + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ + chainId, + ]), + ], + }, }, - }, - }); + ); + } catch (e) { + console.log('ALEX LOGGING: requestPermissionsIncremental error', e); + } } } } else { + console.log('ALEX LOGGING: requestUserApproval'); const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; await requestUserApproval({ type: 'SWITCH_ETHEREUM_CHAIN', diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index 8927d2938a6..e1a2e288566 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -154,7 +154,10 @@ const wallet_addEthereumChain = async ({ ...analytics, }); - const network = [networkConfigurationId, requestData]; + const network = { + networkConfigurationId, + networkConfiguration: requestData, + }; const analyticsParams = await switchToNetwork({ network, diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.js b/app/core/RPCMethods/wallet_switchEthereumChain.js index 7dc69513806..08d04a35dfe 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.js @@ -23,7 +23,7 @@ const wallet_switchEthereumChain = async ({ } = Engine.context; const params = req.params?.[0]; const { origin } = req; - + console.log('params', params); if (!params || typeof params !== 'object') { throw rpcErrors.invalidParams({ message: `Expected single, object parameter. Received:\n${JSON.stringify( @@ -31,9 +31,7 @@ const wallet_switchEthereumChain = async ({ )}`, }); } - const { chainId } = params; - const allowedKeys = { chainId: true, }; @@ -44,7 +42,6 @@ const wallet_switchEthereumChain = async ({ `Received unexpected keys on object parameter. Unsupported keys:\n${extraKeys}`, ); } - const _chainId = validateChainId(chainId); const networkConfigurations = selectNetworkConfigurations(store.getState()); @@ -52,7 +49,6 @@ const wallet_switchEthereumChain = async ({ if (existingNetwork) { const currentDomainSelectedNetworkClientId = SelectedNetworkController.getNetworkClientIdForDomain(origin); - const { configuration: { chainId: currentDomainSelectedChainId }, } = NetworkController.getNetworkClientById( @@ -63,9 +59,11 @@ const wallet_switchEthereumChain = async ({ res.result = null; return; } - const analyticsParams = await switchToNetwork({ - network: existingNetwork, + network: { + networkConfigurationId: existingNetwork?.[0], + networkConfiguration: existingNetwork?.[1], + }, chainId: _chainId, controllers: { CurrencyRateController, diff --git a/app/util/networks/index.js b/app/util/networks/index.js index 4a11aa7798a..556f196248f 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -581,3 +581,6 @@ export const deprecatedGetNetworkId = async () => { export const isMultichainVersion1Enabled = process.env.MM_MULTICHAIN_V1_ENABLED === '1'; + +export const isChainPermissionsFeatureEnabled = + process.env.MM_CHAIN_PERMISSIONS === '1'; From 90e9e866cf64e115fa24444f5771bed72b2ded5e Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 09:58:21 -0500 Subject: [PATCH 14/29] WIP --- .../SwitchChainApproval.tsx | 1 - app/components/Nav/Main/RootRPCMethodsUI.js | 3 -- .../PermissionsSummary/PermissionsSummary.tsx | 35 +++++++++++++------ .../UI/SwitchCustomNetwork/index.js | 2 -- .../Views/AccountConnect/AccountConnect.tsx | 6 +--- .../AccountConnect/AccountConnect.types.ts | 1 + .../AccountPermissions/AccountPermissions.tsx | 1 - .../RPCMethods/lib/ethereum-chain-utils.js | 32 ++++++++--------- app/selectors/networkController.ts | 21 +++++++++++ 9 files changed, 63 insertions(+), 39 deletions(-) diff --git a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx index dfa80235412..9a3310addf9 100644 --- a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx +++ b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx @@ -14,7 +14,6 @@ const SwitchChainApproval = () => { onReject, } = useApprovalRequest(); - console.log('ALEX LOGGING: approvalRequest', approvalRequest); const dispatch = useDispatch(); const onConfirm = useCallback(() => { diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 068c73d01d9..086d3c3a61f 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -128,7 +128,6 @@ export const useSwapConfirmedEvent = ({ trackSwaps }) => { }; const RootRPCMethodsUI = (props) => { - console.log('ALEX LOGGING: RootRPCMethodsUI', props); const { trackEvent } = useMetrics(); const [transactionModalType, setTransactionModalType] = useState(undefined); const tokenList = useSelector(selectTokenList); @@ -472,8 +471,6 @@ const RootRPCMethodsUI = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - console.log('ALEX LOGGING: transactionModalType', transactionModalType); - return ( diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx index cbd71063c57..568850d50c7 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx @@ -35,12 +35,13 @@ import Routes from '../../../constants/navigation/Routes'; import ButtonIcon, { ButtonIconSizes, } from '../../../component-library/components/Buttons/ButtonIcon'; +import { PermissionKeys } from '../../../core/Permissions/specifications'; +import { selectNetworkNameByChainId } from '../../../selectors/networkController'; +import { useSelector } from 'react-redux'; +import { RootState } from 'app/reducers'; const PermissionsSummary = ({ currentPageInformation, - customNetworkInformation, - onConfirm, - onCancel, requestData, onEdit, onEditNetworks, @@ -50,18 +51,31 @@ const PermissionsSummary = ({ isAlreadyConnected = true, isRenderedAsBottomSheet = true, isDisconnectAllShown = true, - isNetworkSwitch = false, }: PermissionsSummaryProps) => { const { colors } = useTheme(); const { styles } = useStyles(styleSheet, { isRenderedAsBottomSheet }); const { navigate } = useNavigation(); const selectedAccount = useSelectedAccount(); - console.log('ALEX LOGGING: customNetworkInformation', customNetworkInformation); - console.log('ALEX LOGGING: isNetworkSwitch', isNetworkSwitch); - // const { chainName } = customNetworkInformation; - // TODO get diff from requestData - // TODO get chainName from networkController based on chainId requested - const chainName = 'ALEX LOGGING: chainName'; + + const permissionDiffMap = requestData?.diff; + let isNetworkSwitch = false; + + //TODO: This could be true if the dapp is requesting permission for networks... + // need to check how we differentiate between these types of requests on extension + if (permissionDiffMap?.[PermissionKeys.permittedChains]) { + isNetworkSwitch = true; + } + + const chainId = + requestData?.diff?.[PermissionKeys.permittedChains] + ?.restrictNetworkSwitching?.[0]; + + const chainName = useSelector((state: RootState) => + selectNetworkNameByChainId(state, chainId), + ); + // this method is not available until version 21 of the network controller + // const { name: chainName } = + // Engine.context.NetworkController.getNetworkConfigurationByChainId(chainId); const confirm = () => { onUserAction?.(USER_INTENT.Confirm); // onConfirm?.(); @@ -226,6 +240,7 @@ const PermissionsSummary = ({ + {/* TODO: shouldn't be an avatar group here when its a network switch*/} ); - console.log('ALEX LOGGING: isMultichainVersion1Enabled', isMultichainVersion1Enabled); - return isMultichainVersion1Enabled ? renderPermissionSummary() : renderNetworkSwitchingNotice(); diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 30a125b8e7f..8c425f3ad9f 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -86,11 +86,8 @@ const AccountConnect = (props: AccountConnectProps) => { const styles = createStyles(); const { hostInfo, permissionRequestId } = props.route.params; + // TODO Fix type here const permissionDiffMap = hostInfo?.diff?.permissionDiffMap; - console.log( - 'ALEX LOGGING: permissionDiffMap in accountConnect', - permissionDiffMap, - ); const [isLoading, setIsLoading] = useState(false); const navigation = useNavigation(); @@ -351,7 +348,6 @@ const AccountConnect = (props: AccountConnectProps) => { }, approvedAccounts: selectedAddresses, }; - console.log('ALEX LOGGING: request in handleConnect', request.diff); const connectedAccountLength = selectedAddresses.length; const activeAddress = selectedAddresses[0]; const activeAccountName = getAccountNameWithENS({ diff --git a/app/components/Views/AccountConnect/AccountConnect.types.ts b/app/components/Views/AccountConnect/AccountConnect.types.ts index dd94ca045b5..5a5fd7112d3 100644 --- a/app/components/Views/AccountConnect/AccountConnect.types.ts +++ b/app/components/Views/AccountConnect/AccountConnect.types.ts @@ -16,6 +16,7 @@ export interface AccountConnectParams { permissions: RequestedPermissions; }; permissionRequestId: string; + diff: unknown; } /** diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index f29f78e9405..78a3555fdca 100755 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -394,7 +394,6 @@ const AccountPermissions = (props: AccountPermissionsProps) => { : navigation.navigate('PermissionsManager'), isRenderedAsBottomSheet, }; - console.log('ALEX LOGGING: renderPermissionsSummaryScreen in accountPermissions', permissionsSummaryProps); return ; }, [faviconSource, urlWithProtocol, isRenderedAsBottomSheet, navigation]); diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 80d5158c6f6..a751b6cd426 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -274,27 +274,25 @@ export async function switchToNetwork({ }, }); } else { - console.log('ALEX LOGGING: requestPermissionsIncremental'); - try { - await PermissionController.requestPermissionsIncremental( - { origin }, - { - [PermissionKeys.permittedChains]: { - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ - chainId, - ]), - ], - }, + // try { + await PermissionController.requestPermissionsIncremental( + { origin }, + { + [PermissionKeys.permittedChains]: { + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ + chainId, + ]), + ], }, - ); - } catch (e) { - console.log('ALEX LOGGING: requestPermissionsIncremental error', e); - } + }, + ); + // } catch (e) { + // console.log('ALEX LOGGING: requestPermissionsIncremental error', e); + // } } } } else { - console.log('ALEX LOGGING: requestUserApproval'); const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; await requestUserApproval({ type: 'SWITCH_ETHEREUM_CHAIN', diff --git a/app/selectors/networkController.ts b/app/selectors/networkController.ts index a77ea397b14..254be8afa8b 100644 --- a/app/selectors/networkController.ts +++ b/app/selectors/networkController.ts @@ -96,3 +96,24 @@ export const selectNetworkClientId = createSelector( (networkControllerState: NetworkState) => networkControllerState.selectedNetworkClientId, ); + +// fetches the first network configuration that matches the chainId +// TODO: this is a temporary selector until network controller is updated to v21 +export const selectNetworkNameByChainId = createSelector( + [selectNetworkControllerState, (_: RootState, chainId: Hex) => chainId], + (networkControllerState: NetworkState, chainId: Hex) => { + const builtInNetwork = Object.values(NetworkList).find((network) => { + if ('chainId' in network) { + return network.chainId === chainId; + } + return false; + }); + if (builtInNetwork) { + return builtInNetwork.name; + } + const networkConfig = Object.values( + networkControllerState.networkConfigurations, + ).find((config) => config.chainId === chainId); + return networkConfig?.nickname; + }, +); From 5a1ef60ab76ec877581da78b0a2e41ddabe4e849 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 14:34:53 -0500 Subject: [PATCH 15/29] cleanup --- .../PermissionsSummary/PermissionsSummary.tsx | 61 ++++++++++--------- .../PermissionsSummary.types.ts | 1 + .../Views/AccountConnect/AccountConnect.tsx | 1 + app/core/Permissions/specifications.js | 6 ++ .../RPCMethods/lib/ethereum-chain-utils.js | 43 ++++++++----- 5 files changed, 67 insertions(+), 45 deletions(-) diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx index 568850d50c7..796044f6674 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx @@ -35,55 +35,47 @@ import Routes from '../../../constants/navigation/Routes'; import ButtonIcon, { ButtonIconSizes, } from '../../../component-library/components/Buttons/ButtonIcon'; -import { PermissionKeys } from '../../../core/Permissions/specifications'; -import { selectNetworkNameByChainId } from '../../../selectors/networkController'; -import { useSelector } from 'react-redux'; -import { RootState } from 'app/reducers'; +import { getNetworkImageSource } from '../../../util/networks'; const PermissionsSummary = ({ currentPageInformation, - requestData, + customNetworkInformation, onEdit, onEditNetworks, onBack, + onCancel, + onConfirm, onUserAction, showActionButtons = true, isAlreadyConnected = true, isRenderedAsBottomSheet = true, isDisconnectAllShown = true, + isNetworkSwitch = false, }: PermissionsSummaryProps) => { const { colors } = useTheme(); const { styles } = useStyles(styleSheet, { isRenderedAsBottomSheet }); const { navigate } = useNavigation(); const selectedAccount = useSelectedAccount(); - const permissionDiffMap = requestData?.diff; - let isNetworkSwitch = false; + // if network switch, we get the chain name from the customNetworkInformation + // let chainName = ''; + // let chainImage = ''; + // if (isNetworkSwitch && customNetworkInformation?.chainId) { + const chainName = customNetworkInformation?.chainName; + // @ts-expect-error getNetworkImageSource is not implemented in typescript + const chainImage = getNetworkImageSource({ + chainId: customNetworkInformation?.chainId, + }); + // } - //TODO: This could be true if the dapp is requesting permission for networks... - // need to check how we differentiate between these types of requests on extension - if (permissionDiffMap?.[PermissionKeys.permittedChains]) { - isNetworkSwitch = true; - } - - const chainId = - requestData?.diff?.[PermissionKeys.permittedChains] - ?.restrictNetworkSwitching?.[0]; - - const chainName = useSelector((state: RootState) => - selectNetworkNameByChainId(state, chainId), - ); - // this method is not available until version 21 of the network controller - // const { name: chainName } = - // Engine.context.NetworkController.getNetworkConfigurationByChainId(chainId); const confirm = () => { onUserAction?.(USER_INTENT.Confirm); - // onConfirm?.(); + onConfirm?.(); }; const cancel = () => { onUserAction?.(USER_INTENT.Cancel); - // onCancel?.(); + onCancel?.(); }; const handleEditAccountsButtonPress = () => { @@ -240,12 +232,21 @@ const PermissionsSummary = ({ - {/* TODO: shouldn't be an avatar group here when its a network switch*/} - - - + )} + {!isNetworkSwitch && ( + + + + )} {!isNetworkSwitch && renderEndAccessory()} diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts index c6a97684e56..c076c88c5da 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts @@ -20,5 +20,6 @@ export interface PermissionsSummaryProps { isNetworkSwitch?: boolean; customNetworkInformation: { chainName: string; + chainId: string; }; } diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 8c425f3ad9f..6453f9beac9 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -348,6 +348,7 @@ const AccountConnect = (props: AccountConnectProps) => { }, approvedAccounts: selectedAddresses, }; + console.log('ALEX LOGGING: request in handleConnect', request); const connectedAccountLength = selectedAddresses.length; const activeAddress = selectedAddresses[0]; const activeAccountName = getAccountNameWithENS({ diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index 5442cd40cf5..6d3b0dca2f1 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -216,6 +216,12 @@ export const getPermissionSpecifications = ({ targetName: PermissionKeys.permittedChains, allowedCaveats: [CaveatTypes.restrictNetworkSwitching], factory: (permissionOptions, requestData) => { + if (requestData === undefined) { + return constructPermission({ + ...permissionOptions, + }); + } + if (!requestData.approvedChainIds) { throw new Error( `${PermissionKeys.permittedChains}: No approved networks specified.`, diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index a751b6cd426..04679229fe9 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -188,13 +188,12 @@ export function findExistingNetwork(chainId, networkConfigurations) { const existingEntry = Object.entries(networkConfigurations).find( ([, networkConfiguration]) => networkConfiguration.chainId === chainId, ); - - return ( - existingEntry || [ - existingNetworkDefault?.networkType, - existingNetworkDefault, - ] - ); + if (existingNetworkDefault) { + return [existingNetworkDefault?.networkType, existingNetworkDefault]; + } else if (existingEntry) { + return existingEntry; + } + return; } export async function switchToNetwork({ @@ -274,10 +273,14 @@ export async function switchToNetwork({ }, }); } else { - // try { - await PermissionController.requestPermissionsIncremental( - { origin }, - { + const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; + await requestUserApproval({ + type: 'SWITCH_ETHEREUM_CHAIN', + requestData: { ...requestData, type: requestModalType }, + }); + await PermissionController.grantPermissionsIncremental({ + subject: { origin }, + approvedPermissions: { [PermissionKeys.permittedChains]: { caveats: [ CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ @@ -286,10 +289,20 @@ export async function switchToNetwork({ ], }, }, - ); - // } catch (e) { - // console.log('ALEX LOGGING: requestPermissionsIncremental error', e); - // } + }); + // TODO: eventually switch to using requestPermissionsIncremental once permissions screens are refactored + // await PermissionController.requestPermissionsIncremental( + // { origin }, + // { + // [PermissionKeys.permittedChains]: { + // caveats: [ + // CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ + // chainId, + // ]), + // ], + // }, + // }, + // ); } } } else { From 4557221d288898455ccbe2ab3163c38e897945c3 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 15:59:47 -0500 Subject: [PATCH 16/29] refactor & cleanup --- .../RPCMethods/lib/ethereum-chain-utils.js | 90 +++++++------------ .../wallet_addEthereumChain.test.js | 15 ++++ .../RPCMethods/wallet_switchEthereumChain.js | 1 - .../wallet_switchEthereumChain.test.js | 66 +++++++++++--- 4 files changed, 101 insertions(+), 71 deletions(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 04679229fe9..9168a399610 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -243,76 +243,48 @@ export async function switchToNetwork({ ...analytics, }; - // for some reason this extra step is necessary in test environment + // for some reason this extra step is necessary for accessing the env variable in test environment const chainPermissionsFeatureEnabled = { ...process.env }?.NODE_ENV === 'test' ? { ...process.env }?.MM_CHAIN_PERMISSIONS === '1' : isChainPermissionsFeatureEnabled; - if (chainPermissionsFeatureEnabled) { - const { value: permissionedChainIds } = - getCaveat({ - target: PermissionKeys.permittedChains, - caveatType: CaveatTypes.restrictNetworkSwitching, - }) ?? {}; - if ( - permissionedChainIds === undefined || - !permissionedChainIds.includes(chainId) - ) { - if (isAddNetworkFlow) { - await PermissionController.grantPermissionsIncremental({ - subject: { origin }, - approvedPermissions: { - [PermissionKeys.permittedChains]: { - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ - chainId, - ]), - ], - }, - }, - }); - } else { - const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; - await requestUserApproval({ - type: 'SWITCH_ETHEREUM_CHAIN', - requestData: { ...requestData, type: requestModalType }, - }); - await PermissionController.grantPermissionsIncremental({ - subject: { origin }, - approvedPermissions: { - [PermissionKeys.permittedChains]: { - caveats: [ - CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ - chainId, - ]), - ], - }, - }, - }); - // TODO: eventually switch to using requestPermissionsIncremental once permissions screens are refactored - // await PermissionController.requestPermissionsIncremental( - // { origin }, - // { - // [PermissionKeys.permittedChains]: { - // caveats: [ - // CaveatFactories[CaveatTypes.restrictNetworkSwitching]([ - // chainId, - // ]), - // ], - // }, - // }, - // ); - } - } - } else { - const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; + const { value: permissionedChainIds } = + getCaveat({ + target: PermissionKeys.permittedChains, + caveatType: CaveatTypes.restrictNetworkSwitching, + }) ?? {}; + + const shouldGrantPermissions = + chainPermissionsFeatureEnabled && + (!permissionedChainIds || !permissionedChainIds.includes(chainId)); + + const requestModalType = isAddNetworkFlow ? 'new' : 'switch'; + + const shouldShowRequestModal = + (!isAddNetworkFlow && shouldGrantPermissions) || + !chainPermissionsFeatureEnabled; + + if (shouldShowRequestModal) { await requestUserApproval({ type: 'SWITCH_ETHEREUM_CHAIN', requestData: { ...requestData, type: requestModalType }, }); } + if (shouldGrantPermissions) { + await PermissionController.grantPermissionsIncremental({ + subject: { origin }, + approvedPermissions: { + [PermissionKeys.permittedChains]: { + caveats: [ + CaveatFactories[CaveatTypes.restrictNetworkSwitching]([chainId]), + ], + }, + }, + }); + } + const originHasAccountsPermission = PermissionController.hasPermission( origin, 'eth_accounts', diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index fb2495cc81b..a0675e059dc 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -319,6 +319,21 @@ describe('RPC Method - wallet_addEthereumChain', () => { }); }); + it('should not modify/add permissions', async () => { + const spyOnGrantPermissionsIncremental = jest.spyOn( + Engine.context.PermissionController, + 'grantPermissionsIncremental', + ); + await wallet_addEthereumChain({ + req: { + params: [correctParams], + }, + ...otherOptions, + }); + + expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledTimes(0); + }); + describe('MM_CHAIN_PERMISSIONS is enabled', () => { beforeAll(() => { process.env.MM_CHAIN_PERMISSIONS = 1; diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.js b/app/core/RPCMethods/wallet_switchEthereumChain.js index 08d04a35dfe..de6744d8cd9 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.js @@ -23,7 +23,6 @@ const wallet_switchEthereumChain = async ({ } = Engine.context; const params = req.params?.[0]; const { origin } = req; - console.log('params', params); if (!params || typeof params !== 'object') { throw rpcErrors.invalidParams({ message: `Expected single, object parameter. Received:\n${JSON.stringify( diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.test.js b/app/core/RPCMethods/wallet_switchEthereumChain.test.js index b3d7b2e8bad..a8515db1fbf 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.test.js @@ -16,7 +16,7 @@ jest.mock('../Engine', () => ({ }, PermissionController: { hasPermission: jest.fn().mockReturnValue(true), - requestPermissionsIncremental: jest.fn(), + grantPermissionsIncremental: jest.fn(), getCaveat: jest.fn(), }, SelectedNetworkController: { @@ -74,6 +74,10 @@ const otherOptions = { }; describe('RPC Method - wallet_switchEthereumChain', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should report missing params', async () => { try { await wallet_switchEthereumChain({ @@ -132,6 +136,33 @@ describe('RPC Method - wallet_switchEthereumChain', () => { } }); + it('should should show a modal for user approval and not grant permissions', async () => { + const spyOnGrantPermissionsIncremental = jest.spyOn( + Engine.context.PermissionController, + 'grantPermissionsIncremental', + ); + jest + .spyOn( + Engine.context.SelectedNetworkController, + 'getNetworkClientIdForDomain', + ) + .mockReturnValue('mainnet'); + jest + .spyOn(Engine.context.NetworkController, 'getNetworkClientById') + .mockReturnValue({ configuration: { chainId: '0x1' } }); + jest + .spyOn(Engine.context.PermissionController, 'getCaveat') + .mockReturnValue({ value: [] }); + await wallet_switchEthereumChain({ + req: { + params: [{ chainId: '0x64' }], + }, + ...otherOptions, + }); + expect(otherOptions.requestUserApproval).toHaveBeenCalled(); + expect(spyOnGrantPermissionsIncremental).not.toHaveBeenCalled(); + }); + describe('MM_CHAIN_PERMISSIONS is enabled', () => { beforeAll(() => { process.env.MM_CHAIN_PERMISSIONS = 1; @@ -140,9 +171,9 @@ describe('RPC Method - wallet_switchEthereumChain', () => { process.env.MM_CHAIN_PERMISSIONS = 0; }); it('should not change network permissions and should switch without user approval when chain is already permitted', async () => { - const spyOnRequestPermissionsIncremental = jest.spyOn( + const spyOnGrantPermissionsIncremental = jest.spyOn( Engine.context.PermissionController, - 'requestPermissionsIncremental', + 'grantPermissionsIncremental', ); jest .spyOn( @@ -168,18 +199,17 @@ describe('RPC Method - wallet_switchEthereumChain', () => { ...otherOptions, }); - expect(spyOnRequestPermissionsIncremental).not.toHaveBeenCalled(); - expect(otherOptions.requestUserApproval).not.toHaveBeenCalled(); + expect(spyOnGrantPermissionsIncremental).not.toHaveBeenCalled(); expect(spyOnSetActiveNetwork).toHaveBeenCalledWith( 'test-network-configuration-id', ); }); it('should add network permission and should switch with user approval when requested chain is not permitted', async () => { - const spyOnRequestPermissionsIncremental = jest.spyOn( + const spyOnGrantPermissionsIncremental = jest.spyOn( Engine.context.PermissionController, - 'requestPermissionsIncremental', + 'grantPermissionsIncremental', ); jest .spyOn( @@ -200,13 +230,27 @@ describe('RPC Method - wallet_switchEthereumChain', () => { await wallet_switchEthereumChain({ req: { params: [{ chainId: '0x64' }], + origin: 'https://test.com', }, ...otherOptions, }); - - // this request shows a permissions request to the user - // which, if approved, adds an endowmnet:permittedChains permission - expect(spyOnRequestPermissionsIncremental).toHaveBeenCalledTimes(1); + expect(otherOptions.requestUserApproval).toHaveBeenCalled(); + expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledTimes(1); + expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledWith({ + approvedPermissions: { + 'endowment:permitted-chains': { + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0x64'], + }, + ], + }, + }, + subject: { + origin: 'https://test.com', + }, + }); expect(spyOnSetActiveNetwork).toHaveBeenCalledWith( 'test-network-configuration-id', ); From dc8e89639ef1f4fe3741760dce0a750b76a7ccef Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 16:19:42 -0500 Subject: [PATCH 17/29] lint --- .../PermissionsSummary/PermissionsSummary.tsx | 20 +++++++++---------- .../PermissionsSummary.types.ts | 3 +-- .../Views/AccountConnect/AccountConnect.tsx | 10 +--------- .../AccountConnect/AccountConnect.types.ts | 1 - 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx index 796044f6674..e084f3ffdc9 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; import StyledButton from '../StyledButton'; -import { SafeAreaView, TouchableOpacity, View } from 'react-native'; +import { ImageSourcePropType, SafeAreaView, TouchableOpacity, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { strings } from '../../../../locales/i18n'; import { useTheme } from '../../../util/theme'; @@ -58,15 +58,15 @@ const PermissionsSummary = ({ const selectedAccount = useSelectedAccount(); // if network switch, we get the chain name from the customNetworkInformation - // let chainName = ''; - // let chainImage = ''; - // if (isNetworkSwitch && customNetworkInformation?.chainId) { - const chainName = customNetworkInformation?.chainName; - // @ts-expect-error getNetworkImageSource is not implemented in typescript - const chainImage = getNetworkImageSource({ - chainId: customNetworkInformation?.chainId, - }); - // } + let chainName = ''; + let chainImage: ImageSourcePropType; + if (isNetworkSwitch && customNetworkInformation?.chainId) { + chainName = customNetworkInformation?.chainName; + // @ts-expect-error getNetworkImageSource is not implemented in typescript + chainImage = getNetworkImageSource({ + chainId: customNetworkInformation?.chainId, + }); + } const confirm = () => { onUserAction?.(USER_INTENT.Confirm); diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts index c076c88c5da..1be07727a7d 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts @@ -6,7 +6,6 @@ export interface PermissionsSummaryProps { icon: string | { uri: string }; url: string; }; - requestData: unknown; onEdit?: () => void; onEditNetworks?: () => void; onBack?: () => void; @@ -18,7 +17,7 @@ export interface PermissionsSummaryProps { isRenderedAsBottomSheet?: boolean; isDisconnectAllShown?: boolean; isNetworkSwitch?: boolean; - customNetworkInformation: { + customNetworkInformation?: { chainName: string; chainId: string; }; diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 6453f9beac9..6c20c1679f5 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -85,10 +85,6 @@ const AccountConnect = (props: AccountConnectProps) => { const { colors } = useTheme(); const styles = createStyles(); const { hostInfo, permissionRequestId } = props.route.params; - - // TODO Fix type here - const permissionDiffMap = hostInfo?.diff?.permissionDiffMap; - const [isLoading, setIsLoading] = useState(false); const navigation = useNavigation(); const { trackEvent } = useMetrics(); @@ -348,7 +344,6 @@ const AccountConnect = (props: AccountConnectProps) => { }, approvedAccounts: selectedAddresses, }; - console.log('ALEX LOGGING: request in handleConnect', request); const connectedAccountLength = selectedAddresses.length; const activeAddress = selectedAddresses[0]; const activeAccountName = getAccountNameWithENS({ @@ -568,12 +563,9 @@ const AccountConnect = (props: AccountConnectProps) => { setScreen(AccountConnectScreens.MultiConnectNetworkSelector), onUserAction: setUserIntent, isAlreadyConnected: false, - requestData: { - diff: permissionDiffMap, - }, }; return ; - }, [faviconSource, urlWithProtocol, permissionDiffMap]); + }, [faviconSource, urlWithProtocol]); const renderSingleConnectSelectorScreen = useCallback( () => ( diff --git a/app/components/Views/AccountConnect/AccountConnect.types.ts b/app/components/Views/AccountConnect/AccountConnect.types.ts index 5a5fd7112d3..dd94ca045b5 100644 --- a/app/components/Views/AccountConnect/AccountConnect.types.ts +++ b/app/components/Views/AccountConnect/AccountConnect.types.ts @@ -16,7 +16,6 @@ export interface AccountConnectParams { permissions: RequestedPermissions; }; permissionRequestId: string; - diff: unknown; } /** From 6323cde690983058edea1a02bf402705583ac69b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 16:25:04 -0500 Subject: [PATCH 18/29] cleanup --- app/core/RPCMethods/wallet_addEthereumChain.js | 4 ++-- app/core/RPCMethods/wallet_switchEthereumChain.test.js | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index e1a2e288566..9dc9674957d 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -142,8 +142,8 @@ const wallet_addEthereumChain = async ({ }, }, { - referrer: 'Custom Network API', - source: 'Custom Network API', + referrer: 'ignored', + source: 'ignored', }, ); diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.test.js b/app/core/RPCMethods/wallet_switchEthereumChain.test.js index a8515db1fbf..a550d938600 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.test.js @@ -1,10 +1,6 @@ import wallet_switchEthereumChain from './wallet_switchEthereumChain'; import Engine from '../Engine'; -import { NetworkController } from '@metamask/network-controller'; -import { CaveatFactories, PermissionKeys } from '../Permissions/specifications'; -import { CaveatTypes } from '../Permissions/constants'; -// jest.mock('@metamask/network-controller');s jest.mock('../Engine', () => ({ context: { NetworkController: { From a9f5aace8499c2f038e2c0e16d2444eb2cb76b31 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 17:13:19 -0500 Subject: [PATCH 19/29] modify snapshot --- .../PermissionsSummary.test.tsx | 18 + .../PermissionsSummary/PermissionsSummary.tsx | 42 +- .../PermissionsSummary.test.tsx.snap | 546 ++++++++++++++++-- 3 files changed, 535 insertions(+), 71 deletions(-) diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx index ebc79900213..5ce8f2a4f7b 100644 --- a/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx +++ b/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx @@ -29,6 +29,24 @@ const mockInitialState = { }; describe('PermissionsSummary', () => { + it('should render correctly for network switch', () => { + const { toJSON } = renderWithProvider( + , + { state: mockInitialState }, + ); + expect(toJSON()).toMatchSnapshot(); + }); it('should render correctly', () => { const { toJSON } = renderWithProvider( - - - - {strings('permissions.requesting_for')} - - - {chainName} - - - {isNetworkSwitch && ( - + <> + + + + {strings('permissions.requesting_for')} + + + {chainName} + + + + + )} {!isNetworkSwitch && ( @@ -271,6 +278,7 @@ const PermissionsSummary = ({ })} + {/*TODO These should be conditional upon which permissions are being requested*/} {!isNetworkSwitch && renderAccountPermissionsRequestInfoCard()} {renderNetworkPermissionsRequestInfoCard()} diff --git a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap index 07ce968d48c..a103fb4e77f 100644 --- a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap +++ b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap @@ -500,60 +500,6 @@ exports[`PermissionsSummary should render correctly 1`] = ` } } > - - - - Requesting for - - - Ethereum Main Network - - - `; + +exports[`PermissionsSummary should render correctly for network switch 1`] = ` + + + + + + + + + + a + + + + + + + + + app.uniswap.org wants to: + + + + + + + + + + Use your enabled networks + + + + + + Requesting for + + + Sepolia + + + + + + S + + + + + + + + + + + + + Disconnect all + + + + + + + Cancel + + + + + Confirm + + + + + + +`; From 23f7d06aa5dc06ec3f926d3e21829f83b5e8cf72 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 17:41:05 -0500 Subject: [PATCH 20/29] final cleanup? --- app/core/RPCMethods/lib/ethereum-chain-utils.js | 3 +-- app/core/RPCMethods/wallet_addEthereumChain.js | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 9168a399610..5645d7d5b0e 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -225,8 +225,7 @@ export async function switchToNetwork({ return undefined; }; - const { networkConfigurationId, networkConfiguration } = network; - + const [networkConfigurationId, networkConfiguration] = network; const requestData = { rpcUrl: networkConfiguration.rpcUrl, chainId, diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index 9dc9674957d..b59f314afad 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -57,7 +57,6 @@ const wallet_addEthereumChain = async ({ const networkConfigurations = selectNetworkConfigurations(store.getState()); const existingNetwork = findExistingNetwork(chainId, networkConfigurations); - if (existingNetwork) { const analyticsParams = await switchToNetwork({ network: existingNetwork, @@ -154,11 +153,7 @@ const wallet_addEthereumChain = async ({ ...analytics, }); - const network = { - networkConfigurationId, - networkConfiguration: requestData, - }; - + const network = [networkConfigurationId, requestData]; const analyticsParams = await switchToNetwork({ network, chainId, @@ -171,7 +166,7 @@ const wallet_addEthereumChain = async ({ requestUserApproval, analytics, origin, - isAddNetworkFlow: true, // isAddNetworkFlow + isAddNetworkFlow: true, }); MetaMetrics.getInstance().trackEvent( From d87da01d5daf8282bb551a02e701e53634537759 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Oct 2024 21:55:45 -0500 Subject: [PATCH 21/29] fix --- app/core/RPCMethods/wallet_switchEthereumChain.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.js b/app/core/RPCMethods/wallet_switchEthereumChain.js index de6744d8cd9..f9b2d6b7d3c 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.js @@ -59,10 +59,7 @@ const wallet_switchEthereumChain = async ({ return; } const analyticsParams = await switchToNetwork({ - network: { - networkConfigurationId: existingNetwork?.[0], - networkConfiguration: existingNetwork?.[1], - }, + network: existingNetwork, chainId: _chainId, controllers: { CurrencyRateController, From 3feefbb0fbc1f31160c1110a7cd1781f75966056 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 10 Oct 2024 11:31:48 -0500 Subject: [PATCH 22/29] add test --- .../wallet_addEthereumChain.test.js | 100 ++++++++++++++++++ .../wallet_switchEthereumChain.test.js | 10 +- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index a0675e059dc..3c61b9576f4 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -15,6 +15,17 @@ const correctParams = { rpcUrls: ['https://rpc.gnosischain.com'], }; +const existingNetworkConfiguration = { + id: 'test-network-configuration-id', + chainId: '0x2', + rpcUrl: 'https://rpc.test-chain.com', + ticker: 'TST', + nickname: 'Test Chain', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.test-chain.com', + }, +}; + jest.mock('../Engine', () => ({ init: () => mockEngine.init({}), context: { @@ -59,6 +70,10 @@ jest.mock('../../store', () => ({ blockExplorerUrl: 'https://etherscan.com', }, }, + [existingNetworkConfiguration.id]: { + id: 'test-network-configuration', + ...existingNetworkConfiguration, + }, }, }, }, @@ -334,6 +349,91 @@ describe('RPC Method - wallet_addEthereumChain', () => { expect(spyOnGrantPermissionsIncremental).toHaveBeenCalledTimes(0); }); + it('should correctly add and switch to a new chain when chain is not already in wallet state ', async () => { + const spyOnUpsertNetworkConfiguration = jest.spyOn( + Engine.context.NetworkController, + 'upsertNetworkConfiguration', + ); + const spyOnSetActiveNetwork = jest.spyOn( + Engine.context.NetworkController, + 'setActiveNetwork', + ); + const spyOnUpdateExchangeRate = jest.spyOn( + Engine.context.CurrencyRateController, + 'updateExchangeRate', + ); + + await wallet_addEthereumChain({ + req: { + params: [correctParams], + origin: 'https://example.com', + }, + ...otherOptions, + }); + + expect(spyOnUpsertNetworkConfiguration).toHaveBeenCalledTimes(1); + expect(spyOnUpsertNetworkConfiguration).toHaveBeenCalledWith( + expect.objectContaining({ + chainId: correctParams.chainId, + rpcUrl: correctParams.rpcUrls[0], + ticker: correctParams.nativeCurrency.symbol, + nickname: correctParams.chainName, + }), + expect.any(Object), + ); + expect(spyOnSetActiveNetwork).toHaveBeenCalledTimes(1); + expect(spyOnUpdateExchangeRate).toHaveBeenCalledTimes(1); + }); + + it('should not add a networkConfiguration that has a chainId that already exists in wallet state, and should switch to the existing network', async () => { + const spyOnUpsertNetworkConfiguration = jest.spyOn( + Engine.context.NetworkController, + 'upsertNetworkConfiguration', + ); + const spyOnSetActiveNetwork = jest.spyOn( + Engine.context.NetworkController, + 'setActiveNetwork', + ); + const spyOnUpdateExchangeRate = jest.spyOn( + Engine.context.CurrencyRateController, + 'updateExchangeRate', + ); + + const existingNetworkConfiguration = { + id: 'test-network-configuration-id', + chainId: '0x2', + rpcUrl: 'https://rpc.test-chain.com', + ticker: 'TST', + nickname: 'Test Chain', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.test-chain.com', + }, + }; + + const existingParams = { + chainId: existingNetworkConfiguration.chainId, + rpcUrls: ['https://different-rpc-url.com'], + chainName: existingNetworkConfiguration.nickname, + nativeCurrency: { + name: existingNetworkConfiguration.ticker, + symbol: existingNetworkConfiguration.ticker, + decimals: 18, + }, + }; + + await wallet_addEthereumChain({ + req: { + params: [existingParams], + origin: 'https://example.com', + }, + ...otherOptions, + }); + + expect(spyOnUpsertNetworkConfiguration).not.toHaveBeenCalled(); + expect(spyOnSetActiveNetwork).toHaveBeenCalledTimes(1); + expect(spyOnUpdateExchangeRate).toHaveBeenCalledTimes(1); + }); + describe('MM_CHAIN_PERMISSIONS is enabled', () => { beforeAll(() => { process.env.MM_CHAIN_PERMISSIONS = 1; diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.test.js b/app/core/RPCMethods/wallet_switchEthereumChain.test.js index a550d938600..ba54456ea3a 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.test.js @@ -146,9 +146,10 @@ describe('RPC Method - wallet_switchEthereumChain', () => { jest .spyOn(Engine.context.NetworkController, 'getNetworkClientById') .mockReturnValue({ configuration: { chainId: '0x1' } }); - jest - .spyOn(Engine.context.PermissionController, 'getCaveat') - .mockReturnValue({ value: [] }); + const spyOnSetActiveNetwork = jest.spyOn( + Engine.context.NetworkController, + 'setActiveNetwork', + ); await wallet_switchEthereumChain({ req: { params: [{ chainId: '0x64' }], @@ -157,6 +158,9 @@ describe('RPC Method - wallet_switchEthereumChain', () => { }); expect(otherOptions.requestUserApproval).toHaveBeenCalled(); expect(spyOnGrantPermissionsIncremental).not.toHaveBeenCalled(); + expect(spyOnSetActiveNetwork).toHaveBeenCalledWith( + 'test-network-configuration-id', + ); }); describe('MM_CHAIN_PERMISSIONS is enabled', () => { From 63e47d58a2164a31ac6d6569d03d54b285a01e94 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 10 Oct 2024 12:40:27 -0500 Subject: [PATCH 23/29] fix test --- app/core/RPCMethods/wallet_addEthereumChain.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index 3c61b9576f4..32adb42e6f0 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -87,6 +87,7 @@ describe('RPC Method - wallet_addEthereumChain', () => { let otherOptions; beforeEach(() => { + jest.clearAllMocks(); otherOptions = { res: {}, addCustomNetworkRequest: {}, From e5ee3c361bd2004d9fa1b2846b477a75c597625c Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 14 Oct 2024 15:28:52 -0500 Subject: [PATCH 24/29] fix up merge commit --- .../RPCMethods/wallet_addEthereumChain.js | 21 ++++--- .../wallet_addEthereumChain.test.js | 57 ++++++++++-------- .../wallet_switchEthereumChain.test.js | 60 ++++++++++++------- app/selectors/networkController.ts | 6 +- app/util/test/network.ts | 1 - 5 files changed, 87 insertions(+), 58 deletions(-) diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index 873299d0ab6..f66e943392a 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -3,7 +3,10 @@ import { ChainId } from '@metamask/controller-utils'; import Engine from '../Engine'; import { providerErrors, rpcErrors } from '@metamask/rpc-errors'; import { MetaMetricsEvents, MetaMetrics } from '../../core/Analytics'; -import { selectNetworkConfigurations } from '../../selectors/networkController'; +import { + selectChainId, + selectNetworkConfigurations, +} from '../../selectors/networkController'; import { store } from '../../store'; import checkSafeNetwork from './networkChecker.util'; import { @@ -70,7 +73,6 @@ const wallet_addEthereumChain = async ({ const existingEntry = Object.entries(networkConfigurations).find( ([, networkConfiguration]) => networkConfiguration.chainId === chainId, ); - if (existingEntry) { const [chainId, networkConfiguration] = existingEntry; const currentChainId = selectChainId(store.getState()); @@ -115,14 +117,15 @@ const wallet_addEthereumChain = async ({ ); const analyticsParams = { - chain_id: getDecimalChainId(_chainId), + chain_id: getDecimalChainId(chainId), source: 'Custom Network API', symbol: networkConfiguration.ticker, ...analytics, }; + const network = [clonedNetwork.id, clonedNetwork]; await switchToNetwork({ - network: existingNetwork, + network, chainId, controllers: { CurrencyRateController, @@ -192,7 +195,7 @@ const wallet_addEthereumChain = async ({ ); throw providerErrors.userRejectedRequest(); } - const NetworkConfiguration = await NetworkController.addNetwork({ + const networkConfiguration = await NetworkController.addNetwork({ chainId, blockExplorerUrls: [firstValidBlockExplorerUrl], defaultRpcEndpointIndex: 0, @@ -216,11 +219,11 @@ const wallet_addEthereumChain = async ({ }); const { networkClientId } = - NetworkConfiguration?.rpcEndpoints?.[ - NetworkConfiguration.defaultRpcEndpointIndex - ] ?? {}; + networkConfiguration?.rpcEndpoints?.[ + networkConfiguration.defaultRpcEndpointIndex + ] ?? {}; - const network = [networkClientId, requestData]; + const network = [networkClientId, networkConfiguration]; const analyticsParams = await switchToNetwork({ network, chainId, diff --git a/app/core/RPCMethods/wallet_addEthereumChain.test.js b/app/core/RPCMethods/wallet_addEthereumChain.test.js index eb1f507c624..f84e2ebcd29 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.test.js @@ -34,6 +34,7 @@ jest.mock('../Engine', () => ({ setActiveNetwork: jest.fn(), upsertNetworkConfiguration: jest.fn(), addNetwork: jest.fn(), + updateNetwork: jest.fn(), }, CurrencyRateController: { updateExchangeRate: jest.fn(), @@ -59,7 +60,7 @@ jest.mock('../../store', () => ({ engine: { backgroundState: { NetworkController: { - ...mockNetworkState([ + ...mockNetworkState( { chainId: '0x1', id: 'Mainnet', @@ -69,7 +70,7 @@ jest.mock('../../store', () => ({ { ...existingNetworkConfiguration, }, - ]), + ), }, }, }, @@ -99,6 +100,8 @@ describe('RPC Method - wallet_addEthereumChain', () => { mockFetch = jest.fn().mockImplementation(async (url) => { if (url === 'https://rpc.gnosischain.com') { return { json: () => Promise.resolve({ result: '0x64' }) }; + } else if (url === 'https://different-rpc-url.com') { + return { json: () => Promise.resolve({ result: '0x2' }) }; } else if (url === 'https://chainid.network/chains.json') { return { json: () => @@ -290,6 +293,14 @@ describe('RPC Method - wallet_addEthereumChain', () => { describe('Approval Flow', () => { it('should start and end a new approval flow if chain does not already exist', async () => { + jest + .spyOn(Engine.context.NetworkController, 'addNetwork') + .mockResolvedValue({ + id: '1', + chainId: '0x64', + rpcEndpoints: [correctParams.rpcUrls[0]], + defaultRpcEndpointIndex: 0, + }); await wallet_addEthereumChain({ req: { params: [correctParams], @@ -346,10 +357,15 @@ describe('RPC Method - wallet_addEthereumChain', () => { }); it('should correctly add and switch to a new chain when chain is not already in wallet state ', async () => { - const spyOnUpsertNetworkConfiguration = jest.spyOn( - Engine.context.NetworkController, - 'upsertNetworkConfiguration', - ); + const spyOnAddNetwork = jest + .spyOn(Engine.context.NetworkController, 'addNetwork') + .mockResolvedValue({ + id: '1', + chainId: '0x64', + rpcEndpoints: [correctParams.rpcUrls[0]], + defaultRpcEndpointIndex: 0, + }); + const spyOnSetActiveNetwork = jest.spyOn( Engine.context.NetworkController, 'setActiveNetwork', @@ -367,25 +383,25 @@ describe('RPC Method - wallet_addEthereumChain', () => { ...otherOptions, }); - expect(spyOnUpsertNetworkConfiguration).toHaveBeenCalledTimes(1); - expect(spyOnUpsertNetworkConfiguration).toHaveBeenCalledWith( + expect(spyOnAddNetwork).toHaveBeenCalledTimes(1); + expect(spyOnAddNetwork).toHaveBeenCalledWith( expect.objectContaining({ chainId: correctParams.chainId, - rpcUrl: correctParams.rpcUrls[0], - ticker: correctParams.nativeCurrency.symbol, - nickname: correctParams.chainName, + blockExplorerUrls: correctParams.blockExplorerUrls, + nativeCurrency: correctParams.nativeCurrency.symbol, + name: correctParams.chainName, }), - expect.any(Object), ); expect(spyOnSetActiveNetwork).toHaveBeenCalledTimes(1); expect(spyOnUpdateExchangeRate).toHaveBeenCalledTimes(1); }); it('should not add a networkConfiguration that has a chainId that already exists in wallet state, and should switch to the existing network', async () => { - const spyOnUpsertNetworkConfiguration = jest.spyOn( + const spyOnAddNetwork = jest.spyOn( Engine.context.NetworkController, - 'upsertNetworkConfiguration', + 'addNetwork', ); + const spyOnSetActiveNetwork = jest.spyOn( Engine.context.NetworkController, 'setActiveNetwork', @@ -395,17 +411,6 @@ describe('RPC Method - wallet_addEthereumChain', () => { 'updateExchangeRate', ); - const existingNetworkConfiguration = { - id: 'test-network-configuration-id', - chainId: '0x2', - rpcUrl: 'https://rpc.test-chain.com', - ticker: 'TST', - nickname: 'Test Chain', - rpcPrefs: { - blockExplorerUrl: 'https://explorer.test-chain.com', - }, - }; - const existingParams = { chainId: existingNetworkConfiguration.chainId, rpcUrls: ['https://different-rpc-url.com'], @@ -425,7 +430,7 @@ describe('RPC Method - wallet_addEthereumChain', () => { ...otherOptions, }); - expect(spyOnUpsertNetworkConfiguration).not.toHaveBeenCalled(); + expect(spyOnAddNetwork).not.toHaveBeenCalled(); expect(spyOnSetActiveNetwork).toHaveBeenCalledTimes(1); expect(spyOnUpdateExchangeRate).toHaveBeenCalledTimes(1); }); diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.test.js b/app/core/RPCMethods/wallet_switchEthereumChain.test.js index ba54456ea3a..33b6e32e972 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.test.js @@ -1,6 +1,17 @@ import wallet_switchEthereumChain from './wallet_switchEthereumChain'; import Engine from '../Engine'; +import { mockNetworkState } from '../../util/test/network'; +const existingNetworkConfiguration = { + id: 'test-network-configuration-id', + chainId: '0x64', + rpcUrl: 'https://rpc.test-chain.com', + ticker: 'ETH', + nickname: 'Gnosis Chain', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.test-chain.com', + }, +}; jest.mock('../Engine', () => ({ context: { NetworkController: { @@ -28,30 +39,39 @@ jest.mock('../../store', () => ({ engine: { backgroundState: { NetworkController: { - selectedNetworkClientId: 'mainnet', - networksMetadata: {}, - networkConfigurations: { - mainnet: { - id: 'mainnet', - rpcUrl: 'https://mainnet.infura.io/v3', + // selectedNetworkClientId: 'mainnet', + // networksMetadata: {}, + ...mockNetworkState( + { chainId: '0x1', + id: 'Mainnet', + nickname: 'Mainnet', ticker: 'ETH', - nickname: 'Sepolia network', - rpcPrefs: { - blockExplorerUrl: 'https://etherscan.com', - }, }, - 'test-network-configuration-id': { - id: 'test-network-configuration-id', - rpcUrl: 'https://gnosis-chain.infura.io/v3', - chainId: '0x64', - ticker: 'ETH', - nickname: 'Gnosis Chain', - rpcPrefs: { - blockExplorerUrl: 'https://gnosisscan.com', - }, + { + ...existingNetworkConfiguration, }, - }, + ), + // mainnet: { + // id: 'mainnet', + // rpcUrl: 'https://mainnet.infura.io/v3', + // chainId: '0x1', + // ticker: 'ETH', + // nickname: 'Sepolia network', + // rpcPrefs: { + // blockExplorerUrl: 'https://etherscan.com', + // }, + // }, + // 'test-network-configuration-id': { + // id: 'test-network-configuration-id', + // rpcUrl: 'https://gnosis-chain.infura.io/v3', + // chainId: '0x64', + // ticker: 'ETH', + // nickname: 'Gnosis Chain', + // rpcPrefs: { + // blockExplorerUrl: 'https://gnosisscan.com', + // }, + // }, }, }, }, diff --git a/app/selectors/networkController.ts b/app/selectors/networkController.ts index 0bd39d21db5..b4d94e43c8f 100644 --- a/app/selectors/networkController.ts +++ b/app/selectors/networkController.ts @@ -6,10 +6,12 @@ import { NetworkConfiguration, NetworkState, RpcEndpointType, + Hex, } from '@metamask/network-controller'; import { createDeepEqualSelector } from './util'; import { NETWORKS_CHAIN_ID } from '../constants/network'; import { InfuraNetworkType } from '@metamask/controller-utils'; +import { NetworkList } from '../util/networks'; interface InfuraRpcEndpoint { name?: string; @@ -171,9 +173,9 @@ export const selectNetworkNameByChainId = createSelector( if (builtInNetwork) { return builtInNetwork.name; } - const networkConfig = Object.values( + const networkConfig: NetworkConfiguration = Object.values( networkControllerState.networkConfigurations, - ).find((config) => config.chainId === chainId); + ).find((config: NetworkConfiguration) => config.chainId === chainId); return networkConfig?.nickname; }, ); diff --git a/app/util/test/network.ts b/app/util/test/network.ts index 8f8520a6fab..33fc493f727 100644 --- a/app/util/test/network.ts +++ b/app/util/test/network.ts @@ -40,7 +40,6 @@ export const mockNetworkState = ( 'rpcUrl' in network ? network.rpcUrl : `https://localhost/rpc/${network.chainId}`; - return { chainId: network.chainId, blockExplorerUrls: blockExplorer ? [blockExplorer] : [], From 59de68faa5ccbca65d758c49fbcf46e066e09cf8 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 14 Oct 2024 16:09:37 -0500 Subject: [PATCH 25/29] update snapshot --- .../PermissionsSummary.test.tsx.snap | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap index a103fb4e77f..b6d0fea2047 100644 --- a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap +++ b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap @@ -1289,9 +1289,8 @@ exports[`PermissionsSummary should render correctly for network switch 1`] = ` style={ { "alignItems": "center", - "backgroundColor": "#f2f4f6", + "backgroundColor": "#ffffff", "borderRadius": 8, - "borderWidth": 1, "height": 16, "justifyContent": "center", "overflow": "hidden", @@ -1299,21 +1298,24 @@ exports[`PermissionsSummary should render correctly for network switch 1`] = ` } } > - - S - + testID="network-avatar-image" + /> From 97dbf9122e70d4d8e59c047ce4e0c9d6139d724d Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 14 Oct 2024 16:52:47 -0500 Subject: [PATCH 26/29] revert unnecessary selector --- app/selectors/networkController.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/selectors/networkController.ts b/app/selectors/networkController.ts index b4d94e43c8f..d55ff91e0ea 100644 --- a/app/selectors/networkController.ts +++ b/app/selectors/networkController.ts @@ -6,12 +6,10 @@ import { NetworkConfiguration, NetworkState, RpcEndpointType, - Hex, } from '@metamask/network-controller'; import { createDeepEqualSelector } from './util'; import { NETWORKS_CHAIN_ID } from '../constants/network'; import { InfuraNetworkType } from '@metamask/controller-utils'; -import { NetworkList } from '../util/networks'; interface InfuraRpcEndpoint { name?: string; @@ -159,23 +157,3 @@ export const selectNetworkClientId = createSelector( networkControllerState.selectedNetworkClientId, ); -// fetches the first network configuration that matches the chainId -// TODO: this is a temporary selector until network controller is updated to v21 -export const selectNetworkNameByChainId = createSelector( - [selectNetworkControllerState, (_: RootState, chainId: Hex) => chainId], - (networkControllerState: NetworkState, chainId: Hex) => { - const builtInNetwork = Object.values(NetworkList).find((network) => { - if ('chainId' in network) { - return network.chainId === chainId; - } - return false; - }); - if (builtInNetwork) { - return builtInNetwork.name; - } - const networkConfig: NetworkConfiguration = Object.values( - networkControllerState.networkConfigurations, - ).find((config: NetworkConfiguration) => config.chainId === chainId); - return networkConfig?.nickname; - }, -); From 663b31548bc7f576092a66bc5050c359da55601a Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 15 Oct 2024 09:40:10 -0500 Subject: [PATCH 27/29] fix preloaded networks issue --- app/core/RPCMethods/lib/ethereum-chain-utils.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js index 19327ada48a..9769933d577 100644 --- a/app/core/RPCMethods/lib/ethereum-chain-utils.js +++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js @@ -5,7 +5,6 @@ import { jsonRpcRequest } from '../../../util/jsonRpcRequest'; import { getDecimalChainId, isPrefixedFormattedHexString, - getDefaultNetworkByChainId, isChainPermissionsFeatureEnabled, } from '../../../util/networks'; import { @@ -184,13 +183,10 @@ export async function validateRpcEndpoint(rpcUrl, chainId) { } export function findExistingNetwork(chainId, networkConfigurations) { - const existingNetworkDefault = getDefaultNetworkByChainId(chainId); const existingEntry = Object.entries(networkConfigurations).find( ([, networkConfiguration]) => networkConfiguration.chainId === chainId, ); - if (existingNetworkDefault) { - return [existingNetworkDefault?.networkType, existingNetworkDefault]; - } else if (existingEntry) { + if (existingEntry) { const [, networkConfiguration] = existingEntry; const networkConfigurationId = networkConfiguration.rpcEndpoints[ @@ -238,6 +234,7 @@ export async function switchToNetwork({ ], chainId, chainName: + networkConfiguration.name || networkConfiguration.chainName || networkConfiguration.nickname || networkConfiguration.shortName, From f103c440630aecec8ed364124abe87bd8736f30a Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 15 Oct 2024 13:09:49 -0500 Subject: [PATCH 28/29] another cleanup --- app/core/RPCMethods/wallet_addEthereumChain.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js index f66e943392a..6bbc661fce7 100644 --- a/app/core/RPCMethods/wallet_addEthereumChain.js +++ b/app/core/RPCMethods/wallet_addEthereumChain.js @@ -123,7 +123,12 @@ const wallet_addEthereumChain = async ({ ...analytics, }; - const network = [clonedNetwork.id, clonedNetwork]; + const { networkClientId } = + networkConfiguration.rpcEndpoints[ + networkConfiguration.defaultRpcEndpointIndex + ]; + + const network = [networkClientId, clonedNetwork]; await switchToNetwork({ network, chainId, @@ -136,6 +141,7 @@ const wallet_addEthereumChain = async ({ requestUserApproval, analytics, origin, + isAddNetworkFlow: true, }); MetaMetrics.getInstance().trackEvent( From 9e0e64348df72ce6265eadb493d34bddc2a04900 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 17 Oct 2024 19:08:56 -0500 Subject: [PATCH 29/29] remove unneeded comments --- .../wallet_switchEthereumChain.test.js | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/app/core/RPCMethods/wallet_switchEthereumChain.test.js b/app/core/RPCMethods/wallet_switchEthereumChain.test.js index 33b6e32e972..032a8a6ca5d 100644 --- a/app/core/RPCMethods/wallet_switchEthereumChain.test.js +++ b/app/core/RPCMethods/wallet_switchEthereumChain.test.js @@ -39,8 +39,6 @@ jest.mock('../../store', () => ({ engine: { backgroundState: { NetworkController: { - // selectedNetworkClientId: 'mainnet', - // networksMetadata: {}, ...mockNetworkState( { chainId: '0x1', @@ -52,26 +50,6 @@ jest.mock('../../store', () => ({ ...existingNetworkConfiguration, }, ), - // mainnet: { - // id: 'mainnet', - // rpcUrl: 'https://mainnet.infura.io/v3', - // chainId: '0x1', - // ticker: 'ETH', - // nickname: 'Sepolia network', - // rpcPrefs: { - // blockExplorerUrl: 'https://etherscan.com', - // }, - // }, - // 'test-network-configuration-id': { - // id: 'test-network-configuration-id', - // rpcUrl: 'https://gnosis-chain.infura.io/v3', - // chainId: '0x64', - // ticker: 'ETH', - // nickname: 'Gnosis Chain', - // rpcPrefs: { - // blockExplorerUrl: 'https://gnosisscan.com', - // }, - // }, }, }, },