mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
This reverts commit f09ab8889148c406551dea1643966e3331fde4aa, reversing changes made to effc761e0ee4ea7ffb77f275b5ed650a7098d6f8. This is being temporarily reverted to make it easier to release an urgent fix for v10.15.1.
315 lines
8.5 KiB
JavaScript
315 lines
8.5 KiB
JavaScript
import { ethErrors, errorCodes } from 'eth-rpc-errors';
|
|
import validUrl from 'valid-url';
|
|
import { omit } from 'lodash';
|
|
import { MESSAGE_TYPE } from '../../../../../shared/constants/app';
|
|
import { EVENT } from '../../../../../shared/constants/metametrics';
|
|
import {
|
|
isPrefixedFormattedHexString,
|
|
isSafeChainId,
|
|
} from '../../../../../shared/modules/network.utils';
|
|
import { jsonRpcRequest } from '../../../../../shared/modules/rpc.utils';
|
|
import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../../shared/constants/network';
|
|
|
|
const addEthereumChain = {
|
|
methodNames: [MESSAGE_TYPE.ADD_ETHEREUM_CHAIN],
|
|
implementation: addEthereumChainHandler,
|
|
hookNames: {
|
|
addCustomRpc: true,
|
|
getCurrentChainId: true,
|
|
findCustomRpcBy: true,
|
|
updateRpcTarget: true,
|
|
requestUserApproval: true,
|
|
sendMetrics: true,
|
|
},
|
|
};
|
|
export default addEthereumChain;
|
|
|
|
async function addEthereumChainHandler(
|
|
req,
|
|
res,
|
|
_next,
|
|
end,
|
|
{
|
|
addCustomRpc,
|
|
getCurrentChainId,
|
|
findCustomRpcBy,
|
|
updateRpcTarget,
|
|
requestUserApproval,
|
|
sendMetrics,
|
|
},
|
|
) {
|
|
if (!req.params?.[0] || typeof req.params[0] !== 'object') {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected single, object parameter. Received:\n${JSON.stringify(
|
|
req.params,
|
|
)}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
const { origin } = req;
|
|
|
|
const {
|
|
chainId,
|
|
chainName = null,
|
|
blockExplorerUrls = null,
|
|
nativeCurrency = null,
|
|
rpcUrls,
|
|
} = req.params[0];
|
|
|
|
const otherKeys = Object.keys(
|
|
omit(req.params[0], [
|
|
'chainId',
|
|
'chainName',
|
|
'blockExplorerUrls',
|
|
'iconUrls',
|
|
'rpcUrls',
|
|
'nativeCurrency',
|
|
]),
|
|
);
|
|
|
|
if (otherKeys.length > 0) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Received unexpected keys on object parameter. Unsupported keys:\n${otherKeys}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
const isLocalhost = (strUrl) => {
|
|
try {
|
|
const url = new URL(strUrl);
|
|
return url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const firstValidRPCUrl = Array.isArray(rpcUrls)
|
|
? rpcUrls.find(
|
|
(rpcUrl) => isLocalhost(rpcUrl) || validUrl.isHttpsUri(rpcUrl),
|
|
)
|
|
: null;
|
|
|
|
const firstValidBlockExplorerUrl =
|
|
blockExplorerUrls !== null && Array.isArray(blockExplorerUrls)
|
|
? blockExplorerUrls.find(
|
|
(blockExplorerUrl) =>
|
|
isLocalhost(blockExplorerUrl) ||
|
|
validUrl.isHttpsUri(blockExplorerUrl),
|
|
)
|
|
: null;
|
|
|
|
if (!firstValidRPCUrl) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected an array with at least one valid string HTTPS url 'rpcUrls', Received:\n${rpcUrls}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (blockExplorerUrls !== null && !firstValidBlockExplorerUrl) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `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)) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected 0x-prefixed, unpadded, non-zero hexadecimal string 'chainId'. Received:\n${chainId}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (!isSafeChainId(parseInt(_chainId, 16))) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Invalid chain ID "${_chainId}": numerical value greater than max safe value. Received:\n${chainId}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (CHAIN_ID_TO_NETWORK_ID_MAP[_chainId]) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `May not specify default MetaMask chain.`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
const existingNetwork = findCustomRpcBy({ chainId: _chainId });
|
|
|
|
if (existingNetwork) {
|
|
// If the network already exists, the request is considered successful
|
|
res.result = null;
|
|
|
|
const currentChainId = getCurrentChainId();
|
|
if (currentChainId === _chainId) {
|
|
return end();
|
|
}
|
|
|
|
// Ask the user to switch the network
|
|
try {
|
|
await updateRpcTarget(
|
|
await requestUserApproval({
|
|
origin,
|
|
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
|
requestData: {
|
|
rpcUrl: existingNetwork.rpcUrl,
|
|
chainId: existingNetwork.chainId,
|
|
nickname: existingNetwork.nickname,
|
|
ticker: existingNetwork.ticker,
|
|
},
|
|
}),
|
|
);
|
|
res.result = null;
|
|
} catch (error) {
|
|
// For the purposes of this method, it does not matter if the user
|
|
// declines to switch the selected network. However, other errors indicate
|
|
// that something is wrong.
|
|
if (error.code !== errorCodes.provider.userRejectedRequest) {
|
|
return end(error);
|
|
}
|
|
}
|
|
return end();
|
|
}
|
|
|
|
let endpointChainId;
|
|
|
|
try {
|
|
endpointChainId = await jsonRpcRequest(firstValidRPCUrl, 'eth_chainId');
|
|
} catch (err) {
|
|
return end(
|
|
ethErrors.rpc.internal({
|
|
message: `Request for method 'eth_chainId on ${firstValidRPCUrl} failed`,
|
|
data: { networkErr: err },
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (_chainId !== endpointChainId) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Chain ID returned by RPC URL ${firstValidRPCUrl} does not match ${_chainId}`,
|
|
data: { chainId: endpointChainId },
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (typeof chainName !== 'string' || !chainName) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected non-empty string 'chainName'. Received:\n${chainName}`,
|
|
}),
|
|
);
|
|
}
|
|
const _chainName =
|
|
chainName.length > 100 ? chainName.substring(0, 100) : chainName;
|
|
|
|
if (nativeCurrency !== null) {
|
|
if (typeof nativeCurrency !== 'object' || Array.isArray(nativeCurrency)) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected null or object 'nativeCurrency'. Received:\n${nativeCurrency}`,
|
|
}),
|
|
);
|
|
}
|
|
if (nativeCurrency.decimals !== 18) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected the number 18 for 'nativeCurrency.decimals' when 'nativeCurrency' is provided. Received: ${nativeCurrency.decimals}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (!nativeCurrency.symbol || typeof nativeCurrency.symbol !== 'string') {
|
|
return end(
|
|
ethErrors.rpc.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) {
|
|
return end(
|
|
ethErrors.rpc.invalidParams({
|
|
message: `Expected 2-6 character string 'nativeCurrency.symbol'. Received:\n${ticker}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
try {
|
|
await addCustomRpc(
|
|
await requestUserApproval({
|
|
origin,
|
|
type: MESSAGE_TYPE.ADD_ETHEREUM_CHAIN,
|
|
requestData: {
|
|
chainId: _chainId,
|
|
blockExplorerUrl: firstValidBlockExplorerUrl,
|
|
chainName: _chainName,
|
|
rpcUrl: firstValidRPCUrl,
|
|
ticker,
|
|
},
|
|
}),
|
|
);
|
|
|
|
sendMetrics({
|
|
event: 'Custom Network Added',
|
|
category: EVENT.CATEGORIES.NETWORK,
|
|
referrer: {
|
|
url: origin,
|
|
},
|
|
sensitiveProperties: {
|
|
chain_id: _chainId,
|
|
rpc_url: firstValidRPCUrl,
|
|
network_name: _chainName,
|
|
// Including network to override the default network
|
|
// property included in all events. For RPC type networks
|
|
// the MetaMetrics controller uses the rpcUrl for the network
|
|
// property.
|
|
network: firstValidRPCUrl,
|
|
symbol: ticker,
|
|
block_explorer_url: firstValidBlockExplorerUrl,
|
|
source: 'dapp',
|
|
},
|
|
});
|
|
|
|
// Once the network has been added, the requested is considered successful
|
|
res.result = null;
|
|
} catch (error) {
|
|
return end(error);
|
|
}
|
|
|
|
// Ask the user to switch the network
|
|
try {
|
|
await updateRpcTarget(
|
|
await requestUserApproval({
|
|
origin,
|
|
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
|
requestData: {
|
|
rpcUrl: firstValidRPCUrl,
|
|
chainId: _chainId,
|
|
nickname: _chainName,
|
|
ticker,
|
|
},
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
// For the purposes of this method, it does not matter if the user
|
|
// declines to switch the selected network. However, other errors indicate
|
|
// that something is wrong.
|
|
if (error.code !== errorCodes.provider.userRejectedRequest) {
|
|
return end(error);
|
|
}
|
|
}
|
|
return end();
|
|
}
|