mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-24 19:10:22 +01:00
Migrate network configurations (previously frequentRpcListDetail
) from PreferencesController
to NetworkController
(#17421)
This commit is contained in:
parent
423e0854d3
commit
23ca4460cf
@ -420,7 +420,6 @@ const state = {
|
||||
],
|
||||
},
|
||||
},
|
||||
frequentRpcList: [],
|
||||
addressBook: {
|
||||
undefined: {
|
||||
0: {
|
||||
@ -458,20 +457,28 @@ const state = {
|
||||
},
|
||||
],
|
||||
allDetectedTokens: {
|
||||
'0x5' : {
|
||||
'0x5': {
|
||||
'0x9d0ba4ddac06032527b140912ec808ab9451b788': [
|
||||
{
|
||||
address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
|
||||
decimals: 18,
|
||||
symbol: 'LINK',
|
||||
image: 'https://crypto.com/price/coin-data/icon/LINK/color_icon.png',
|
||||
aggregators: ['coinGecko', 'oneInch', 'paraswap', 'zapper', 'zerion'],
|
||||
image:
|
||||
'https://crypto.com/price/coin-data/icon/LINK/color_icon.png',
|
||||
aggregators: [
|
||||
'coinGecko',
|
||||
'oneInch',
|
||||
'paraswap',
|
||||
'zapper',
|
||||
'zerion',
|
||||
],
|
||||
},
|
||||
{
|
||||
address: '0xc00e94Cb662C3520282E6f5717214004A7f26888',
|
||||
decimals: 18,
|
||||
symbol: 'COMP',
|
||||
image: 'https://crypto.com/price/coin-data/icon/COMP/color_icon.png',
|
||||
image:
|
||||
'https://crypto.com/price/coin-data/icon/COMP/color_icon.png',
|
||||
aggregators: [
|
||||
'bancor',
|
||||
'cmc',
|
||||
@ -501,8 +508,8 @@ const state = {
|
||||
'zerion',
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
detectedTokens: [
|
||||
{
|
||||
@ -1176,15 +1183,21 @@ const state = {
|
||||
],
|
||||
},
|
||||
],
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
networkConfigurations: {
|
||||
'test-networkConfigurationId-1': {
|
||||
rpcUrl: 'https://testrpc.com',
|
||||
chainId: '0x1',
|
||||
nickname: 'mainnet',
|
||||
rpcPrefs: { blockExplorerUrl: 'https://etherscan.io' },
|
||||
},
|
||||
'test-networkConfigurationId-2': {
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
chainId: '0x539',
|
||||
ticker: 'ETH',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
accountTokens: {
|
||||
'0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': {
|
||||
'0x1': [
|
||||
|
@ -211,7 +211,7 @@ browser.runtime.onConnectExternal.addListener(async (...args) => {
|
||||
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
|
||||
* @property {object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
|
||||
* @property {object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
|
||||
* @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
|
||||
* @property {object} networkConfigurations - A list of network configurations, containing RPC provider details (eg chainId, rpcUrl, rpcPreferences).
|
||||
* @property {Array} addressBook - A list of previously sent to addresses.
|
||||
* @property {object} contractExchangeRates - Info about current token prices.
|
||||
* @property {Array} tokens - Tokens held by the current user, including their balances.
|
||||
|
@ -5,17 +5,19 @@ export default class BackupController {
|
||||
const {
|
||||
preferencesController,
|
||||
addressBookController,
|
||||
networkController,
|
||||
trackMetaMetricsEvent,
|
||||
} = opts;
|
||||
|
||||
this.preferencesController = preferencesController;
|
||||
this.addressBookController = addressBookController;
|
||||
this.networkController = networkController;
|
||||
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
}
|
||||
|
||||
async restoreUserData(jsonString) {
|
||||
const existingPreferences = this.preferencesController.store.getState();
|
||||
const { preferences, addressBook } = JSON.parse(jsonString);
|
||||
const { preferences, addressBook, network } = JSON.parse(jsonString);
|
||||
if (preferences) {
|
||||
preferences.identities = existingPreferences.identities;
|
||||
preferences.lostIdentities = existingPreferences.lostIdentities;
|
||||
@ -28,7 +30,11 @@ export default class BackupController {
|
||||
this.addressBookController.update(addressBook, true);
|
||||
}
|
||||
|
||||
if (preferences && addressBook) {
|
||||
if (network) {
|
||||
this.networkController.store.updateState(network);
|
||||
}
|
||||
|
||||
if (preferences || addressBook || network) {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'User Data Imported',
|
||||
category: 'Backup',
|
||||
@ -40,6 +46,10 @@ export default class BackupController {
|
||||
const userData = {
|
||||
preferences: { ...this.preferencesController.store.getState() },
|
||||
addressBook: { ...this.addressBookController.state },
|
||||
network: {
|
||||
networkConfigurations:
|
||||
this.networkController.store.getState().networkConfigurations,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@ import { strict as assert } from 'assert';
|
||||
import sinon from 'sinon';
|
||||
import BackupController from './backup';
|
||||
|
||||
function getMockController() {
|
||||
function getMockPreferencesController() {
|
||||
const mcState = {
|
||||
getSelectedAddress: sinon.stub().returns('0x01'),
|
||||
selectedAddress: '0x01',
|
||||
@ -31,13 +31,135 @@ function getMockController() {
|
||||
return mcState;
|
||||
}
|
||||
|
||||
const jsonData = `{"preferences":{"frequentRpcListDetail":[{"chainId":"0x539","nickname":"Localhost 8545","rpcPrefs":{},"rpcUrl":"http://localhost:8545","ticker":"ETH"},{"chainId":"0x38","nickname":"Binance Smart Chain Mainnet","rpcPrefs":{"blockExplorerUrl":"https://bscscan.com"},"rpcUrl":"https://bsc-dataseed1.binance.org","ticker":"BNB"},{"chainId":"0x61","nickname":"Binance Smart Chain Testnet","rpcPrefs":{"blockExplorerUrl":"https://testnet.bscscan.com"},"rpcUrl":"https://data-seed-prebsc-1-s1.binance.org:8545","ticker":"tBNB"},{"chainId":"0x89","nickname":"Polygon Mainnet","rpcPrefs":{"blockExplorerUrl":"https://polygonscan.com"},"rpcUrl":"https://polygon-rpc.com","ticker":"MATIC"}],"useBlockie":false,"useNonceField":false,"usePhishDetect":true,"dismissSeedBackUpReminder":false,"useTokenDetection":false,"useNftDetection":false,"openSeaEnabled":false,"advancedGasFee":null,"featureFlags":{"sendHexData":true,"showIncomingTransactions":true},"knownMethodData":{},"currentLocale":"en","forgottenPassword":false,"preferences":{"hideZeroBalanceTokens":false,"showFiatInTestnets":false,"showTestNetworks":true,"useNativeCurrencyAsPrimaryCurrency":true},"ipfsGateway":"dweb.link","infuraBlocked":false,"ledgerTransportType":"webhid","theme":"light","customNetworkListEnabled":false,"textDirection":"auto"},"addressBook":{"addressBook":{"0x61":{"0x42EB768f2244C8811C63729A21A3569731535f06":{"address":"0x42EB768f2244C8811C63729A21A3569731535f06","chainId":"0x61","isEns":false,"memo":"","name":""}}}}}`;
|
||||
function getMockAddressBookController() {
|
||||
const mcState = {
|
||||
addressBook: {
|
||||
'0x61': {
|
||||
'0x42EB768f2244C8811C63729A21A3569731535f06': {
|
||||
address: '0x42EB768f2244C8811C63729A21A3569731535f06',
|
||||
chainId: '0x61',
|
||||
isEns: false,
|
||||
memo: '',
|
||||
name: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
update: (store) => (mcState.store = store),
|
||||
};
|
||||
|
||||
mcState.store = {
|
||||
getState: sinon.stub().returns(mcState),
|
||||
updateState: (store) => (mcState.store = store),
|
||||
};
|
||||
|
||||
return mcState;
|
||||
}
|
||||
|
||||
function getMockNetworkController() {
|
||||
const mcState = {
|
||||
networkConfigurations: {},
|
||||
|
||||
update: (store) => (mcState.store = store),
|
||||
};
|
||||
|
||||
mcState.store = {
|
||||
getState: sinon.stub().returns(mcState),
|
||||
updateState: (store) => (mcState.store = store),
|
||||
};
|
||||
|
||||
return mcState;
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify({
|
||||
addressBook: {
|
||||
addressBook: {
|
||||
'0x61': {
|
||||
'0x42EB768f2244C8811C63729A21A3569731535f06': {
|
||||
address: '0x42EB768f2244C8811C63729A21A3569731535f06',
|
||||
chainId: '0x61',
|
||||
isEns: false,
|
||||
memo: '',
|
||||
name: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
network: {
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0x38',
|
||||
nickname: 'Binance Smart Chain Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed1.binance.org',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
'network-configuration-id-3': {
|
||||
chainId: '0x61',
|
||||
nickname: 'Binance Smart Chain Testnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://testnet.bscscan.com',
|
||||
},
|
||||
rpcUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545',
|
||||
ticker: 'tBNB',
|
||||
},
|
||||
'network-configuration-id-4': {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://polygonscan.com',
|
||||
},
|
||||
rpcUrl: 'https://polygon-rpc.com',
|
||||
ticker: 'MATIC',
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
useBlockie: false,
|
||||
useNonceField: false,
|
||||
usePhishDetect: true,
|
||||
dismissSeedBackUpReminder: false,
|
||||
useTokenDetection: false,
|
||||
useCollectibleDetection: false,
|
||||
openSeaEnabled: false,
|
||||
advancedGasFee: null,
|
||||
featureFlags: {
|
||||
sendHexData: true,
|
||||
showIncomingTransactions: true,
|
||||
},
|
||||
knownMethodData: {},
|
||||
currentLocale: 'en',
|
||||
forgottenPassword: false,
|
||||
preferences: {
|
||||
hideZeroBalanceTokens: false,
|
||||
showFiatInTestnets: false,
|
||||
showTestNetworks: true,
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
ipfsGateway: 'dweb.link',
|
||||
infuraBlocked: false,
|
||||
ledgerTransportType: 'webhid',
|
||||
theme: 'light',
|
||||
customNetworkListEnabled: false,
|
||||
textDirection: 'auto',
|
||||
},
|
||||
});
|
||||
|
||||
describe('BackupController', function () {
|
||||
const getBackupController = () => {
|
||||
return new BackupController({
|
||||
preferencesController: getMockController(),
|
||||
addressBookController: getMockController(),
|
||||
preferencesController: getMockPreferencesController(),
|
||||
addressBookController: getMockAddressBookController(),
|
||||
networkController: getMockNetworkController(),
|
||||
trackMetaMetricsEvent: sinon.stub(),
|
||||
});
|
||||
};
|
||||
@ -53,17 +175,31 @@ describe('BackupController', function () {
|
||||
it('should restore backup', async function () {
|
||||
const backupController = getBackupController();
|
||||
backupController.restoreUserData(jsonData);
|
||||
// check Preferences backup
|
||||
// check networks backup
|
||||
assert.equal(
|
||||
backupController.preferencesController.store.frequentRpcListDetail[0]
|
||||
.chainId,
|
||||
backupController.networkController.store.networkConfigurations[
|
||||
'network-configuration-id-1'
|
||||
].chainId,
|
||||
'0x539',
|
||||
);
|
||||
assert.equal(
|
||||
backupController.preferencesController.store.frequentRpcListDetail[1]
|
||||
.chainId,
|
||||
backupController.networkController.store.networkConfigurations[
|
||||
'network-configuration-id-2'
|
||||
].chainId,
|
||||
'0x38',
|
||||
);
|
||||
assert.equal(
|
||||
backupController.networkController.store.networkConfigurations[
|
||||
'network-configuration-id-3'
|
||||
].chainId,
|
||||
'0x61',
|
||||
);
|
||||
assert.equal(
|
||||
backupController.networkController.store.networkConfigurations[
|
||||
'network-configuration-id-4'
|
||||
].chainId,
|
||||
'0x89',
|
||||
);
|
||||
// make sure identities are not lost after restore
|
||||
assert.equal(
|
||||
backupController.preferencesController.store.identities[
|
||||
|
@ -697,19 +697,14 @@ export default class MetaMetricsController {
|
||||
),
|
||||
[TRAITS.INSTALL_DATE_EXT]: traits[TRAITS.INSTALL_DATE_EXT] || '',
|
||||
[TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType,
|
||||
[TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map(
|
||||
(rpc) => rpc.chainId,
|
||||
),
|
||||
[TRAITS.NETWORKS_WITHOUT_TICKER]:
|
||||
metamaskState.frequentRpcListDetail.reduce(
|
||||
(networkList, currentNetwork) => {
|
||||
if (!currentNetwork.ticker) {
|
||||
networkList.push(currentNetwork.chainId);
|
||||
}
|
||||
return networkList;
|
||||
},
|
||||
[],
|
||||
),
|
||||
[TRAITS.NETWORKS_ADDED]: Object.values(
|
||||
metamaskState.networkConfigurations,
|
||||
).map((networkConfiguration) => networkConfiguration.chainId),
|
||||
[TRAITS.NETWORKS_WITHOUT_TICKER]: Object.values(
|
||||
metamaskState.networkConfigurations,
|
||||
)
|
||||
.filter(({ ticker }) => !ticker)
|
||||
.map(({ chainId }) => chainId),
|
||||
[TRAITS.NFT_AUTODETECTION_ENABLED]: metamaskState.useNftDetection,
|
||||
[TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities)
|
||||
.length,
|
||||
|
@ -934,11 +934,17 @@ describe('MetaMetricsController', function () {
|
||||
},
|
||||
},
|
||||
allTokens: MOCK_ALL_TOKENS,
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: CHAIN_IDS.MAINNET, ticker: CURRENCY_SYMBOLS.ETH },
|
||||
{ chainId: CHAIN_IDS.GOERLI, ticker: CURRENCY_SYMBOLS.TEST_ETH },
|
||||
{ chainId: '0xaf' },
|
||||
],
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: CHAIN_IDS.MAINNET,
|
||||
ticker: CURRENCY_SYMBOLS.ETH,
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: CHAIN_IDS.GOERLI,
|
||||
ticker: CURRENCY_SYMBOLS.TEST_ETH,
|
||||
},
|
||||
'network-configuration-id-3': { chainId: '0xaf' },
|
||||
},
|
||||
identities: [{}, {}],
|
||||
ledgerTransportType: 'web-hid',
|
||||
openSeaEnabled: true,
|
||||
@ -975,10 +981,10 @@ describe('MetaMetricsController', function () {
|
||||
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
||||
},
|
||||
allTokens: {},
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: CHAIN_IDS.MAINNET },
|
||||
{ chainId: CHAIN_IDS.GOERLI },
|
||||
],
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
||||
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
||||
},
|
||||
ledgerTransportType: 'web-hid',
|
||||
openSeaEnabled: true,
|
||||
identities: [{}, {}],
|
||||
@ -996,10 +1002,10 @@ describe('MetaMetricsController', function () {
|
||||
allTokens: {
|
||||
'0x1': { '0xabcde': [{ '0x12345': { address: '0xtestAddress' } }] },
|
||||
},
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: CHAIN_IDS.MAINNET },
|
||||
{ chainId: CHAIN_IDS.GOERLI },
|
||||
],
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
||||
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
||||
},
|
||||
ledgerTransportType: 'web-hid',
|
||||
openSeaEnabled: false,
|
||||
identities: [{}, {}, {}],
|
||||
@ -1025,10 +1031,10 @@ describe('MetaMetricsController', function () {
|
||||
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
||||
},
|
||||
allTokens: {},
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: CHAIN_IDS.MAINNET },
|
||||
{ chainId: CHAIN_IDS.GOERLI },
|
||||
],
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
||||
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
||||
},
|
||||
ledgerTransportType: 'web-hid',
|
||||
openSeaEnabled: true,
|
||||
identities: [{}, {}],
|
||||
@ -1044,10 +1050,10 @@ describe('MetaMetricsController', function () {
|
||||
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
||||
},
|
||||
allTokens: {},
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: CHAIN_IDS.MAINNET },
|
||||
{ chainId: CHAIN_IDS.GOERLI },
|
||||
],
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
||||
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
||||
},
|
||||
ledgerTransportType: 'web-hid',
|
||||
openSeaEnabled: true,
|
||||
identities: [{}, {}],
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
import EthQuery from 'eth-query';
|
||||
import createFilterMiddleware from 'eth-json-rpc-filters';
|
||||
import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager';
|
||||
import { v4 as random } from 'uuid';
|
||||
import {
|
||||
INFURA_PROVIDER_TYPES,
|
||||
BUILT_IN_NETWORKS,
|
||||
@ -22,14 +23,24 @@ import {
|
||||
CHAIN_IDS,
|
||||
NETWORK_TYPES,
|
||||
} from '../../../../shared/constants/network';
|
||||
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout';
|
||||
import {
|
||||
isPrefixedFormattedHexString,
|
||||
isSafeChainId,
|
||||
} from '../../../../shared/modules/network.utils';
|
||||
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout';
|
||||
import { EVENT } from '../../../../shared/constants/metametrics';
|
||||
import createInfuraClient from './createInfuraClient';
|
||||
import createJsonRpcClient from './createJsonRpcClient';
|
||||
|
||||
/**
|
||||
* @typedef {object} NetworkConfiguration
|
||||
* @property {string} rpcUrl - RPC target URL.
|
||||
* @property {string} chainId - Network ID as per EIP-155
|
||||
* @property {string} ticker - Currency ticker.
|
||||
* @property {object} [rpcPrefs] - Personalized preferences.
|
||||
* @property {string} [nickname] - Personalized network name.
|
||||
*/
|
||||
|
||||
const env = process.env.METAMASK_ENV;
|
||||
const fetchWithTimeout = getFetchWithTimeout();
|
||||
|
||||
@ -83,8 +94,9 @@ export default class NetworkController extends EventEmitter {
|
||||
* @param {object} [options] - NetworkController options.
|
||||
* @param {object} [options.state] - Initial controller state.
|
||||
* @param {string} [options.infuraProjectId] - The Infura project ID.
|
||||
* @param {string} [options.trackMetaMetricsEvent] - A method to forward events to the MetaMetricsController
|
||||
*/
|
||||
constructor({ state = {}, infuraProjectId } = {}) {
|
||||
constructor({ state = {}, infuraProjectId, trackMetaMetricsEvent } = {}) {
|
||||
super();
|
||||
|
||||
// create stores
|
||||
@ -105,11 +117,17 @@ export default class NetworkController extends EventEmitter {
|
||||
...defaultNetworkDetailsState,
|
||||
},
|
||||
);
|
||||
|
||||
this.networkConfigurationsStore = new ObservableStore(
|
||||
state.networkConfigurations || {},
|
||||
);
|
||||
|
||||
this.store = new ComposedStore({
|
||||
provider: this.providerStore,
|
||||
previousProviderStore: this.previousProviderStore,
|
||||
network: this.networkStore,
|
||||
networkDetails: this.networkDetails,
|
||||
networkConfigurations: this.networkConfigurationsStore,
|
||||
});
|
||||
|
||||
// provider and block tracker
|
||||
@ -124,6 +142,7 @@ export default class NetworkController extends EventEmitter {
|
||||
throw new Error('Invalid Infura project ID');
|
||||
}
|
||||
this._infuraProjectId = infuraProjectId;
|
||||
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
|
||||
this.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
|
||||
this.lookupNetwork();
|
||||
@ -224,42 +243,48 @@ export default class NetworkController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
setRpcTarget(rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
||||
assert.ok(
|
||||
isPrefixedFormattedHexString(chainId),
|
||||
`Invalid chain ID "${chainId}": invalid hex string.`,
|
||||
);
|
||||
assert.ok(
|
||||
isSafeChainId(parseInt(chainId, 16)),
|
||||
`Invalid chain ID "${chainId}": numerical value greater than max safe value.`,
|
||||
);
|
||||
/**
|
||||
* A method for setting the currently selected network provider by networkConfigurationId.
|
||||
*
|
||||
* @param {string} networkConfigurationId - the universal unique identifier that corresponds to the network configuration to set as active.
|
||||
* @returns {string} The rpcUrl of the network that was just set as active
|
||||
*/
|
||||
setActiveNetwork(networkConfigurationId) {
|
||||
const targetNetwork =
|
||||
this.networkConfigurationsStore.getState()[networkConfigurationId];
|
||||
|
||||
if (!targetNetwork) {
|
||||
throw new Error(
|
||||
`networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`,
|
||||
);
|
||||
}
|
||||
|
||||
this._setProviderConfig({
|
||||
type: NETWORK_TYPES.RPC,
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
...targetNetwork,
|
||||
});
|
||||
|
||||
return targetNetwork.rpcUrl;
|
||||
}
|
||||
|
||||
setProviderType(type) {
|
||||
assert.notStrictEqual(
|
||||
type,
|
||||
NETWORK_TYPES.RPC,
|
||||
`NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPES.RPC}". Use "setRpcTarget"`,
|
||||
`NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPES.RPC}". Use "setActiveNetwork"`,
|
||||
);
|
||||
assert.ok(
|
||||
INFURA_PROVIDER_TYPES.includes(type),
|
||||
`Unknown Infura provider type "${type}".`,
|
||||
);
|
||||
const { chainId, ticker } = BUILT_IN_NETWORKS[type];
|
||||
const { chainId, ticker, blockExplorerUrl } = BUILT_IN_NETWORKS[type];
|
||||
this._setProviderConfig({
|
||||
type,
|
||||
rpcUrl: '',
|
||||
chainId,
|
||||
ticker: ticker ?? 'ETH',
|
||||
nickname: '',
|
||||
rpcPrefs: { blockExplorerUrl },
|
||||
});
|
||||
}
|
||||
|
||||
@ -476,4 +501,117 @@ export default class NetworkController extends EventEmitter {
|
||||
this._provider = provider;
|
||||
this._blockTracker = blockTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Network Configuration management functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds a network configuration if the rpcUrl is not already present on an
|
||||
* existing network configuration. Otherwise updates the entry with the matching rpcUrl.
|
||||
*
|
||||
* @param {NetworkConfiguration} networkConfiguration - The network configuration to add or, if rpcUrl matches an existing entry, to modify.
|
||||
* @param {object} options
|
||||
* @param {boolean} options.setActive - An option to set the newly added networkConfiguration as the active provider.
|
||||
* @param {string} options.referrer - The site from which the call originated, or 'metamask' for internal calls - used for event metrics.
|
||||
* @param {string} options.source - Where the upsertNetwork event originated (i.e. from a dapp or from the network form)- used for event metrics.
|
||||
* @returns {string} id for the added or updated network configuration
|
||||
*/
|
||||
upsertNetworkConfiguration(
|
||||
{ rpcUrl, chainId, ticker, nickname, rpcPrefs },
|
||||
{ setActive = false, referrer, source },
|
||||
) {
|
||||
assert.ok(
|
||||
isPrefixedFormattedHexString(chainId),
|
||||
`Invalid chain ID "${chainId}": invalid hex string.`,
|
||||
);
|
||||
assert.ok(
|
||||
isSafeChainId(parseInt(chainId, 16)),
|
||||
`Invalid chain ID "${chainId}": numerical value greater than max safe value.`,
|
||||
);
|
||||
|
||||
if (!rpcUrl) {
|
||||
throw new Error(
|
||||
'An rpcUrl is required to add or update network configuration',
|
||||
);
|
||||
}
|
||||
|
||||
if (!referrer || !source) {
|
||||
throw new Error(
|
||||
'referrer and source are required arguments for adding or updating a network configuration',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(rpcUrl);
|
||||
} catch (e) {
|
||||
if (e.message.includes('Invalid URL')) {
|
||||
throw new Error('rpcUrl must be a valid URL');
|
||||
}
|
||||
}
|
||||
|
||||
if (!ticker) {
|
||||
throw new Error(
|
||||
'A ticker is required to add or update networkConfiguration',
|
||||
);
|
||||
}
|
||||
|
||||
const networkConfigurations = this.networkConfigurationsStore.getState();
|
||||
const newNetworkConfiguration = {
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
};
|
||||
|
||||
const oldNetworkConfigurationId = Object.values(networkConfigurations).find(
|
||||
(networkConfiguration) =>
|
||||
networkConfiguration.rpcUrl?.toLowerCase() === rpcUrl?.toLowerCase(),
|
||||
)?.id;
|
||||
|
||||
const newNetworkConfigurationId = oldNetworkConfigurationId || random();
|
||||
this.networkConfigurationsStore.updateState({
|
||||
...networkConfigurations,
|
||||
[newNetworkConfigurationId]: {
|
||||
...newNetworkConfiguration,
|
||||
id: newNetworkConfigurationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!oldNetworkConfigurationId) {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Custom Network Added',
|
||||
category: EVENT.CATEGORIES.NETWORK,
|
||||
referrer: {
|
||||
url: referrer,
|
||||
},
|
||||
properties: {
|
||||
chain_id: chainId,
|
||||
symbol: ticker,
|
||||
source,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (setActive) {
|
||||
this.setActiveNetwork(newNetworkConfigurationId);
|
||||
}
|
||||
|
||||
return newNetworkConfigurationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes network configuration from state.
|
||||
*
|
||||
* @param {string} networkConfigurationId - the unique id for the network configuration to remove.
|
||||
*/
|
||||
removeNetworkConfiguration(networkConfigurationId) {
|
||||
const networkConfigurations = {
|
||||
...this.networkConfigurationsStore.getState(),
|
||||
};
|
||||
delete networkConfigurations[networkConfigurationId];
|
||||
this.networkConfigurationsStore.putState(networkConfigurations);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { normalize as normalizeAddress } from 'eth-sig-util';
|
||||
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network';
|
||||
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
|
||||
import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets';
|
||||
import { ThemeType } from '../../../shared/constants/preferences';
|
||||
import { NETWORK_EVENTS } from './network';
|
||||
@ -12,7 +11,6 @@ export default class PreferencesController {
|
||||
* @typedef {object} PreferencesController
|
||||
* @param {object} opts - Overrides the defaults for the initial state of this.store
|
||||
* @property {object} store The stored object containing a users preferences, stored in local storage
|
||||
* @property {Array} store.frequentRpcList A list of custom rpcs to provide the user
|
||||
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
||||
* @property {boolean} store.useNonceField The users preference for nonce field within the UI
|
||||
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
|
||||
@ -25,7 +23,6 @@ export default class PreferencesController {
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
const initState = {
|
||||
frequentRpcListDetail: [],
|
||||
useBlockie: false,
|
||||
useNonceField: false,
|
||||
usePhishDetect: true,
|
||||
@ -399,67 +396,6 @@ export default class PreferencesController {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom RPC url to state.
|
||||
*
|
||||
* @param {string} rpcUrl - The RPC url to add to frequentRpcList.
|
||||
* @param {string} chainId - The chainId of the selected network.
|
||||
* @param {string} [ticker] - Ticker symbol of the selected network.
|
||||
* @param {string} [nickname] - Nickname of the selected network.
|
||||
* @param {object} [rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
||||
*/
|
||||
upsertToFrequentRpcList(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker = 'ETH',
|
||||
nickname = '',
|
||||
rpcPrefs = {},
|
||||
) {
|
||||
const rpcList = this.getFrequentRpcListDetail();
|
||||
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === rpcUrl;
|
||||
});
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1, { rpcUrl, chainId, ticker, nickname, rpcPrefs });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPrefixedFormattedHexString(chainId)) {
|
||||
throw new Error(`Invalid chainId: "${chainId}"`);
|
||||
}
|
||||
|
||||
rpcList.push({ rpcUrl, chainId, ticker, nickname, rpcPrefs });
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes custom RPC url from state.
|
||||
*
|
||||
* @param {string} url - The RPC url to remove from frequentRpcList.
|
||||
* @returns {Promise<Array>} Promise resolving to updated frequentRpcList.
|
||||
*/
|
||||
async removeFromFrequentRpcList(url) {
|
||||
const rpcList = this.getFrequentRpcListDetail();
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === url;
|
||||
});
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1);
|
||||
}
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList });
|
||||
return rpcList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the `frequentRpcListDetail` property.
|
||||
*
|
||||
* @returns {Array<Array>} An array of rpc urls.
|
||||
*/
|
||||
getFrequentRpcListDetail() {
|
||||
return this.store.getState().frequentRpcListDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean.
|
||||
*
|
||||
|
@ -182,66 +182,6 @@ describe('preferences controller', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding and removing from frequentRpcListDetail', function () {
|
||||
it('should add custom RPC url to state', function () {
|
||||
preferencesController.upsertToFrequentRpcList('rpc_url', '0x1');
|
||||
assert.deepEqual(
|
||||
preferencesController.store.getState().frequentRpcListDetail,
|
||||
[
|
||||
{
|
||||
rpcUrl: 'rpc_url',
|
||||
chainId: '0x1',
|
||||
ticker: 'ETH',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
);
|
||||
preferencesController.upsertToFrequentRpcList('rpc_url', '0x1');
|
||||
assert.deepEqual(
|
||||
preferencesController.store.getState().frequentRpcListDetail,
|
||||
[
|
||||
{
|
||||
rpcUrl: 'rpc_url',
|
||||
chainId: '0x1',
|
||||
ticker: 'ETH',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if chainId is invalid', function () {
|
||||
assert.throws(() => {
|
||||
preferencesController.upsertToFrequentRpcList('rpc_url', '1');
|
||||
}, 'should throw on invalid chainId');
|
||||
});
|
||||
|
||||
it('should remove custom RPC url from state', function () {
|
||||
preferencesController.upsertToFrequentRpcList('rpc_url', '0x1');
|
||||
assert.deepEqual(
|
||||
preferencesController.store.getState().frequentRpcListDetail,
|
||||
[
|
||||
{
|
||||
rpcUrl: 'rpc_url',
|
||||
chainId: '0x1',
|
||||
ticker: 'ETH',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
);
|
||||
preferencesController.removeFromFrequentRpcList('other_rpc_url');
|
||||
preferencesController.removeFromFrequentRpcList('http://localhost:8545');
|
||||
preferencesController.removeFromFrequentRpcList('rpc_url');
|
||||
assert.deepEqual(
|
||||
preferencesController.store.getState().frequentRpcListDetail,
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setUsePhishDetect', function () {
|
||||
it('should default to true', function () {
|
||||
const state = preferencesController.store.getState();
|
||||
|
@ -9,17 +9,5 @@
|
||||
*/
|
||||
const initialState = {
|
||||
config: {},
|
||||
PreferencesController: {
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
chainId: '0x539',
|
||||
ticker: 'ETH',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default initialState;
|
||||
|
@ -5,23 +5,22 @@ import {
|
||||
MESSAGE_TYPE,
|
||||
UNKNOWN_TICKER_SYMBOL,
|
||||
} from '../../../../../shared/constants/app';
|
||||
import { EVENT } from '../../../../../shared/constants/metametrics';
|
||||
import {
|
||||
isPrefixedFormattedHexString,
|
||||
isSafeChainId,
|
||||
} from '../../../../../shared/modules/network.utils';
|
||||
import { EVENT } from '../../../../../shared/constants/metametrics';
|
||||
|
||||
const addEthereumChain = {
|
||||
methodNames: [MESSAGE_TYPE.ADD_ETHEREUM_CHAIN],
|
||||
implementation: addEthereumChainHandler,
|
||||
hookNames: {
|
||||
addCustomRpc: true,
|
||||
upsertNetworkConfiguration: true,
|
||||
getCurrentChainId: true,
|
||||
getCurrentRpcUrl: true,
|
||||
findCustomRpcBy: true,
|
||||
updateRpcTarget: true,
|
||||
findNetworkConfigurationBy: true,
|
||||
setActiveNetwork: true,
|
||||
requestUserApproval: true,
|
||||
sendMetrics: true,
|
||||
},
|
||||
};
|
||||
export default addEthereumChain;
|
||||
@ -32,13 +31,12 @@ async function addEthereumChainHandler(
|
||||
_next,
|
||||
end,
|
||||
{
|
||||
addCustomRpc,
|
||||
upsertNetworkConfiguration,
|
||||
getCurrentChainId,
|
||||
getCurrentRpcUrl,
|
||||
findCustomRpcBy,
|
||||
updateRpcTarget,
|
||||
findNetworkConfigurationBy,
|
||||
setActiveNetwork,
|
||||
requestUserApproval,
|
||||
sendMetrics,
|
||||
},
|
||||
) {
|
||||
if (!req.params?.[0] || typeof req.params[0] !== 'object') {
|
||||
@ -138,7 +136,7 @@ async function addEthereumChainHandler(
|
||||
);
|
||||
}
|
||||
|
||||
const existingNetwork = findCustomRpcBy({ chainId: _chainId });
|
||||
const existingNetwork = findNetworkConfigurationBy({ chainId: _chainId });
|
||||
|
||||
// if the request is to add a network that is already added and configured
|
||||
// with the same RPC gateway we shouldn't try to add it again.
|
||||
@ -158,18 +156,18 @@ async function addEthereumChainHandler(
|
||||
// If this network is already added with but is not the currently selected network
|
||||
// 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,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
||||
requestData: {
|
||||
rpcUrl: existingNetwork.rpcUrl,
|
||||
chainId: existingNetwork.chainId,
|
||||
nickname: existingNetwork.nickname,
|
||||
ticker: existingNetwork.ticker,
|
||||
},
|
||||
});
|
||||
|
||||
await setActiveNetwork(existingNetwork.id);
|
||||
res.result = null;
|
||||
} catch (error) {
|
||||
// For the purposes of this method, it does not matter if the user
|
||||
@ -242,32 +240,30 @@ async function addEthereumChainHandler(
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let networkConfigurationId;
|
||||
try {
|
||||
const customRpc = await requestUserApproval({
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.ADD_ETHEREUM_CHAIN,
|
||||
requestData: {
|
||||
chainId: _chainId,
|
||||
blockExplorerUrl: firstValidBlockExplorerUrl,
|
||||
rpcPrefs: { blockExplorerUrl: firstValidBlockExplorerUrl },
|
||||
chainName: _chainName,
|
||||
rpcUrl: firstValidRPCUrl,
|
||||
ticker,
|
||||
},
|
||||
});
|
||||
await addCustomRpc(customRpc);
|
||||
sendMetrics({
|
||||
event: 'Custom Network Added',
|
||||
category: EVENT.CATEGORIES.NETWORK,
|
||||
referrer: {
|
||||
url: origin,
|
||||
|
||||
networkConfigurationId = await upsertNetworkConfiguration(
|
||||
{
|
||||
chainId: _chainId,
|
||||
rpcPrefs: { blockExplorerUrl: firstValidBlockExplorerUrl },
|
||||
nickname: _chainName,
|
||||
rpcUrl: firstValidRPCUrl,
|
||||
ticker,
|
||||
},
|
||||
properties: {
|
||||
chain_id: _chainId,
|
||||
symbol: ticker,
|
||||
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||
},
|
||||
});
|
||||
{ source: EVENT.SOURCE.NETWORK.DAPP, referrer: origin },
|
||||
);
|
||||
|
||||
// Once the network has been added, the requested is considered successful
|
||||
res.result = null;
|
||||
@ -277,18 +273,18 @@ async function addEthereumChainHandler(
|
||||
|
||||
// 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,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SWITCH_ETHEREUM_CHAIN,
|
||||
requestData: {
|
||||
rpcUrl: firstValidRPCUrl,
|
||||
chainId: _chainId,
|
||||
nickname: _chainName,
|
||||
ticker,
|
||||
networkConfigurationId,
|
||||
},
|
||||
});
|
||||
await setActiveNetwork(networkConfigurationId);
|
||||
} 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
|
||||
|
@ -18,15 +18,15 @@ const switchEthereumChain = {
|
||||
implementation: switchEthereumChainHandler,
|
||||
hookNames: {
|
||||
getCurrentChainId: true,
|
||||
findCustomRpcBy: true,
|
||||
findNetworkConfigurationBy: true,
|
||||
setProviderType: true,
|
||||
updateRpcTarget: true,
|
||||
setActiveNetwork: true,
|
||||
requestUserApproval: true,
|
||||
},
|
||||
};
|
||||
export default switchEthereumChain;
|
||||
|
||||
function findExistingNetwork(chainId, findCustomRpcBy) {
|
||||
function findExistingNetwork(chainId, findNetworkConfigurationBy) {
|
||||
if (chainId in CHAIN_ID_TO_TYPE_MAP) {
|
||||
return {
|
||||
chainId,
|
||||
@ -37,7 +37,7 @@ function findExistingNetwork(chainId, findCustomRpcBy) {
|
||||
};
|
||||
}
|
||||
|
||||
return findCustomRpcBy({ chainId });
|
||||
return findNetworkConfigurationBy({ chainId });
|
||||
}
|
||||
|
||||
async function switchEthereumChainHandler(
|
||||
@ -47,9 +47,9 @@ async function switchEthereumChainHandler(
|
||||
end,
|
||||
{
|
||||
getCurrentChainId,
|
||||
findCustomRpcBy,
|
||||
findNetworkConfigurationBy,
|
||||
setProviderType,
|
||||
updateRpcTarget,
|
||||
setActiveNetwork,
|
||||
requestUserApproval,
|
||||
},
|
||||
) {
|
||||
@ -95,7 +95,7 @@ async function switchEthereumChainHandler(
|
||||
);
|
||||
}
|
||||
|
||||
const requestData = findExistingNetwork(_chainId, findCustomRpcBy);
|
||||
const requestData = findExistingNetwork(_chainId, findNetworkConfigurationBy);
|
||||
if (requestData) {
|
||||
const currentChainId = getCurrentChainId();
|
||||
if (currentChainId === _chainId) {
|
||||
@ -114,7 +114,7 @@ async function switchEthereumChainHandler(
|
||||
) {
|
||||
setProviderType(approvedRequestData.type);
|
||||
} else {
|
||||
await updateRpcTarget(approvedRequestData);
|
||||
await setActiveNetwork(approvedRequestData);
|
||||
}
|
||||
res.result = null;
|
||||
} catch (error) {
|
||||
|
@ -222,27 +222,6 @@ describe('MetaMaskController', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addCustomNetwork', function () {
|
||||
const customRpc = {
|
||||
chainId: '0x1',
|
||||
chainName: 'DUMMY_CHAIN_NAME',
|
||||
rpcUrl: 'DUMMY_RPCURL',
|
||||
ticker: 'DUMMY_TICKER',
|
||||
blockExplorerUrl: 'DUMMY_EXPLORER',
|
||||
};
|
||||
it('two successive calls with custom RPC details give same result', async function () {
|
||||
await metamaskController.addCustomNetwork(customRpc);
|
||||
const rpcList1Length =
|
||||
metamaskController.preferencesController.store.getState()
|
||||
.frequentRpcListDetail.length;
|
||||
await metamaskController.addCustomNetwork(customRpc);
|
||||
const rpcList2Length =
|
||||
metamaskController.preferencesController.store.getState()
|
||||
.frequentRpcListDetail.length;
|
||||
assert.equal(rpcList1Length, rpcList2Length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateTransactionSendFlowHistory', function () {
|
||||
it('two sequential calls with same history give same result', async function () {
|
||||
const recipientAddress = '0xc42edfcc21ed14dda456aa0756c153f7985d8813';
|
||||
|
@ -3,9 +3,9 @@ import pump from 'pump';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { storeAsStream } from '@metamask/obs-store/dist/asStream';
|
||||
import { JsonRpcEngine } from 'json-rpc-engine';
|
||||
import { debounce } from 'lodash';
|
||||
import { createEngineStream } from 'json-rpc-middleware-stream';
|
||||
import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware';
|
||||
import { debounce } from 'lodash';
|
||||
import {
|
||||
KeyringController,
|
||||
keyringBuilderFactory,
|
||||
@ -258,6 +258,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController = new NetworkController({
|
||||
state: initState.NetworkController,
|
||||
infuraProjectId: opts.infuraProjectId,
|
||||
trackMetaMetricsEvent: (...args) =>
|
||||
this.metaMetricsController.trackEvent(...args),
|
||||
});
|
||||
this.networkController.initializeProvider();
|
||||
this.provider =
|
||||
@ -897,6 +899,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.backupController = new BackupController({
|
||||
preferencesController: this.preferencesController,
|
||||
addressBookController: this.addressBookController,
|
||||
networkController: this.networkController,
|
||||
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
@ -961,14 +964,17 @@ export default class MetamaskController extends EventEmitter {
|
||||
status === TransactionStatus.failed
|
||||
) {
|
||||
const txMeta = this.txController.txStateManager.getTransaction(txId);
|
||||
const frequentRpcListDetail =
|
||||
this.preferencesController.getFrequentRpcListDetail();
|
||||
let rpcPrefs = {};
|
||||
if (txMeta.chainId) {
|
||||
const rpcSettings = frequentRpcListDetail.find(
|
||||
(rpc) => txMeta.chainId === rpc.chainId,
|
||||
const { networkConfigurations } =
|
||||
this.networkController.store.getState();
|
||||
const matchingNetworkConfig = Object.values(
|
||||
networkConfigurations,
|
||||
).find(
|
||||
(networkConfiguration) =>
|
||||
networkConfiguration.chainId === txMeta.chainId,
|
||||
);
|
||||
rpcPrefs = rpcSettings?.rpcPrefs ?? {};
|
||||
rpcPrefs = matchingNetworkConfig?.rpcPrefs ?? {};
|
||||
}
|
||||
this.platform.showTransactionNotification(txMeta, rpcPrefs);
|
||||
|
||||
@ -1712,6 +1718,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
txController,
|
||||
assetsContractController,
|
||||
backupController,
|
||||
approvalController,
|
||||
} = this;
|
||||
|
||||
return {
|
||||
@ -1763,6 +1770,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
markNotificationPopupAsAutomaticallyClosed: () =>
|
||||
this.notificationManager.markAsAutomaticallyClosed(),
|
||||
|
||||
// approval
|
||||
requestUserApproval:
|
||||
approvalController.addAndShowApprovalRequest.bind(approvalController),
|
||||
|
||||
// primary HD keyring management
|
||||
addNewAccount: this.addNewAccount.bind(this),
|
||||
verifySeedPhrase: this.verifySeedPhrase.bind(this),
|
||||
@ -1806,11 +1817,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
networkController.setProviderType.bind(networkController),
|
||||
rollbackToPreviousProvider:
|
||||
networkController.rollbackToPreviousProvider.bind(networkController),
|
||||
setCustomRpc: this.setCustomRpc.bind(this),
|
||||
updateAndSetCustomRpc: this.updateAndSetCustomRpc.bind(this),
|
||||
delCustomRpc: this.delCustomRpc.bind(this),
|
||||
addCustomNetwork: this.addCustomNetwork.bind(this),
|
||||
requestAddNetworkApproval: this.requestAddNetworkApproval.bind(this),
|
||||
removeNetworkConfiguration:
|
||||
networkController.removeNetworkConfiguration.bind(networkController),
|
||||
setActiveNetwork:
|
||||
networkController.setActiveNetwork.bind(networkController),
|
||||
upsertNetworkConfiguration:
|
||||
this.networkController.upsertNetworkConfiguration.bind(
|
||||
this.networkController,
|
||||
),
|
||||
// PreferencesController
|
||||
setSelectedAddress: preferencesController.setSelectedAddress.bind(
|
||||
preferencesController,
|
||||
@ -2304,57 +2318,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async requestAddNetworkApproval(customRpc, originIsMetaMask) {
|
||||
try {
|
||||
await this.approvalController.addAndShowApprovalRequest({
|
||||
origin: 'metamask',
|
||||
type: 'wallet_addEthereumChain',
|
||||
requestData: {
|
||||
chainId: customRpc.chainId,
|
||||
blockExplorerUrl: customRpc.rpcPrefs.blockExplorerUrl,
|
||||
chainName: customRpc.nickname,
|
||||
rpcUrl: customRpc.rpcUrl,
|
||||
ticker: customRpc.ticker,
|
||||
imageUrl: customRpc.rpcPrefs.imageUrl,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (
|
||||
!(originIsMetaMask && error.message === 'User rejected the request.')
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addCustomNetwork(customRpc, actionId) {
|
||||
const { chainId, chainName, rpcUrl, ticker, blockExplorerUrl } = customRpc;
|
||||
|
||||
this.preferencesController.upsertToFrequentRpcList(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
chainName,
|
||||
{
|
||||
blockExplorerUrl,
|
||||
},
|
||||
);
|
||||
|
||||
this.metaMetricsController.trackEvent({
|
||||
event: 'Custom Network Added',
|
||||
category: EVENT.CATEGORIES.NETWORK,
|
||||
referrer: {
|
||||
url: ORIGIN_METAMASK,
|
||||
},
|
||||
properties: {
|
||||
chain_id: chainId,
|
||||
symbol: ticker,
|
||||
source: EVENT.SOURCE.NETWORK.POPULAR_NETWORK_LIST,
|
||||
},
|
||||
actionId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Vault and restore an existent keyring.
|
||||
*
|
||||
@ -2479,18 +2442,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
async fetchInfoToSync() {
|
||||
// Preferences
|
||||
const {
|
||||
currentLocale,
|
||||
frequentRpcList,
|
||||
identities,
|
||||
selectedAddress,
|
||||
useTokenDetection,
|
||||
} = this.preferencesController.store.getState();
|
||||
const { currentLocale, identities, selectedAddress, useTokenDetection } =
|
||||
this.preferencesController.store.getState();
|
||||
|
||||
const isTokenDetectionInactiveInMainnet =
|
||||
!useTokenDetection &&
|
||||
this.networkController.store.getState().provider.chainId ===
|
||||
CHAIN_IDS.MAINNET;
|
||||
|
||||
const { networkConfigurations } = this.networkController.store.getState();
|
||||
|
||||
const { tokenList } = this.tokenListController.state;
|
||||
const caseInSensitiveTokenList = isTokenDetectionInactiveInMainnet
|
||||
? STATIC_MAINNET_TOKEN_LIST
|
||||
@ -2498,7 +2459,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
const preferences = {
|
||||
currentLocale,
|
||||
frequentRpcList,
|
||||
identities,
|
||||
selectedAddress,
|
||||
};
|
||||
@ -2574,6 +2534,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
transactions,
|
||||
tokens: { allTokens: allERC20Tokens, allIgnoredTokens },
|
||||
network: this.networkController.store.getState(),
|
||||
networkConfigurations,
|
||||
};
|
||||
}
|
||||
|
||||
@ -4039,40 +4000,24 @@ export default class MetamaskController extends EventEmitter {
|
||||
{ origin },
|
||||
),
|
||||
|
||||
// Custom RPC-related
|
||||
addCustomRpc: async ({
|
||||
chainId,
|
||||
blockExplorerUrl,
|
||||
ticker,
|
||||
chainName,
|
||||
rpcUrl,
|
||||
} = {}) => {
|
||||
await this.preferencesController.upsertToFrequentRpcList(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
chainName,
|
||||
{
|
||||
blockExplorerUrl,
|
||||
},
|
||||
);
|
||||
},
|
||||
findCustomRpcBy: this.findCustomRpcBy.bind(this),
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
getCurrentRpcUrl:
|
||||
getCurrentRpcUrl: () =>
|
||||
this.networkController.store.getState().provider.rpcUrl,
|
||||
// network configuration-related
|
||||
getNetworkConfigurations: () =>
|
||||
this.networkController.store.getState().networkConfigurations,
|
||||
upsertNetworkConfiguration:
|
||||
this.networkController.upsertNetworkConfiguration.bind(
|
||||
this.networkController,
|
||||
),
|
||||
setActiveNetwork: this.networkController.setActiveNetwork.bind(
|
||||
this.networkController,
|
||||
),
|
||||
findNetworkConfigurationBy: this.findNetworkConfigurationBy.bind(this),
|
||||
setProviderType: this.networkController.setProviderType.bind(
|
||||
this.networkController,
|
||||
),
|
||||
updateRpcTarget: ({ rpcUrl, chainId, ticker, nickname }) => {
|
||||
this.networkController.setRpcTarget(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
);
|
||||
},
|
||||
|
||||
// Web3 shim-related
|
||||
getWeb3ShimUsageState: this.alertController.getWeb3ShimUsageState.bind(
|
||||
@ -4428,120 +4373,24 @@ export default class MetamaskController extends EventEmitter {
|
||||
// CONFIG
|
||||
//=============================================================================
|
||||
|
||||
// Log blocks
|
||||
|
||||
/**
|
||||
* A method for selecting a custom URL for an ethereum RPC provider and updating it
|
||||
*
|
||||
* @param {string} rpcUrl - A URL for a valid Ethereum RPC API.
|
||||
* @param {string} chainId - The chainId of the selected network.
|
||||
* @param {string} ticker - The ticker symbol of the selected network.
|
||||
* @param {string} [nickname] - Nickname of the selected network.
|
||||
* @param {object} [rpcPrefs] - RPC preferences.
|
||||
* @param {string} [rpcPrefs.blockExplorerUrl] - URL of block explorer for the chain.
|
||||
* @returns {Promise<string>} The RPC Target URL confirmed.
|
||||
*/
|
||||
async updateAndSetCustomRpc(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker = 'ETH',
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
) {
|
||||
this.networkController.setRpcTarget(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
);
|
||||
await this.preferencesController.upsertToFrequentRpcList(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
);
|
||||
return rpcUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for selecting a custom URL for an ethereum RPC provider.
|
||||
*
|
||||
* @param {string} rpcUrl - A URL for a valid Ethereum RPC API.
|
||||
* @param {string} chainId - The chainId of the selected network.
|
||||
* @param {string} ticker - The ticker symbol of the selected network.
|
||||
* @param {string} nickname - Optional nickname of the selected network.
|
||||
* @param rpcPrefs
|
||||
* @returns {Promise<string>} The RPC Target URL confirmed.
|
||||
*/
|
||||
async setCustomRpc(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker = 'ETH',
|
||||
nickname = '',
|
||||
rpcPrefs = {},
|
||||
) {
|
||||
const frequentRpcListDetail =
|
||||
this.preferencesController.getFrequentRpcListDetail();
|
||||
const rpcSettings = frequentRpcListDetail.find(
|
||||
(rpc) => rpcUrl === rpc.rpcUrl,
|
||||
);
|
||||
|
||||
if (rpcSettings) {
|
||||
this.networkController.setRpcTarget(
|
||||
rpcSettings.rpcUrl,
|
||||
rpcSettings.chainId,
|
||||
rpcSettings.ticker,
|
||||
rpcSettings.nickname,
|
||||
rpcPrefs,
|
||||
);
|
||||
} else {
|
||||
this.networkController.setRpcTarget(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
);
|
||||
await this.preferencesController.upsertToFrequentRpcList(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
);
|
||||
}
|
||||
return rpcUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for deleting a selected custom URL.
|
||||
*
|
||||
* @param {string} rpcUrl - A RPC URL to delete.
|
||||
*/
|
||||
async delCustomRpc(rpcUrl) {
|
||||
await this.preferencesController.removeFromFrequentRpcList(rpcUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first RPC info object that matches at least one field of the
|
||||
* Returns the first network configuration object that matches at least one field of the
|
||||
* provided search criteria. Returns null if no match is found
|
||||
*
|
||||
* @param {object} rpcInfo - The RPC endpoint properties and values to check.
|
||||
* @returns {object} rpcInfo found in the frequentRpcList
|
||||
* @returns {object} rpcInfo found in the network configurations list
|
||||
*/
|
||||
findCustomRpcBy(rpcInfo) {
|
||||
const frequentRpcListDetail =
|
||||
this.preferencesController.getFrequentRpcListDetail();
|
||||
for (const existingRpcInfo of frequentRpcListDetail) {
|
||||
for (const key of Object.keys(rpcInfo)) {
|
||||
if (existingRpcInfo[key] === rpcInfo[key]) {
|
||||
return existingRpcInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
findNetworkConfigurationBy(rpcInfo) {
|
||||
const { networkConfigurations } = this.networkController.store.getState();
|
||||
const networkConfiguration = Object.values(networkConfigurations).find(
|
||||
(configuration) => {
|
||||
return Object.keys(rpcInfo).some((key) => {
|
||||
return configuration[key] === rpcInfo[key];
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return networkConfiguration || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,35 +19,6 @@ import { deferredPromise } from './lib/util';
|
||||
|
||||
const Ganache = require('../../test/e2e/ganache');
|
||||
|
||||
const NOTIFICATION_ID = 'NHL8f2eSSTn9TKBamRLiU';
|
||||
|
||||
const firstTimeState = {
|
||||
config: {},
|
||||
NetworkController: {
|
||||
provider: {
|
||||
type: NETWORK_TYPES.RPC,
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
chainId: '0x539',
|
||||
},
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
NotificationController: {
|
||||
notifications: {
|
||||
[NOTIFICATION_ID]: {
|
||||
id: NOTIFICATION_ID,
|
||||
origin: 'local:http://localhost:8086/',
|
||||
createdDate: 1652967897732,
|
||||
readDate: null,
|
||||
message: 'Hello, http://localhost:8086!',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const ganacheServer = new Ganache();
|
||||
|
||||
const browserPolyfillMock = {
|
||||
@ -120,8 +91,78 @@ const TEST_ADDRESS_3 = '0xeb9e64b93097bc15f01f13eae97015c57ab64823';
|
||||
const TEST_SEED_ALT =
|
||||
'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle';
|
||||
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813';
|
||||
const CUSTOM_RPC_URL = 'http://localhost:8545';
|
||||
const CUSTOM_RPC_CHAIN_ID = '0x539';
|
||||
|
||||
const NOTIFICATION_ID = 'NHL8f2eSSTn9TKBamRLiU';
|
||||
|
||||
const ALT_MAINNET_RPC_URL = 'http://localhost:8545';
|
||||
const POLYGON_RPC_URL = 'https://polygon.llamarpc.com';
|
||||
const POLYGON_RPC_URL_2 = 'https://polygon-rpc.com';
|
||||
|
||||
const NETWORK_CONFIGURATION_ID_1 = 'networkConfigurationId1';
|
||||
const NETWORK_CONFIGURATION_ID_2 = 'networkConfigurationId2';
|
||||
const NETWORK_CONFIGURATION_ID_3 = 'networkConfigurationId3';
|
||||
|
||||
const ETH = 'ETH';
|
||||
const MATIC = 'MATIC';
|
||||
|
||||
const POLYGON_CHAIN_ID = '0x89';
|
||||
const MAINNET_CHAIN_ID = '0x1';
|
||||
|
||||
const firstTimeState = {
|
||||
config: {},
|
||||
NetworkController: {
|
||||
provider: {
|
||||
type: NETWORK_TYPES.RPC,
|
||||
rpcUrl: ALT_MAINNET_RPC_URL,
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
ticker: ETH,
|
||||
nickname: 'Alt Mainnet',
|
||||
id: NETWORK_CONFIGURATION_ID_1,
|
||||
},
|
||||
networkConfigurations: {
|
||||
[NETWORK_CONFIGURATION_ID_1]: {
|
||||
rpcUrl: ALT_MAINNET_RPC_URL,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
ticker: ETH,
|
||||
nickname: 'Alt Mainnet',
|
||||
id: NETWORK_CONFIGURATION_ID_1,
|
||||
},
|
||||
[NETWORK_CONFIGURATION_ID_2]: {
|
||||
rpcUrl: POLYGON_RPC_URL,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
chainId: POLYGON_CHAIN_ID,
|
||||
ticker: MATIC,
|
||||
nickname: 'Polygon',
|
||||
id: NETWORK_CONFIGURATION_ID_2,
|
||||
},
|
||||
[NETWORK_CONFIGURATION_ID_3]: {
|
||||
rpcUrl: POLYGON_RPC_URL_2,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
chainId: POLYGON_CHAIN_ID,
|
||||
ticker: MATIC,
|
||||
nickname: 'Alt Polygon',
|
||||
id: NETWORK_CONFIGURATION_ID_1,
|
||||
},
|
||||
},
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
NotificationController: {
|
||||
notifications: {
|
||||
[NOTIFICATION_ID]: {
|
||||
id: NOTIFICATION_ID,
|
||||
origin: 'local:http://localhost:8086/',
|
||||
createdDate: 1652967897732,
|
||||
readDate: null,
|
||||
message: 'Hello, http://localhost:8086!',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('MetaMaskController', function () {
|
||||
let metamaskController;
|
||||
@ -700,26 +741,6 @@ describe('MetaMaskController', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setCustomRpc', function () {
|
||||
it('returns custom RPC that when called', async function () {
|
||||
const rpcUrl = await metamaskController.setCustomRpc(
|
||||
CUSTOM_RPC_URL,
|
||||
CUSTOM_RPC_CHAIN_ID,
|
||||
);
|
||||
assert.equal(rpcUrl, CUSTOM_RPC_URL);
|
||||
});
|
||||
|
||||
it('changes the network controller rpc', async function () {
|
||||
await metamaskController.setCustomRpc(
|
||||
CUSTOM_RPC_URL,
|
||||
CUSTOM_RPC_CHAIN_ID,
|
||||
);
|
||||
const networkControllerState =
|
||||
metamaskController.networkController.store.getState();
|
||||
assert.equal(networkControllerState.provider.rpcUrl, CUSTOM_RPC_URL);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addNewAccount', function () {
|
||||
it('errors when an primary keyring is does not exist', async function () {
|
||||
const addNewAccount = metamaskController.addNewAccount();
|
||||
@ -1610,5 +1631,97 @@ describe('MetaMaskController', function () {
|
||||
'tokenDetails should include a balance',
|
||||
);
|
||||
});
|
||||
|
||||
describe('findNetworkConfigurationBy', function () {
|
||||
it('returns null if passed an object containing a valid networkConfiguration key but no matching value is found', function () {
|
||||
assert.strictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
chainId: '0xnone',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
it('returns null if passed an object containing an invalid networkConfiguration key', function () {
|
||||
assert.strictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
invalidKey: '0xnone',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns matching networkConfiguration when passed a chainId that matches an existing configuration', function () {
|
||||
assert.deepStrictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
}),
|
||||
{
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
nickname: 'Alt Mainnet',
|
||||
id: NETWORK_CONFIGURATION_ID_1,
|
||||
rpcUrl: ALT_MAINNET_RPC_URL,
|
||||
ticker: ETH,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns matching networkConfiguration when passed a ticker that matches an existing configuration', function () {
|
||||
assert.deepStrictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
ticker: MATIC,
|
||||
}),
|
||||
{
|
||||
rpcUrl: POLYGON_RPC_URL,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
chainId: POLYGON_CHAIN_ID,
|
||||
ticker: MATIC,
|
||||
nickname: 'Polygon',
|
||||
id: NETWORK_CONFIGURATION_ID_2,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns matching networkConfiguration when passed a nickname that matches an existing configuration', function () {
|
||||
assert.deepStrictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
nickname: 'Alt Mainnet',
|
||||
}),
|
||||
{
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
nickname: 'Alt Mainnet',
|
||||
id: NETWORK_CONFIGURATION_ID_1,
|
||||
rpcUrl: ALT_MAINNET_RPC_URL,
|
||||
ticker: ETH,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null if passed an object containing mismatched networkConfiguration key/value combination', function () {
|
||||
assert.deepStrictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
nickname: MAINNET_CHAIN_ID,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the first networkConfiguration added if passed an key/value combination for which there are multiple matching configurations', function () {
|
||||
assert.deepStrictEqual(
|
||||
metamaskController.findNetworkConfigurationBy({
|
||||
chainId: POLYGON_CHAIN_ID,
|
||||
}),
|
||||
{
|
||||
rpcUrl: POLYGON_RPC_URL,
|
||||
type: NETWORK_TYPES.RPC,
|
||||
chainId: POLYGON_CHAIN_ID,
|
||||
ticker: MATIC,
|
||||
nickname: 'Polygon',
|
||||
id: NETWORK_CONFIGURATION_ID_2,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
598
app/scripts/migrations/082.test.js
Normal file
598
app/scripts/migrations/082.test.js
Normal file
@ -0,0 +1,598 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { migrate, version } from './082';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
v4: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('migration #82', () => {
|
||||
beforeEach(() => {
|
||||
v4.mockImplementationOnce(() => 'network-configuration-id-1')
|
||||
.mockImplementationOnce(() => 'network-configuration-id-2')
|
||||
.mockImplementationOnce(() => 'network-configuration-id-3')
|
||||
.mockImplementationOnce(() => 'network-configuration-id-4');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.meta).toStrictEqual({
|
||||
version,
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate the network configurations from an array on the PreferencesController to an object on the NetworkController', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
],
|
||||
},
|
||||
NetworkController: {},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {},
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-3': {
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
},
|
||||
'network-configuration-id-4': {
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change data other than removing `frequentRpcListDetail` and adding `networkConfigurations` on the PreferencesController and NetworkController respectively', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
transactionSecurityCheckEnabled: false,
|
||||
useBlockie: false,
|
||||
useCurrencyRateCheck: true,
|
||||
useMultiAccountBalanceChecker: true,
|
||||
useNftDetection: false,
|
||||
useNonceField: false,
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
],
|
||||
},
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
transactionSecurityCheckEnabled: false,
|
||||
useBlockie: false,
|
||||
useCurrencyRateCheck: true,
|
||||
useMultiAccountBalanceChecker: true,
|
||||
useNftDetection: false,
|
||||
useNonceField: false,
|
||||
},
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-3': {
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
},
|
||||
'network-configuration-id-4': {
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate the network configurations from an array on the PreferencesController to an object on the NetworkController and not include any properties on the frequentRpcListDetail objects that are not included in the list: [chainId, nickname, rpcPrefs, rpcUrl, ticker]', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
invalidKey: 'invalidKey',
|
||||
anotherInvalidKey: 'anotherInvalidKey',
|
||||
},
|
||||
{
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
randomInvalidKey: 'randomInvalidKey',
|
||||
randomInvalidKey2: 'randomInvalidKey2',
|
||||
},
|
||||
],
|
||||
},
|
||||
NetworkController: {},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {},
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate the network configurations from an array on the PreferencesController to an object on the NetworkController even if frequentRpcListDetail entries do not include all members of list [chainId, nickname, rpcPrefs, rpcUrl, ticker]', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0xa4b1',
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
],
|
||||
},
|
||||
NetworkController: {},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage).toStrictEqual({
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {},
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
nickname: undefined,
|
||||
rpcPrefs: undefined,
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
nickname: undefined,
|
||||
rpcPrefs: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change anything if any PreferencesController.frequentRpcListDetail entries are not objects', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
transactionSecurityCheckEnabled: false,
|
||||
useBlockie: false,
|
||||
useCurrencyRateCheck: true,
|
||||
useMultiAccountBalanceChecker: true,
|
||||
useNftDetection: false,
|
||||
useNonceField: false,
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'invalid entry type',
|
||||
1,
|
||||
],
|
||||
},
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
|
||||
it('should not change anything if there is no frequentRpcListDetail property on PreferencesController', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
transactionSecurityCheckEnabled: false,
|
||||
useBlockie: false,
|
||||
useCurrencyRateCheck: true,
|
||||
useMultiAccountBalanceChecker: true,
|
||||
useNftDetection: false,
|
||||
useNonceField: false,
|
||||
},
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-3': {
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
},
|
||||
'network-configuration-id-4': {
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
|
||||
it('should change nothing if PreferencesController is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
});
|
76
app/scripts/migrations/082.ts
Normal file
76
app/scripts/migrations/082.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { hasProperty, isObject } from '@metamask/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const version = 82;
|
||||
|
||||
/**
|
||||
* Migrate the frequentRpcListDetail from the PreferencesController to the NetworkController, convert it from an array to an object
|
||||
* keyed by random uuids.
|
||||
*
|
||||
* @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
|
||||
* @param originalVersionedData.meta - State metadata.
|
||||
* @param originalVersionedData.meta.version - The current state version.
|
||||
* @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
|
||||
* @returns Updated versioned MetaMask extension state.
|
||||
*/
|
||||
export async function migrate(originalVersionedData: {
|
||||
meta: { version: number };
|
||||
data: Record<string, unknown>;
|
||||
}) {
|
||||
const versionedData = cloneDeep(originalVersionedData);
|
||||
versionedData.meta.version = version;
|
||||
versionedData.data = transformState(versionedData.data);
|
||||
return versionedData;
|
||||
}
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (
|
||||
!hasProperty(state, 'PreferencesController') ||
|
||||
!isObject(state.PreferencesController) ||
|
||||
!isObject(state.NetworkController) ||
|
||||
!hasProperty(state.PreferencesController, 'frequentRpcListDetail') ||
|
||||
!Array.isArray(state.PreferencesController.frequentRpcListDetail) ||
|
||||
!state.PreferencesController.frequentRpcListDetail.every(isObject)
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
const { PreferencesController, NetworkController } = state;
|
||||
const { frequentRpcListDetail } = PreferencesController;
|
||||
if (!Array.isArray(frequentRpcListDetail)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const networkConfigurations = frequentRpcListDetail.reduce(
|
||||
(
|
||||
networkConfigurationsAcc,
|
||||
{ rpcUrl, chainId, ticker, nickname, rpcPrefs },
|
||||
) => {
|
||||
const networkConfigurationId = v4();
|
||||
return {
|
||||
...networkConfigurationsAcc,
|
||||
[networkConfigurationId]: {
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
rpcPrefs,
|
||||
nickname,
|
||||
},
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
delete PreferencesController.frequentRpcListDetail;
|
||||
|
||||
return {
|
||||
...state,
|
||||
NetworkController: {
|
||||
...NetworkController,
|
||||
networkConfigurations,
|
||||
},
|
||||
PreferencesController: {
|
||||
...PreferencesController,
|
||||
},
|
||||
};
|
||||
}
|
@ -85,6 +85,7 @@ import * as m078 from './078';
|
||||
import m079 from './079';
|
||||
import m080 from './080';
|
||||
import * as m081 from './081';
|
||||
import * as m082 from './082';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -167,6 +168,7 @@ const migrations = [
|
||||
m079,
|
||||
m080,
|
||||
m081,
|
||||
m082,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
@ -426,6 +426,7 @@ export const EVENT = {
|
||||
NETWORK: {
|
||||
CUSTOM_NETWORK_FORM: 'custom_network_form',
|
||||
POPULAR_NETWORK_LIST: 'popular_network_list',
|
||||
DAPP: 'dapp',
|
||||
},
|
||||
SWAPS: {
|
||||
MAIN_VIEW: 'Main View',
|
||||
|
@ -266,15 +266,18 @@ export const BUILT_IN_NETWORKS = {
|
||||
networkId: NETWORK_IDS.GOERLI,
|
||||
chainId: CHAIN_IDS.GOERLI,
|
||||
ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI],
|
||||
blockExplorerUrl: `https://${NETWORK_TYPES.GOERLI}.etherscan.io`,
|
||||
},
|
||||
[NETWORK_TYPES.SEPOLIA]: {
|
||||
networkId: NETWORK_IDS.SEPOLIA,
|
||||
chainId: CHAIN_IDS.SEPOLIA,
|
||||
ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA],
|
||||
blockExplorerUrl: `https://${NETWORK_TYPES.SEPOLIA}.etherscan.io`,
|
||||
},
|
||||
[NETWORK_TYPES.MAINNET]: {
|
||||
networkId: NETWORK_IDS.MAINNET,
|
||||
chainId: CHAIN_IDS.MAINNET,
|
||||
blockExplorerUrl: `https://etherscan.io`,
|
||||
},
|
||||
[NETWORK_TYPES.LOCALHOST]: {
|
||||
networkId: NETWORK_IDS.LOCALHOST,
|
||||
|
@ -76,7 +76,14 @@
|
||||
"provider": {
|
||||
"type": "rpc",
|
||||
"chainId": "0x5",
|
||||
"ticker": "ETH"
|
||||
"ticker": "ETH",
|
||||
"id": "testNetworkConfigurationId"
|
||||
},
|
||||
"networkConfigurations": {
|
||||
"testNetworkConfigurationId": {
|
||||
"rpcUrl": "https://testrpc.com",
|
||||
"chainId": "0x1"
|
||||
}
|
||||
},
|
||||
"keyrings": [
|
||||
{
|
||||
|
@ -183,6 +183,16 @@ function defaultFixture() {
|
||||
ticker: 'ETH',
|
||||
type: 'rpc',
|
||||
},
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
chainId: CHAIN_IDS.LOCALHOST,
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
networkConfigurationId: 'networkConfigurationId',
|
||||
},
|
||||
},
|
||||
},
|
||||
OnboardingController: {
|
||||
completedOnboarding: true,
|
||||
@ -201,15 +211,6 @@ function defaultFixture() {
|
||||
showIncomingTransactions: true,
|
||||
},
|
||||
forgottenPassword: false,
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: CHAIN_IDS.LOCALHOST,
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
],
|
||||
identities: {
|
||||
'0x5cfe73b6021e818b776b421b1c4db2474086a7e1': {
|
||||
address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
|
||||
@ -313,6 +314,16 @@ function onboardingFixture() {
|
||||
chainId: CHAIN_IDS.LOCALHOST,
|
||||
nickname: 'Localhost 8545',
|
||||
},
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
chainId: CHAIN_IDS.LOCALHOST,
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
networkConfigurationId: 'networkConfigurationId',
|
||||
},
|
||||
},
|
||||
},
|
||||
PreferencesController: {
|
||||
advancedGasFee: null,
|
||||
@ -322,15 +333,6 @@ function onboardingFixture() {
|
||||
showIncomingTransactions: true,
|
||||
},
|
||||
forgottenPassword: false,
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: CHAIN_IDS.LOCALHOST,
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
],
|
||||
identities: {},
|
||||
infuraBlocked: false,
|
||||
ipfsGateway: 'dweb.link',
|
||||
|
@ -263,22 +263,22 @@ describe('Custom network', function () {
|
||||
assert.equal(
|
||||
await networkName.getText(),
|
||||
networkNAME,
|
||||
'Network name is not correct displayed',
|
||||
'Network name is not correctly displayed',
|
||||
);
|
||||
assert.equal(
|
||||
await networkUrl.getText(),
|
||||
networkURL,
|
||||
'Network Url is not correct displayed',
|
||||
'Network Url is not correctly displayed',
|
||||
);
|
||||
assert.equal(
|
||||
await chainIdElement.getText(),
|
||||
chainID.toString(),
|
||||
'Chain Id is not correct displayed',
|
||||
'Chain Id is not correctly displayed',
|
||||
);
|
||||
assert.equal(
|
||||
await currencySymbol.getText(),
|
||||
currencySYMBOL,
|
||||
'Currency symbol is not correct displayed',
|
||||
'Currency symbol is not correctly displayed',
|
||||
);
|
||||
|
||||
await driver.clickElement({ tag: 'a', text: 'View all details' });
|
||||
@ -353,20 +353,21 @@ describe('Custom network', function () {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('Add a custom network and then delete that same network', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: new FixtureBuilder()
|
||||
.withPreferencesController({
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
.withNetworkController({
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
rpcUrl: networkURL,
|
||||
chainId: chainID,
|
||||
ticker: currencySYMBOL,
|
||||
nickname: networkNAME,
|
||||
ticker: currencySYMBOL,
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.build(),
|
||||
ganacheOptions,
|
||||
|
@ -87,7 +87,7 @@ describe('Backup and Restore', function () {
|
||||
assert.notEqual(info, null);
|
||||
// Verify Json
|
||||
assert.equal(
|
||||
info?.preferences?.frequentRpcListDetail[0].chainId,
|
||||
Object.values(info?.network?.networkConfigurations)?.[0].chainId,
|
||||
'0x539',
|
||||
);
|
||||
},
|
||||
|
@ -190,23 +190,23 @@ describe('Stores custom RPC history', function () {
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: new FixtureBuilder()
|
||||
.withPreferencesController({
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
.withNetworkController({
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
rpcUrl: 'http://127.0.0.1:8545/1',
|
||||
chainId: '0x539',
|
||||
ticker: 'ETH',
|
||||
nickname: 'http://127.0.0.1:8545/1',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
{
|
||||
networkConfigurationId2: {
|
||||
rpcUrl: 'http://127.0.0.1:8545/2',
|
||||
chainId: '0x539',
|
||||
ticker: 'ETH',
|
||||
nickname: 'http://127.0.0.1:8545/2',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.build(),
|
||||
ganacheOptions,
|
||||
@ -238,23 +238,23 @@ describe('Stores custom RPC history', function () {
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: new FixtureBuilder()
|
||||
.withPreferencesController({
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
.withNetworkController({
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
rpcUrl: 'http://127.0.0.1:8545/1',
|
||||
chainId: '0x539',
|
||||
ticker: 'ETH',
|
||||
nickname: 'http://127.0.0.1:8545/1',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
{
|
||||
networkConfigurationId2: {
|
||||
rpcUrl: 'http://127.0.0.1:8545/2',
|
||||
chainId: '0x539',
|
||||
ticker: 'ETH',
|
||||
nickname: 'http://127.0.0.1:8545/2',
|
||||
rpcPrefs: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.build(),
|
||||
ganacheOptions,
|
||||
|
@ -270,7 +270,7 @@ export const createSwapsMockStore = () => {
|
||||
accounts: ['0xd85a4b6a394794842887b8284293d69163007bbb'],
|
||||
},
|
||||
],
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
tokens: [
|
||||
{
|
||||
erc20: true,
|
||||
|
@ -21,7 +21,7 @@ import Tooltip from '../../ui/tooltip';
|
||||
import IconWithFallback from '../../ui/icon-with-fallback';
|
||||
import IconBorder from '../../ui/icon-border';
|
||||
import {
|
||||
getFrequentRpcListDetail,
|
||||
getNetworkConfigurations,
|
||||
getUnapprovedConfirmations,
|
||||
} from '../../../selectors';
|
||||
|
||||
@ -29,8 +29,9 @@ import {
|
||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
MESSAGE_TYPE,
|
||||
ORIGIN_METAMASK,
|
||||
} from '../../../../shared/constants/app';
|
||||
import { requestAddNetworkApproval } from '../../../store/actions';
|
||||
import { requestUserApproval } from '../../../store/actions';
|
||||
import Popover from '../../ui/popover';
|
||||
import ConfirmationPage from '../../../pages/confirmation/confirmation';
|
||||
import { FEATURED_RPCS } from '../../../../shared/constants/network';
|
||||
@ -38,14 +39,15 @@ import { ADD_NETWORK_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
||||
import { Icon, ICON_NAMES, ICON_SIZES } from '../../component-library';
|
||||
import { EVENT } from '../../../../shared/constants/metametrics';
|
||||
|
||||
const AddNetwork = () => {
|
||||
const t = useContext(I18nContext);
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const frequentRpcList = useSelector(getFrequentRpcListDetail);
|
||||
const networkConfigurations = useSelector(getNetworkConfigurations);
|
||||
|
||||
const frequentRpcListChainIds = Object.values(frequentRpcList).map(
|
||||
const networkConfigurationChainIds = Object.values(networkConfigurations).map(
|
||||
(net) => net.chainId,
|
||||
);
|
||||
|
||||
@ -55,8 +57,8 @@ const AddNetwork = () => {
|
||||
a.nickname > b.nickname ? 1 : -1,
|
||||
).slice(0, FEATURED_RPCS.length);
|
||||
|
||||
const notFrequentRpcNetworks = nets.filter(
|
||||
(net) => frequentRpcListChainIds.indexOf(net.chainId) === -1,
|
||||
const notExistingNetworkConfigurations = nets.filter(
|
||||
(net) => networkConfigurationChainIds.indexOf(net.chainId) === -1,
|
||||
);
|
||||
const unapprovedConfirmations = useSelector(getUnapprovedConfirmations);
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
@ -80,7 +82,7 @@ const AddNetwork = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(notFrequentRpcNetworks).length === 0 ? (
|
||||
{Object.keys(notExistingNetworkConfigurations).length === 0 ? (
|
||||
<Box
|
||||
className="add-network__edge-case-box"
|
||||
borderRadius={BorderRadius.MD}
|
||||
@ -178,7 +180,7 @@ const AddNetwork = () => {
|
||||
>
|
||||
{t('popularCustomNetworks')}
|
||||
</Typography>
|
||||
{notFrequentRpcNetworks.map((item, index) => (
|
||||
{notExistingNetworkConfigurations.map((item, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
display={DISPLAY.FLEX}
|
||||
@ -191,7 +193,7 @@ const AddNetwork = () => {
|
||||
<Box>
|
||||
<IconBorder size={24}>
|
||||
<IconWithFallback
|
||||
icon={item.rpcPrefs.imageUrl}
|
||||
icon={item.rpcPrefs?.imageUrl}
|
||||
name={item.nickname}
|
||||
size={24}
|
||||
/>
|
||||
@ -250,7 +252,22 @@ const AddNetwork = () => {
|
||||
type="inline"
|
||||
className="add-network__add-button"
|
||||
onClick={async () => {
|
||||
await dispatch(requestAddNetworkApproval(item, true));
|
||||
await dispatch(
|
||||
requestUserApproval({
|
||||
origin: ORIGIN_METAMASK,
|
||||
type: MESSAGE_TYPE.ADD_ETHEREUM_CHAIN,
|
||||
requestData: {
|
||||
chainId: item.chainId,
|
||||
rpcUrl: item.rpcUrl,
|
||||
ticker: item.ticker,
|
||||
rpcPrefs: item.rpcPrefs,
|
||||
imageUrl: item.rpcPrefs?.imageUrl,
|
||||
chainName: item.nickname,
|
||||
referrer: ORIGIN_METAMASK,
|
||||
source: EVENT.SOURCE.NETWORK.POPULAR_NETWORK_LIST,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('add')}
|
||||
|
@ -6,24 +6,22 @@ import mockState from '../../../../test/data/mock-state.json';
|
||||
import AddNetwork from './add-network';
|
||||
|
||||
jest.mock('../../../selectors', () => ({
|
||||
getFrequentRpcListDetail: () => ({
|
||||
frequentRpcList: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
{
|
||||
chainId: '0xA4B1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: { blockExplorerUrl: 'https://explorer.arbitrum.io' },
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/7e127583378c4732a858df2550aff333',
|
||||
ticker: 'AETH',
|
||||
},
|
||||
],
|
||||
getNetworkConfigurations: () => ({
|
||||
networkConfigurationId: {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
networkConfigurationId2: {
|
||||
chainId: '0xA4B1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: { blockExplorerUrl: 'https://explorer.arbitrum.io' },
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/7e127583378c4732a858df2550aff333',
|
||||
ticker: 'AETH',
|
||||
},
|
||||
}),
|
||||
getUnapprovedConfirmations: jest.fn(),
|
||||
getTheme: () => 'light',
|
||||
|
@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { compose } from 'redux';
|
||||
import { pickBy } from 'lodash';
|
||||
import Button from '../../ui/button';
|
||||
import * as actions from '../../../store/actions';
|
||||
import { openAlert as displayInvalidCustomNetworkAlert } from '../../../ducks/alerts/invalid-custom-network';
|
||||
@ -50,7 +51,7 @@ function mapStateToProps(state) {
|
||||
return {
|
||||
provider: state.metamask.provider,
|
||||
shouldShowTestNetworks: getShowTestNetworks(state),
|
||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
||||
networkConfigurations: state.metamask.networkConfigurations,
|
||||
networkDropdownOpen: state.appState.networkDropdownOpen,
|
||||
showTestnetMessageInDropdown: state.metamask.showTestnetMessageInDropdown,
|
||||
};
|
||||
@ -61,8 +62,8 @@ function mapDispatchToProps(dispatch) {
|
||||
setProviderType: (type) => {
|
||||
dispatch(actions.setProviderType(type));
|
||||
},
|
||||
setRpcTarget: (target, chainId, ticker, nickname) => {
|
||||
dispatch(actions.setRpcTarget(target, chainId, ticker, nickname));
|
||||
setActiveNetwork: (networkConfigurationId) => {
|
||||
dispatch(actions.setActiveNetwork(networkConfigurationId));
|
||||
},
|
||||
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
|
||||
displayInvalidCustomNetworkAlert: (networkName) => {
|
||||
@ -95,9 +96,9 @@ class NetworkDropdown extends Component {
|
||||
ticker: PropTypes.string,
|
||||
}).isRequired,
|
||||
setProviderType: PropTypes.func.isRequired,
|
||||
setRpcTarget: PropTypes.func.isRequired,
|
||||
setActiveNetwork: PropTypes.func.isRequired,
|
||||
hideNetworkDropdown: PropTypes.func.isRequired,
|
||||
frequentRpcListDetail: PropTypes.array.isRequired,
|
||||
networkConfigurations: PropTypes.object.isRequired,
|
||||
shouldShowTestNetworks: PropTypes.bool,
|
||||
networkDropdownOpen: PropTypes.bool.isRequired,
|
||||
displayInvalidCustomNetworkAlert: PropTypes.func.isRequired,
|
||||
@ -153,70 +154,69 @@ class NetworkDropdown extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderCustomRpcList(rpcListDetail, provider, opts = {}) {
|
||||
const reversedRpcListDetail = rpcListDetail.slice().reverse();
|
||||
|
||||
return reversedRpcListDetail.map((entry) => {
|
||||
const { rpcUrl, chainId, ticker = 'ETH', nickname = '' } = entry;
|
||||
const isCurrentRpcTarget =
|
||||
provider.type === NETWORK_TYPES.RPC && rpcUrl === provider.rpcUrl;
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={`common${rpcUrl}`}
|
||||
closeMenu={() => this.props.hideNetworkDropdown()}
|
||||
onClick={() => {
|
||||
if (isPrefixedFormattedHexString(chainId)) {
|
||||
this.props.setRpcTarget(rpcUrl, chainId, ticker, nickname);
|
||||
} else {
|
||||
this.props.displayInvalidCustomNetworkAlert(nickname || rpcUrl);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
padding: '16px',
|
||||
}}
|
||||
>
|
||||
{isCurrentRpcTarget ? (
|
||||
<Icon name={ICON_NAMES.CHECK} color={IconColor.successDefault} />
|
||||
) : (
|
||||
<div className="network-check__transparent">✓</div>
|
||||
)}
|
||||
<ColorIndicator
|
||||
color={opts.isLocalHost ? 'localhost' : IconColor.iconMuted}
|
||||
size={Size.LG}
|
||||
type={ColorIndicator.TYPES.FILLED}
|
||||
/>
|
||||
<span
|
||||
className="network-name-item"
|
||||
data-testid={`${nickname}-network-item`}
|
||||
renderCustomRpcList(networkConfigurations, provider, opts = {}) {
|
||||
return Object.entries(networkConfigurations).map(
|
||||
([networkConfigurationId, networkConfiguration]) => {
|
||||
const { rpcUrl, chainId, nickname = '', id } = networkConfiguration;
|
||||
const isCurrentRpcTarget =
|
||||
provider.type === NETWORK_TYPES.RPC && rpcUrl === provider.rpcUrl;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={`common${rpcUrl}`}
|
||||
closeMenu={() => this.props.hideNetworkDropdown()}
|
||||
onClick={() => {
|
||||
if (isPrefixedFormattedHexString(chainId)) {
|
||||
this.props.setActiveNetwork(networkConfigurationId);
|
||||
} else {
|
||||
this.props.displayInvalidCustomNetworkAlert(nickname || rpcUrl);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
color: isCurrentRpcTarget
|
||||
? 'var(--color-text-default)'
|
||||
: 'var(--color-text-alternative)',
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
padding: '16px',
|
||||
}}
|
||||
>
|
||||
{nickname || rpcUrl}
|
||||
</span>
|
||||
{isCurrentRpcTarget ? null : (
|
||||
<ButtonIcon
|
||||
className="delete"
|
||||
iconName={ICON_NAMES.CLOSE}
|
||||
size={ICON_SIZES.SM}
|
||||
ariaLabel={this.context.t('delete')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.props.showConfirmDeleteNetworkModal({
|
||||
target: rpcUrl,
|
||||
onConfirm: () => undefined,
|
||||
});
|
||||
}}
|
||||
{isCurrentRpcTarget ? (
|
||||
<Icon name={ICON_NAMES.CHECK} color={IconColor.successDefault} />
|
||||
) : (
|
||||
<div className="network-check__transparent">✓</div>
|
||||
)}
|
||||
<ColorIndicator
|
||||
color={opts.isLocalHost ? 'localhost' : IconColor.iconMuted}
|
||||
size={Size.LG}
|
||||
type={ColorIndicator.TYPES.FILLED}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
});
|
||||
<span
|
||||
className="network-name-item"
|
||||
data-testid={`${nickname}-network-item`}
|
||||
style={{
|
||||
color: isCurrentRpcTarget
|
||||
? 'var(--color-text-default)'
|
||||
: 'var(--color-text-alternative)',
|
||||
}}
|
||||
>
|
||||
{nickname || rpcUrl}
|
||||
</span>
|
||||
{isCurrentRpcTarget ? null : (
|
||||
<ButtonIcon
|
||||
className="delete"
|
||||
iconName={ICON_NAMES.CLOSE}
|
||||
size={ICON_SIZES.SM}
|
||||
ariaLabel={this.context.t('delete')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.props.showConfirmDeleteNetworkModal({
|
||||
target: id,
|
||||
onConfirm: () => undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getNetworkName() {
|
||||
@ -283,14 +283,18 @@ class NetworkDropdown extends Component {
|
||||
shouldShowTestNetworks,
|
||||
showTestnetMessageInDropdown,
|
||||
hideTestNetMessage,
|
||||
networkConfigurations,
|
||||
} = this.props;
|
||||
const rpcListDetail = this.props.frequentRpcListDetail;
|
||||
const rpcListDetailWithoutLocalHost = rpcListDetail.filter(
|
||||
(rpc) => rpc.rpcUrl !== LOCALHOST_RPC_URL,
|
||||
|
||||
const rpcListDetailWithoutLocalHost = pickBy(
|
||||
networkConfigurations,
|
||||
(config) => config.rpcUrl !== LOCALHOST_RPC_URL,
|
||||
);
|
||||
const rpcListDetailForLocalHost = rpcListDetail.filter(
|
||||
(rpc) => rpc.rpcUrl === LOCALHOST_RPC_URL,
|
||||
const rpcListDetailForLocalHost = pickBy(
|
||||
networkConfigurations,
|
||||
(config) => config.rpcUrl === LOCALHOST_RPC_URL,
|
||||
);
|
||||
|
||||
const isOpen = this.props.networkDropdownOpen;
|
||||
const { t } = this.context;
|
||||
|
||||
|
@ -54,11 +54,17 @@ describe('Network Dropdown', () => {
|
||||
preferences: {
|
||||
showTestNetworks: true,
|
||||
},
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: '0x1a', rpcUrl: 'http://localhost:7545' },
|
||||
{ rpcUrl: 'http://localhost:7546' },
|
||||
{ rpcUrl: LOCALHOST_RPC_URL, nickname: 'localhost' },
|
||||
],
|
||||
networkConfigurations: {
|
||||
networkConfigurationId1: {
|
||||
chainId: '0x1a',
|
||||
rpcUrl: 'http://localhost:7545',
|
||||
},
|
||||
networkConfigurationId2: { rpcUrl: 'http://localhost:7546' },
|
||||
networkConfigurationId3: {
|
||||
rpcUrl: LOCALHOST_RPC_URL,
|
||||
nickname: 'localhost',
|
||||
},
|
||||
},
|
||||
},
|
||||
appState: {
|
||||
networkDropdownOpen: true,
|
||||
@ -120,10 +126,13 @@ describe('Network Dropdown', () => {
|
||||
preferences: {
|
||||
showTestNetworks: false,
|
||||
},
|
||||
frequentRpcListDetail: [
|
||||
{ chainId: '0x1a', rpcUrl: 'http://localhost:7545' },
|
||||
{ rpcUrl: 'http://localhost:7546' },
|
||||
],
|
||||
networkConfigurations: {
|
||||
networkConfigurationId1: {
|
||||
chainId: '0x1a',
|
||||
rpcUrl: 'http://localhost:7545',
|
||||
},
|
||||
networkConfigurationId2: { rpcUrl: 'http://localhost:7546' },
|
||||
},
|
||||
},
|
||||
appState: {
|
||||
networkDropdownOpen: true,
|
||||
|
@ -25,7 +25,7 @@ const initState = {
|
||||
accounts: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
|
||||
},
|
||||
],
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
},
|
||||
};
|
||||
const mockStore = configureStore();
|
||||
|
@ -78,14 +78,14 @@ describe('Account Details Modal', () => {
|
||||
...mockState,
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
chainId: '0x99',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x99',
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ import Modal, { ModalContent } from '../../modal';
|
||||
export default class ConfirmDeleteNetwork extends PureComponent {
|
||||
static propTypes = {
|
||||
hideModal: PropTypes.func.isRequired,
|
||||
delRpcTarget: PropTypes.func.isRequired,
|
||||
removeNetworkConfiguration: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
target: PropTypes.string.isRequired,
|
||||
};
|
||||
@ -15,7 +15,7 @@ export default class ConfirmDeleteNetwork extends PureComponent {
|
||||
};
|
||||
|
||||
handleDelete = () => {
|
||||
this.props.delRpcTarget(this.props.target).then(() => {
|
||||
this.props.removeNetworkConfiguration(this.props.target).then(() => {
|
||||
this.props.onConfirm();
|
||||
this.props.hideModal();
|
||||
});
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'redux';
|
||||
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
|
||||
import { delRpcTarget } from '../../../../store/actions';
|
||||
import { removeNetworkConfiguration } from '../../../../store/actions';
|
||||
import ConfirmDeleteNetwork from './confirm-delete-network.component';
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
delRpcTarget: (target) => dispatch(delRpcTarget(target)),
|
||||
removeNetworkConfiguration: (target) =>
|
||||
dispatch(removeNetworkConfiguration(target)),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -9,7 +9,7 @@ describe('Confirm Delete Network', () => {
|
||||
const props = {
|
||||
hideModal: jest.fn(),
|
||||
onConfirm: jest.fn(),
|
||||
delRpcTarget: jest.fn().mockResolvedValue(),
|
||||
removeNetworkConfiguration: jest.fn().mockResolvedValue(),
|
||||
target: 'target',
|
||||
};
|
||||
|
||||
@ -30,7 +30,7 @@ describe('Confirm Delete Network', () => {
|
||||
|
||||
fireEvent.click(queryByText('[cancel]'));
|
||||
|
||||
expect(props.delRpcTarget).not.toHaveBeenCalled();
|
||||
expect(props.removeNetworkConfiguration).not.toHaveBeenCalled();
|
||||
expect(props.onConfirm).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.hideModal).toHaveBeenCalled();
|
||||
@ -44,7 +44,7 @@ describe('Confirm Delete Network', () => {
|
||||
fireEvent.click(queryByText('[delete]'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.delRpcTarget).toHaveBeenCalled();
|
||||
expect(props.removeNetworkConfiguration).toHaveBeenCalled();
|
||||
expect(props.onConfirm).toHaveBeenCalled();
|
||||
expect(props.hideModal).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -34,8 +34,7 @@ export default function NetworkDisplay({
|
||||
}));
|
||||
const t = useI18nContext();
|
||||
|
||||
const { nickname: networkNickname, type: networkType } =
|
||||
targetNetwork ?? currentNetwork;
|
||||
const { nickname, type: networkType } = targetNetwork ?? currentNetwork;
|
||||
|
||||
return (
|
||||
<Chip
|
||||
@ -73,7 +72,7 @@ export default function NetworkDisplay({
|
||||
}
|
||||
label={
|
||||
networkType === NETWORK_TYPES.RPC
|
||||
? networkNickname ?? t('privateNetwork')
|
||||
? nickname ?? t('privateNetwork')
|
||||
: t(networkType)
|
||||
}
|
||||
className={classnames('network-display', {
|
||||
|
@ -17,7 +17,7 @@ describe('TransactionActivityLog container', () => {
|
||||
metamask: {
|
||||
conversionRate: 280.45,
|
||||
nativeCurrency: 'ETH',
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
provider: {
|
||||
ticker: 'ETH',
|
||||
},
|
||||
@ -36,17 +36,17 @@ describe('TransactionActivityLog container', () => {
|
||||
metamask: {
|
||||
conversionRate: 280.45,
|
||||
nativeCurrency: 'ETH',
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
networkConfigurations: {
|
||||
networkConfigurationId: {
|
||||
rpcUrl: 'https://customnetwork.com/',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://customblockexplorer.com/',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
provider: {
|
||||
rpcUrl: 'https://customnetwork.com/',
|
||||
ticker: 'ETH',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://customblockexplorer.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
WebHIDConnectedStatuses,
|
||||
HardwareTransportStates,
|
||||
} from '../../../shared/constants/hardware-wallets';
|
||||
import { RPCDefinition } from '../../../shared/constants/network';
|
||||
import * as actionConstants from '../../store/actionConstants';
|
||||
|
||||
interface AppState {
|
||||
@ -56,12 +55,13 @@ interface AppState {
|
||||
smartTransactionsErrorMessageDismissed: boolean;
|
||||
ledgerWebHidConnectedStatus: WebHIDConnectedStatuses;
|
||||
ledgerTransportStatus: HardwareTransportStates;
|
||||
newNetworkAdded: string;
|
||||
newNftAddedMessage: string;
|
||||
removeNftMessage: string;
|
||||
newNetworkAddedName: string;
|
||||
newNetworkAddedConfigurationId: string;
|
||||
selectedNetworkConfigurationId: string;
|
||||
sendInputCurrencySwitched: boolean;
|
||||
newTokensImported: string;
|
||||
newCustomNetworkAdded: RPCDefinition | Record<string, any>;
|
||||
onboardedInThisUISession: boolean;
|
||||
customTokenAmount: string;
|
||||
txId: number | null;
|
||||
@ -117,12 +117,13 @@ const initialState: AppState = {
|
||||
smartTransactionsErrorMessageDismissed: false,
|
||||
ledgerWebHidConnectedStatus: WebHIDConnectedStatuses.unknown,
|
||||
ledgerTransportStatus: HardwareTransportStates.none,
|
||||
newNetworkAdded: '',
|
||||
newNftAddedMessage: '',
|
||||
removeNftMessage: '',
|
||||
newNetworkAddedName: '',
|
||||
newNetworkAddedConfigurationId: '',
|
||||
selectedNetworkConfigurationId: '',
|
||||
sendInputCurrencySwitched: false,
|
||||
newTokensImported: '',
|
||||
newCustomNetworkAdded: {},
|
||||
onboardedInThisUISession: false,
|
||||
customTokenAmount: '',
|
||||
scrollToBottom: true,
|
||||
@ -330,18 +331,20 @@ export default function reduceApp(
|
||||
isMouseUser: action.payload,
|
||||
};
|
||||
|
||||
case actionConstants.SET_SELECTED_SETTINGS_RPC_URL:
|
||||
case actionConstants.SET_SELECTED_NETWORK_CONFIGURATION_ID:
|
||||
return {
|
||||
...appState,
|
||||
networksTabSelectedRpcUrl: action.payload,
|
||||
selectedNetworkConfigurationId: action.payload,
|
||||
};
|
||||
|
||||
case actionConstants.SET_NEW_NETWORK_ADDED:
|
||||
case actionConstants.SET_NEW_NETWORK_ADDED: {
|
||||
const { networkConfigurationId, nickname } = action.payload;
|
||||
return {
|
||||
...appState,
|
||||
newNetworkAdded: action.payload,
|
||||
newNetworkAddedName: nickname,
|
||||
newNetworkAddedConfigurationId: networkConfigurationId,
|
||||
};
|
||||
|
||||
}
|
||||
case actionConstants.SET_NEW_TOKENS_IMPORTED:
|
||||
return {
|
||||
...appState,
|
||||
@ -409,11 +412,6 @@ export default function reduceApp(
|
||||
...appState,
|
||||
sendInputCurrencySwitched: !appState.sendInputCurrencySwitched,
|
||||
};
|
||||
case actionConstants.SET_NEW_CUSTOM_NETWORK_ADDED:
|
||||
return {
|
||||
...appState,
|
||||
newCustomNetworkAdded: action.payload,
|
||||
};
|
||||
case actionConstants.ONBOARDED_IN_THIS_UI_SESSION:
|
||||
return {
|
||||
...appState,
|
||||
@ -458,13 +456,6 @@ export function toggleCurrencySwitch(): Action {
|
||||
return { type: actionConstants.TOGGLE_CURRENCY_INPUT_SWITCH };
|
||||
}
|
||||
|
||||
export function setNewCustomNetworkAdded(
|
||||
// can pass in a valid network or empty one
|
||||
payload: RPCDefinition | Record<string, never>,
|
||||
): PayloadAction<RPCDefinition | Record<string, never>> {
|
||||
return { type: actionConstants.SET_NEW_CUSTOM_NETWORK_ADDED, payload };
|
||||
}
|
||||
|
||||
export function setOnBoardedInThisUISession(
|
||||
payload: boolean,
|
||||
): PayloadAction<boolean> {
|
||||
|
@ -28,7 +28,7 @@ const initialState = {
|
||||
isAccountMenuOpen: false,
|
||||
identities: {},
|
||||
unapprovedTxs: {},
|
||||
frequentRpcList: [],
|
||||
networkConfigurations: {},
|
||||
addressBook: [],
|
||||
contractExchangeRates: {},
|
||||
pendingTokens: {},
|
||||
|
@ -33,7 +33,7 @@ const mockState = {
|
||||
unapprovedTypedMessagesCount: 1,
|
||||
provider: { chainId: '0x5', type: 'goerli' },
|
||||
keyrings: [],
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
subjectMetadata: {},
|
||||
cachedBalances: {
|
||||
'0x5': {},
|
||||
|
@ -78,7 +78,7 @@ export default function ConfirmationNetworkSwitch({ newNetwork }) {
|
||||
{newNetwork.chainId in CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP ? (
|
||||
<SiteIcon
|
||||
icon={CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[newNetwork.chainId]}
|
||||
name={newNetwork.name}
|
||||
name={newNetwork.nickname}
|
||||
size={64}
|
||||
/>
|
||||
) : (
|
||||
@ -96,7 +96,7 @@ export default function ConfirmationNetworkSwitch({ newNetwork }) {
|
||||
justifyContent: JustifyContent.center,
|
||||
}}
|
||||
>
|
||||
{newNetwork.name}
|
||||
{newNetwork.nickname}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@ -106,6 +106,6 @@ export default function ConfirmationNetworkSwitch({ newNetwork }) {
|
||||
ConfirmationNetworkSwitch.propTypes = {
|
||||
newNetwork: PropTypes.shape({
|
||||
chainId: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
nickname: PropTypes.string.isRequired,
|
||||
}),
|
||||
};
|
||||
|
@ -348,7 +348,7 @@ function getValues(pendingApproval, t, actions, history) {
|
||||
[t('chainId')]: parseInt(pendingApproval.requestData.chainId, 16),
|
||||
[t('currencySymbol')]: pendingApproval.requestData.ticker,
|
||||
[t('blockExplorerUrl')]:
|
||||
pendingApproval.requestData.blockExplorerUrl,
|
||||
pendingApproval.requestData.rpcPrefs.blockExplorerUrl,
|
||||
},
|
||||
prefaceKeys: [
|
||||
t('networkName'),
|
||||
@ -385,7 +385,21 @@ function getValues(pendingApproval, t, actions, history) {
|
||||
pendingApproval.requestData,
|
||||
);
|
||||
if (originIsMetaMask) {
|
||||
actions.addCustomNetwork(pendingApproval.requestData);
|
||||
const networkConfigurationId = await actions.upsertNetworkConfiguration(
|
||||
{
|
||||
...pendingApproval.requestData,
|
||||
nickname: pendingApproval.requestData.chainName,
|
||||
},
|
||||
{
|
||||
setActive: false,
|
||||
source: pendingApproval.requestData.source,
|
||||
},
|
||||
);
|
||||
await actions.setNewNetworkAdded({
|
||||
networkConfigurationId,
|
||||
nickname: pendingApproval.requestData.chainName,
|
||||
});
|
||||
|
||||
history.push(DEFAULT_ROUTE);
|
||||
}
|
||||
return [];
|
||||
|
@ -3,7 +3,8 @@ import { MESSAGE_TYPE } from '../../../../shared/constants/app';
|
||||
import {
|
||||
rejectPendingApproval,
|
||||
resolvePendingApproval,
|
||||
addCustomNetwork,
|
||||
setNewNetworkAdded,
|
||||
upsertNetworkConfiguration,
|
||||
} from '../../../store/actions';
|
||||
import addEthereumChain from './add-ethereum-chain';
|
||||
import switchEthereumChain from './switch-ethereum-chain';
|
||||
@ -111,7 +112,9 @@ function getAttenuatedDispatch(dispatch) {
|
||||
dispatch(rejectPendingApproval(...args)),
|
||||
resolvePendingApproval: (...args) =>
|
||||
dispatch(resolvePendingApproval(...args)),
|
||||
addCustomNetwork: (...args) => dispatch(addCustomNetwork(...args)),
|
||||
upsertNetworkConfiguration: (...args) =>
|
||||
dispatch(upsertNetworkConfiguration(...args)),
|
||||
setNewNetworkAdded: (...args) => dispatch(setNewNetworkAdded(...args)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
props: {
|
||||
newNetwork: {
|
||||
chainId: pendingApproval.requestData.chainId,
|
||||
name: pendingApproval.requestData.nickname,
|
||||
nickname: pendingApproval.requestData.nickname,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -133,8 +133,7 @@ export default class Home extends PureComponent {
|
||||
);
|
||||
}
|
||||
},
|
||||
newNetworkAdded: PropTypes.string,
|
||||
setNewNetworkAdded: PropTypes.func.isRequired,
|
||||
newNetworkAddedName: PropTypes.string,
|
||||
// This prop is used in the `shouldCloseNotificationPopup` function
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
isSigningQRHardwareTransaction: PropTypes.bool.isRequired,
|
||||
@ -145,9 +144,9 @@ export default class Home extends PureComponent {
|
||||
closeNotificationPopup: PropTypes.func.isRequired,
|
||||
newTokensImported: PropTypes.string,
|
||||
setNewTokensImported: PropTypes.func.isRequired,
|
||||
newCustomNetworkAdded: PropTypes.object,
|
||||
clearNewCustomNetworkAdded: PropTypes.func,
|
||||
setRpcTarget: PropTypes.func,
|
||||
newNetworkAddedConfigurationId: PropTypes.string,
|
||||
clearNewNetworkAdded: PropTypes.func,
|
||||
setActiveNetwork: PropTypes.func,
|
||||
onboardedInThisUISession: PropTypes.bool,
|
||||
};
|
||||
|
||||
@ -268,17 +267,16 @@ export default class Home extends PureComponent {
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
infuraBlocked,
|
||||
showOutdatedBrowserWarning,
|
||||
newNetworkAdded,
|
||||
setNewNetworkAdded,
|
||||
newNftAddedMessage,
|
||||
setNewNftAddedMessage,
|
||||
newNetworkAddedName,
|
||||
removeNftMessage,
|
||||
setRemoveNftMessage,
|
||||
newTokensImported,
|
||||
setNewTokensImported,
|
||||
newCustomNetworkAdded,
|
||||
clearNewCustomNetworkAdded,
|
||||
setRpcTarget,
|
||||
newNetworkAddedConfigurationId,
|
||||
clearNewNetworkAdded,
|
||||
setActiveNetwork,
|
||||
} = this.props;
|
||||
|
||||
const onAutoHide = () => {
|
||||
@ -378,7 +376,7 @@ export default class Home extends PureComponent {
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{newNetworkAdded ? (
|
||||
{newNetworkAddedName ? (
|
||||
<ActionableMessage
|
||||
type="success"
|
||||
className="home__new-network-notification"
|
||||
@ -389,13 +387,13 @@ export default class Home extends PureComponent {
|
||||
variant={TypographyVariant.H7}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
>
|
||||
{t('newNetworkAdded', [newNetworkAdded])}
|
||||
{t('newNetworkAdded', [newNetworkAddedName])}
|
||||
</Typography>
|
||||
<ButtonIcon
|
||||
iconName={ICON_NAMES.CLOSE}
|
||||
size={ICON_SIZES.SM}
|
||||
ariaLabel={t('close')}
|
||||
onClick={() => setNewNetworkAdded('')}
|
||||
onClick={() => clearNewNetworkAdded()}
|
||||
className="home__new-network-notification-close"
|
||||
/>
|
||||
</Box>
|
||||
@ -508,7 +506,7 @@ export default class Home extends PureComponent {
|
||||
key="home-outdatedBrowserNotification"
|
||||
/>
|
||||
) : null}
|
||||
{Object.keys(newCustomNetworkAdded).length !== 0 && (
|
||||
{newNetworkAddedConfigurationId && (
|
||||
<Popover className="home__new-network-added">
|
||||
<i className="fa fa-check-circle fa-2x home__new-network-added__check-circle" />
|
||||
<Typography
|
||||
@ -526,13 +524,8 @@ export default class Home extends PureComponent {
|
||||
type="primary"
|
||||
className="home__new-network-added__switch-to-button"
|
||||
onClick={() => {
|
||||
setRpcTarget(
|
||||
newCustomNetworkAdded.rpcUrl,
|
||||
newCustomNetworkAdded.chainId,
|
||||
newCustomNetworkAdded.ticker,
|
||||
newCustomNetworkAdded.chainName,
|
||||
);
|
||||
clearNewCustomNetworkAdded();
|
||||
setActiveNetwork(newNetworkAddedConfigurationId);
|
||||
clearNewNetworkAdded();
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -540,13 +533,10 @@ export default class Home extends PureComponent {
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
color={TextColor.primaryInverse}
|
||||
>
|
||||
{t('switchToNetwork', [newCustomNetworkAdded.chainName])}
|
||||
{t('switchToNetwork', [newNetworkAddedName])}
|
||||
</Typography>
|
||||
</Button>
|
||||
<Button
|
||||
type="secondary"
|
||||
onClick={() => clearNewCustomNetworkAdded()}
|
||||
>
|
||||
<Button type="secondary" onClick={() => clearNewNetworkAdded()}>
|
||||
<Typography
|
||||
variant={TypographyVariant.H6}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
@ -625,7 +615,7 @@ export default class Home extends PureComponent {
|
||||
firstTimeFlowType,
|
||||
completedOnboarding,
|
||||
onboardedInThisUISession,
|
||||
newCustomNetworkAdded,
|
||||
newNetworkAddedConfigurationId,
|
||||
} = this.props;
|
||||
|
||||
if (forgottenPassword) {
|
||||
@ -640,7 +630,7 @@ export default class Home extends PureComponent {
|
||||
announcementsToShow &&
|
||||
showWhatsNewPopup &&
|
||||
!process.env.IN_TEST &&
|
||||
Object.keys(newCustomNetworkAdded).length === 0;
|
||||
!newNetworkAddedConfigurationId;
|
||||
return (
|
||||
<div className="main-container">
|
||||
<Route path={CONNECTED_ROUTE} component={ConnectedSites} exact />
|
||||
|
@ -40,15 +40,12 @@ import {
|
||||
setNewNftAddedMessage,
|
||||
setRemoveNftMessage,
|
||||
setNewTokensImported,
|
||||
setRpcTarget,
|
||||
setActiveNetwork,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
removeSnapError,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../store/actions';
|
||||
import {
|
||||
hideWhatsNewPopup,
|
||||
setNewCustomNetworkAdded,
|
||||
} from '../../ducks/app/app';
|
||||
import { hideWhatsNewPopup } from '../../ducks/app/app';
|
||||
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask';
|
||||
import { getSwapsFeatureIsLive } from '../../ducks/swaps/swaps';
|
||||
import { getEnvironmentType } from '../../../app/scripts/lib/util';
|
||||
@ -142,12 +139,12 @@ const mapStateToProps = (state) => {
|
||||
showOutdatedBrowserWarning:
|
||||
getIsBrowserDeprecated() && getShowOutdatedBrowserWarning(state),
|
||||
seedPhraseBackedUp,
|
||||
newNetworkAdded: getNewNetworkAdded(state),
|
||||
newNetworkAddedName: getNewNetworkAdded(state),
|
||||
isSigningQRHardwareTransaction,
|
||||
newNftAddedMessage: getNewNftAddedMessage(state),
|
||||
removeNftMessage: getRemoveNftMessage(state),
|
||||
newTokensImported: getNewTokensImported(state),
|
||||
newCustomNetworkAdded: appState.newCustomNetworkAdded,
|
||||
newNetworkAddedConfigurationId: appState.newNetworkAddedConfigurationId,
|
||||
onboardedInThisUISession: appState.onboardedInThisUISession,
|
||||
};
|
||||
};
|
||||
@ -172,10 +169,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setOutdatedBrowserWarningLastShown: (lastShown) => {
|
||||
dispatch(setOutdatedBrowserWarningLastShown(lastShown));
|
||||
},
|
||||
setNewNetworkAdded: (newNetwork) => {
|
||||
console.log({ newNetwork });
|
||||
dispatch(setNewNetworkAdded(newNetwork));
|
||||
},
|
||||
setNewNftAddedMessage: (message) => {
|
||||
dispatch(setRemoveNftMessage(''));
|
||||
dispatch(setNewNftAddedMessage(message));
|
||||
@ -187,11 +180,11 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setNewTokensImported: (newTokens) => {
|
||||
dispatch(setNewTokensImported(newTokens));
|
||||
},
|
||||
clearNewCustomNetworkAdded: () => {
|
||||
dispatch(setNewCustomNetworkAdded({}));
|
||||
clearNewNetworkAdded: () => {
|
||||
dispatch(setNewNetworkAdded({}));
|
||||
},
|
||||
setRpcTarget: (rpcUrl, chainId, ticker, nickname) => {
|
||||
dispatch(setRpcTarget(rpcUrl, chainId, ticker, nickname));
|
||||
setActiveNetwork: (networkConfigurationId) => {
|
||||
dispatch(setActiveNetwork(networkConfigurationId));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -12,7 +12,7 @@ const store = configureStore(testData);
|
||||
const { metamask } = store.getState();
|
||||
|
||||
const {
|
||||
frequentRpcListDetail,
|
||||
networkConfigurations,
|
||||
identities,
|
||||
pendingTokens,
|
||||
selectedAddress,
|
||||
@ -105,8 +105,8 @@ export default {
|
||||
identities,
|
||||
showSearchTab: true,
|
||||
mostRecentOverviewPage: DEFAULT_ROUTE,
|
||||
chainId: frequentRpcListDetail[0].chainId,
|
||||
rpcPrefs: frequentRpcListDetail[0].rpcPrefs,
|
||||
chainId: networkConfigurations['test-networkConfigurationId-1'].chainId,
|
||||
rpcPrefs: networkConfigurations['test-networkConfigurationId-1'].rpcPrefs,
|
||||
tokenList,
|
||||
useTokenDetection: false,
|
||||
selectedAddress,
|
||||
|
@ -36,7 +36,7 @@ describe('Import Token', () => {
|
||||
metamask: {
|
||||
tokens: [],
|
||||
provider: { chainId: '0x1' },
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
identities: {},
|
||||
selectedAddress: '0x1231231',
|
||||
useTokenDetection: true,
|
||||
|
@ -56,7 +56,7 @@ export default function PrivacySettings() {
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const networks = useSelector(
|
||||
(state) => state.metamask.frequentRpcListDetail || [],
|
||||
(state) => state.metamask.networkConfigurations || {},
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
@ -195,7 +195,7 @@ export default function PrivacySettings() {
|
||||
])}
|
||||
|
||||
<Box paddingTop={2}>
|
||||
{networks.length > 1 ? (
|
||||
{Object.values(networks).length > 1 ? (
|
||||
<div className="privacy-settings__network">
|
||||
<>
|
||||
<NetworkDisplay
|
||||
@ -219,7 +219,7 @@ export default function PrivacySettings() {
|
||||
</>
|
||||
</div>
|
||||
) : null}
|
||||
{networks.length === 1 ? (
|
||||
{Object.values(networks).length === 1 ? (
|
||||
<Button
|
||||
type="secondary"
|
||||
rounded
|
||||
|
@ -11,7 +11,7 @@ import PrivacySettings from './privacy-settings';
|
||||
describe('Privacy Settings Onboarding View', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
provider: {
|
||||
type: 'test',
|
||||
},
|
||||
|
@ -1,16 +1,9 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import validUrl from 'valid-url';
|
||||
import log from 'loglevel';
|
||||
import classnames from 'classnames';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import {
|
||||
@ -22,21 +15,19 @@ import ActionableMessage from '../../../../components/ui/actionable-message';
|
||||
import Button from '../../../../components/ui/button';
|
||||
import FormField from '../../../../components/ui/form-field';
|
||||
import {
|
||||
setSelectedSettingsRpcUrl,
|
||||
updateAndSetCustomRpc,
|
||||
editRpc,
|
||||
setSelectedNetworkConfigurationId,
|
||||
upsertNetworkConfiguration,
|
||||
editAndSetNetworkConfiguration,
|
||||
showModal,
|
||||
setNewNetworkAdded,
|
||||
} from '../../../../store/actions';
|
||||
import fetchWithCache from '../../../../../shared/lib/fetch-with-cache';
|
||||
import { usePrevious } from '../../../../hooks/usePrevious';
|
||||
import { MetaMetricsContext } from '../../../../contexts/metametrics';
|
||||
import { EVENT } from '../../../../../shared/constants/metametrics';
|
||||
import {
|
||||
infuraProjectId,
|
||||
FEATURED_RPCS,
|
||||
} from '../../../../../shared/constants/network';
|
||||
import { ORIGIN_METAMASK } from '../../../../../shared/constants/app';
|
||||
import { decimalToHex } from '../../../../../shared/modules/conversion.utils';
|
||||
|
||||
/**
|
||||
@ -86,7 +77,6 @@ const NetworksForm = ({
|
||||
submitCallback,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
const dispatch = useDispatch();
|
||||
const { label, labelKey, viewOnly, rpcPrefs } = selectedNetwork;
|
||||
const selectedNetworkName = label || (labelKey && t(labelKey));
|
||||
@ -178,7 +168,7 @@ const NetworksForm = ({
|
||||
setTicker('');
|
||||
setBlockExplorerUrl('');
|
||||
setErrors({});
|
||||
dispatch(setSelectedSettingsRpcUrl(''));
|
||||
dispatch(setSelectedNetworkConfigurationId(''));
|
||||
};
|
||||
}, [
|
||||
setNetworkName,
|
||||
@ -502,45 +492,56 @@ const NetworksForm = ({
|
||||
try {
|
||||
const formChainId = chainId.trim().toLowerCase();
|
||||
const prefixedChainId = prefixChainId(formChainId);
|
||||
|
||||
let networkConfigurationId;
|
||||
// After this point, isSubmitting will be reset in componentDidUpdate
|
||||
if (selectedNetwork.rpcUrl && rpcUrl !== selectedNetwork.rpcUrl) {
|
||||
await dispatch(
|
||||
editRpc(
|
||||
selectedNetwork.rpcUrl,
|
||||
rpcUrl,
|
||||
prefixedChainId,
|
||||
ticker,
|
||||
networkName,
|
||||
editAndSetNetworkConfiguration(
|
||||
{
|
||||
...rpcPrefs,
|
||||
blockExplorerUrl: blockExplorerUrl || rpcPrefs?.blockExplorerUrl,
|
||||
rpcUrl,
|
||||
ticker,
|
||||
networkConfigurationId: selectedNetwork.networkConfigurationId,
|
||||
chainId: prefixedChainId,
|
||||
nickname: networkName,
|
||||
rpcPrefs: {
|
||||
...rpcPrefs,
|
||||
blockExplorerUrl:
|
||||
blockExplorerUrl || rpcPrefs?.blockExplorerUrl,
|
||||
},
|
||||
},
|
||||
{
|
||||
source: EVENT.SOURCE.NETWORK.CUSTOM_NETWORK_FORM,
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await dispatch(
|
||||
updateAndSetCustomRpc(rpcUrl, prefixedChainId, ticker, networkName, {
|
||||
...rpcPrefs,
|
||||
blockExplorerUrl: blockExplorerUrl || rpcPrefs?.blockExplorerUrl,
|
||||
}),
|
||||
networkConfigurationId = await dispatch(
|
||||
upsertNetworkConfiguration(
|
||||
{
|
||||
rpcUrl,
|
||||
ticker,
|
||||
chainId: prefixedChainId,
|
||||
nickname: networkName,
|
||||
rpcPrefs: {
|
||||
...rpcPrefs,
|
||||
blockExplorerUrl:
|
||||
blockExplorerUrl || rpcPrefs?.blockExplorerUrl,
|
||||
},
|
||||
},
|
||||
{
|
||||
setActive: true,
|
||||
source: EVENT.SOURCE.NETWORK.CUSTOM_NETWORK_FORM,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (addNewNetwork) {
|
||||
trackEvent({
|
||||
event: 'Custom Network Added',
|
||||
category: EVENT.CATEGORIES.NETWORK,
|
||||
referrer: {
|
||||
url: ORIGIN_METAMASK,
|
||||
},
|
||||
properties: {
|
||||
chain_id: addHexPrefix(Number(chainId).toString(16)),
|
||||
symbol: ticker,
|
||||
source: EVENT.SOURCE.NETWORK.CUSTOM_NETWORK_FORM,
|
||||
},
|
||||
});
|
||||
dispatch(setNewNetworkAdded(networkName));
|
||||
dispatch(
|
||||
setNewNetworkAdded({
|
||||
nickname: networkName,
|
||||
networkConfigurationId,
|
||||
}),
|
||||
);
|
||||
|
||||
submitCallback?.();
|
||||
}
|
||||
@ -552,7 +553,7 @@ const NetworksForm = ({
|
||||
|
||||
const onCancel = () => {
|
||||
if (addNewNetwork) {
|
||||
dispatch(setSelectedSettingsRpcUrl(''));
|
||||
dispatch(setSelectedNetworkConfigurationId(''));
|
||||
cancelCallback?.();
|
||||
} else {
|
||||
resetForm();
|
||||
@ -563,10 +564,10 @@ const NetworksForm = ({
|
||||
dispatch(
|
||||
showModal({
|
||||
name: 'CONFIRM_DELETE_NETWORK',
|
||||
target: selectedNetwork.rpcUrl,
|
||||
target: selectedNetwork.networkConfigurationId,
|
||||
onConfirm: () => {
|
||||
resetForm();
|
||||
dispatch(setSelectedSettingsRpcUrl(''));
|
||||
dispatch(setSelectedNetworkConfigurationId(''));
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
NETWORK_TYPES,
|
||||
} from '../../../../../shared/constants/network';
|
||||
import { NETWORKS_ROUTE } from '../../../../helpers/constants/routes';
|
||||
import { setSelectedSettingsRpcUrl } from '../../../../store/actions';
|
||||
import { setSelectedNetworkConfigurationId } from '../../../../store/actions';
|
||||
import { getEnvironmentType } from '../../../../../app/scripts/lib/util';
|
||||
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../shared/constants/app';
|
||||
import { getProvider } from '../../../../selectors';
|
||||
@ -26,7 +26,7 @@ import { IconColor } from '../../../../helpers/constants/design-system';
|
||||
const NetworksListItem = ({
|
||||
network,
|
||||
networkIsSelected,
|
||||
selectedRpcUrl,
|
||||
selectedNetworkConfigurationId,
|
||||
setSearchQuery,
|
||||
setSearchedNetworks,
|
||||
}) => {
|
||||
@ -38,11 +38,14 @@ const NetworksListItem = ({
|
||||
const {
|
||||
label,
|
||||
labelKey,
|
||||
networkConfigurationId,
|
||||
rpcUrl,
|
||||
providerType: currentProviderType,
|
||||
} = network;
|
||||
|
||||
const listItemNetworkIsSelected = selectedRpcUrl && selectedRpcUrl === rpcUrl;
|
||||
const listItemNetworkIsSelected =
|
||||
selectedNetworkConfigurationId &&
|
||||
selectedNetworkConfigurationId === networkConfigurationId;
|
||||
const listItemUrlIsProviderUrl = rpcUrl === provider.rpcUrl;
|
||||
const listItemTypeIsProviderNonRpcType =
|
||||
provider.type !== NETWORK_TYPES.RPC &&
|
||||
@ -64,12 +67,12 @@ const NetworksListItem = ({
|
||||
return (
|
||||
<div
|
||||
ref={settingsRefs}
|
||||
key={`settings-network-list-item:${rpcUrl}`}
|
||||
key={`settings-network-list-item:${networkConfigurationId}`}
|
||||
className="networks-tab__networks-list-item"
|
||||
onClick={() => {
|
||||
setSearchQuery('');
|
||||
setSearchedNetworks([]);
|
||||
dispatch(setSelectedSettingsRpcUrl(rpcUrl));
|
||||
dispatch(setSelectedNetworkConfigurationId(networkConfigurationId));
|
||||
if (!isFullScreen) {
|
||||
global.platform.openExtensionInBrowser(NETWORKS_ROUTE);
|
||||
}
|
||||
@ -133,7 +136,7 @@ const NetworksListItem = ({
|
||||
NetworksListItem.propTypes = {
|
||||
network: PropTypes.object.isRequired,
|
||||
networkIsSelected: PropTypes.bool,
|
||||
selectedRpcUrl: PropTypes.string,
|
||||
selectedNetworkConfigurationId: PropTypes.string,
|
||||
setSearchQuery: PropTypes.func,
|
||||
setSearchedNetworks: PropTypes.func,
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ const NetworksList = ({
|
||||
networkIsSelected,
|
||||
networksToRender,
|
||||
networkDefaultedToProvider,
|
||||
selectedRpcUrl,
|
||||
selectedNetworkConfigurationId,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const [searchedNetworks, setSearchedNetworks] = useState([]);
|
||||
@ -56,7 +56,7 @@ const NetworksList = ({
|
||||
key={`settings-network-list:${network.rpcUrl}`}
|
||||
network={network}
|
||||
networkIsSelected={networkIsSelected}
|
||||
selectedRpcUrl={selectedRpcUrl}
|
||||
selectedNetworkConfigurationId={selectedNetworkConfigurationId}
|
||||
setSearchQuery={setSearchQuery}
|
||||
setSearchedNetworks={setSearchedNetworks}
|
||||
/>
|
||||
@ -76,7 +76,7 @@ const NetworksList = ({
|
||||
key={`settings-network-list:${network.rpcUrl}`}
|
||||
network={network}
|
||||
networkIsSelected={networkIsSelected}
|
||||
selectedRpcUrl={selectedRpcUrl}
|
||||
selectedNetworkConfigurationId={selectedNetworkConfigurationId}
|
||||
setSearchQuery={setSearchQuery}
|
||||
setSearchedNetworks={setSearchedNetworks}
|
||||
/>
|
||||
@ -89,7 +89,7 @@ NetworksList.propTypes = {
|
||||
networkDefaultedToProvider: PropTypes.bool,
|
||||
networkIsSelected: PropTypes.bool,
|
||||
networksToRender: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedRpcUrl: PropTypes.string,
|
||||
selectedNetworkConfigurationId: PropTypes.string,
|
||||
};
|
||||
|
||||
export default NetworksList;
|
||||
|
@ -27,7 +27,7 @@ const NetworksTabContent = ({
|
||||
networkDefaultedToProvider={networkDefaultedToProvider}
|
||||
networkIsSelected={networkIsSelected}
|
||||
networksToRender={networksToRender}
|
||||
selectedRpcUrl={selectedNetwork.rpcUrl}
|
||||
selectedNetworkConfigurationId={selectedNetwork.networkConfigurationId}
|
||||
/>
|
||||
{shouldRenderNetworkForm ? (
|
||||
<NetworksForm
|
||||
|
@ -14,7 +14,7 @@ const mockState = {
|
||||
ticker: 'ETH',
|
||||
type: 'localhost',
|
||||
},
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
},
|
||||
appState: {
|
||||
networksTabSelectedRpcUrl: 'http://localhost:8545',
|
||||
|
@ -10,13 +10,13 @@ import {
|
||||
DEFAULT_ROUTE,
|
||||
NETWORKS_ROUTE,
|
||||
} from '../../../helpers/constants/routes';
|
||||
import { setSelectedSettingsRpcUrl } from '../../../store/actions';
|
||||
import { setSelectedNetworkConfigurationId } from '../../../store/actions';
|
||||
import Button from '../../../components/ui/button';
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app';
|
||||
import {
|
||||
getFrequentRpcListDetail,
|
||||
getNetworksTabSelectedRpcUrl,
|
||||
getNetworkConfigurations,
|
||||
getNetworksTabSelectedNetworkConfigurationId,
|
||||
getProvider,
|
||||
} from '../../../selectors';
|
||||
import {
|
||||
@ -47,30 +47,34 @@ const NetworksTab = ({ addNewNetwork }) => {
|
||||
Boolean(pathname.match(NETWORKS_FORM_ROUTE)) ||
|
||||
window.location.hash.split('#')[2] === 'blockExplorerUrl';
|
||||
|
||||
const frequentRpcListDetail = useSelector(getFrequentRpcListDetail);
|
||||
const networkConfigurations = useSelector(getNetworkConfigurations);
|
||||
const provider = useSelector(getProvider);
|
||||
const networksTabSelectedRpcUrl = useSelector(getNetworksTabSelectedRpcUrl);
|
||||
const networksTabSelectedNetworkConfigurationId = useSelector(
|
||||
getNetworksTabSelectedNetworkConfigurationId,
|
||||
);
|
||||
|
||||
const frequentRpcNetworkListDetails = frequentRpcListDetail.map((rpc) => {
|
||||
return {
|
||||
label: rpc.nickname,
|
||||
iconColor: 'var(--color-icon-alternative)',
|
||||
providerType: NETWORK_TYPES.RPC,
|
||||
rpcUrl: rpc.rpcUrl,
|
||||
chainId: rpc.chainId,
|
||||
ticker: rpc.ticker,
|
||||
blockExplorerUrl: rpc.rpcPrefs?.blockExplorerUrl || '',
|
||||
isATestNetwork: TEST_CHAINS.includes(rpc.chainId),
|
||||
};
|
||||
});
|
||||
const networkConfigurationsList = Object.entries(networkConfigurations).map(
|
||||
([networkConfigurationId, networkConfiguration]) => {
|
||||
return {
|
||||
label: networkConfiguration.nickname,
|
||||
iconColor: 'var(--color-icon-alternative)',
|
||||
providerType: NETWORK_TYPES.RPC,
|
||||
rpcUrl: networkConfiguration.rpcUrl,
|
||||
chainId: networkConfiguration.chainId,
|
||||
ticker: networkConfiguration.ticker,
|
||||
blockExplorerUrl: networkConfiguration.rpcPrefs?.blockExplorerUrl || '',
|
||||
isATestNetwork: TEST_CHAINS.includes(networkConfiguration.chainId),
|
||||
networkConfigurationId,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const networksToRender = [
|
||||
...defaultNetworks,
|
||||
...frequentRpcNetworkListDetails,
|
||||
];
|
||||
const networksToRender = [...defaultNetworks, ...networkConfigurationsList];
|
||||
let selectedNetwork =
|
||||
networksToRender.find(
|
||||
(network) => network.rpcUrl === networksTabSelectedRpcUrl,
|
||||
(network) =>
|
||||
network.networkConfigurationId ===
|
||||
networksTabSelectedNetworkConfigurationId,
|
||||
) || {};
|
||||
const networkIsSelected = Boolean(selectedNetwork.rpcUrl);
|
||||
|
||||
@ -89,7 +93,7 @@ const NetworksTab = ({ addNewNetwork }) => {
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(setSelectedSettingsRpcUrl(''));
|
||||
dispatch(setSelectedNetworkConfigurationId(''));
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
|
@ -13,7 +13,7 @@ const mockState = {
|
||||
ticker: 'ETH',
|
||||
type: 'localhost',
|
||||
},
|
||||
frequentRpcListDetail: [],
|
||||
networkConfigurations: {},
|
||||
},
|
||||
appState: {
|
||||
networksTabSelectedRpcUrl: 'http://localhost:8545',
|
||||
|
@ -24,7 +24,6 @@ const state = {
|
||||
address: '0xAddress',
|
||||
},
|
||||
},
|
||||
frequentRpcListDetail: [],
|
||||
cachedBalances: {},
|
||||
addressBook: [
|
||||
{
|
||||
|
@ -59,7 +59,7 @@ export default function TokenDetailsPage() {
|
||||
type: state.metamask.provider.type,
|
||||
}));
|
||||
|
||||
const { nickname: networkNickname, type: networkType } = currentNetwork;
|
||||
const { nickname, type: networkType } = currentNetwork;
|
||||
|
||||
const [copied, handleCopy] = useCopyToClipboard();
|
||||
|
||||
@ -180,7 +180,7 @@ export default function TokenDetailsPage() {
|
||||
color={TextColor.textDefault}
|
||||
>
|
||||
{networkType === NETWORK_TYPES.RPC
|
||||
? networkNickname ?? t('privateNetwork')
|
||||
? nickname ?? t('privateNetwork')
|
||||
: t(networkType)}
|
||||
</Typography>
|
||||
{aggregators && (
|
||||
|
@ -649,11 +649,7 @@ export function getTargetSubjectMetadata(state, origin) {
|
||||
}
|
||||
|
||||
export function getRpcPrefsForCurrentProvider(state) {
|
||||
const { frequentRpcListDetail, provider } = state.metamask;
|
||||
const selectRpcInfo = frequentRpcListDetail.find(
|
||||
(rpcInfo) => rpcInfo.rpcUrl === provider.rpcUrl,
|
||||
);
|
||||
const { rpcPrefs = {} } = selectRpcInfo || {};
|
||||
const { provider: { rpcPrefs = {} } = {} } = state.metamask;
|
||||
return rpcPrefs;
|
||||
}
|
||||
|
||||
@ -1109,19 +1105,19 @@ export function getRemoveNftMessage(state) {
|
||||
* @returns string
|
||||
*/
|
||||
export function getNewNetworkAdded(state) {
|
||||
return state.appState.newNetworkAdded;
|
||||
return state.appState.newNetworkAddedName;
|
||||
}
|
||||
|
||||
export function getNetworksTabSelectedRpcUrl(state) {
|
||||
return state.appState.networksTabSelectedRpcUrl;
|
||||
export function getNetworksTabSelectedNetworkConfigurationId(state) {
|
||||
return state.appState.selectedNetworkConfigurationId;
|
||||
}
|
||||
|
||||
export function getProvider(state) {
|
||||
return state.metamask.provider;
|
||||
}
|
||||
|
||||
export function getFrequentRpcListDetail(state) {
|
||||
return state.metamask.frequentRpcListDetail;
|
||||
export function getNetworkConfigurations(state) {
|
||||
return state.metamask.networkConfigurations;
|
||||
}
|
||||
|
||||
export function getIsOptimism(state) {
|
||||
|
@ -16,6 +16,93 @@ describe('Selectors', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getNewNetworkAdded', () => {
|
||||
it('returns undefined if newNetworkAddedName is undefined', () => {
|
||||
expect(selectors.getNewNetworkAdded({ appState: {} })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns newNetworkAddedName', () => {
|
||||
expect(
|
||||
selectors.getNewNetworkAdded({
|
||||
appState: { newNetworkAddedName: 'test-chain' },
|
||||
}),
|
||||
).toStrictEqual('test-chain');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getRpcPrefsForCurrentProvider', () => {
|
||||
it('returns an empty object if state.metamask.provider is undefined', () => {
|
||||
expect(
|
||||
selectors.getRpcPrefsForCurrentProvider({ metamask: {} }),
|
||||
).toStrictEqual({});
|
||||
});
|
||||
it('returns rpcPrefs from the provider', () => {
|
||||
expect(
|
||||
selectors.getRpcPrefsForCurrentProvider({
|
||||
metamask: {
|
||||
provider: {
|
||||
rpcPrefs: { blockExplorerUrl: 'https://test-block-explorer' },
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toStrictEqual({ blockExplorerUrl: 'https://test-block-explorer' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getNetworksTabSelectedNetworkConfigurationId', () => {
|
||||
it('returns undefined if selectedNetworkConfigurationId is undefined', () => {
|
||||
expect(
|
||||
selectors.getNetworksTabSelectedNetworkConfigurationId({
|
||||
appState: {},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns selectedNetworkConfigurationId', () => {
|
||||
expect(
|
||||
selectors.getNetworksTabSelectedNetworkConfigurationId({
|
||||
appState: {
|
||||
selectedNetworkConfigurationId: 'testNetworkConfigurationId',
|
||||
},
|
||||
}),
|
||||
).toStrictEqual('testNetworkConfigurationId');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getNetworkConfigurations', () => {
|
||||
it('returns undefined if state.metamask.networkConfigurations is undefined', () => {
|
||||
expect(
|
||||
selectors.getNetworkConfigurations({
|
||||
metamask: {},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns networkConfigurations', () => {
|
||||
const networkConfigurations = {
|
||||
testNetworkConfigurationId1: {
|
||||
rpcUrl: 'https://mock-rpc-url-1',
|
||||
chainId: '0xtest',
|
||||
ticker: 'TEST',
|
||||
id: 'testNetworkConfigurationId1',
|
||||
},
|
||||
testNetworkConfigurationId2: {
|
||||
rpcUrl: 'https://mock-rpc-url-2',
|
||||
chainId: '0x1337',
|
||||
ticker: 'RPC',
|
||||
id: 'testNetworkConfigurationId2',
|
||||
},
|
||||
};
|
||||
expect(
|
||||
selectors.getNetworkConfigurations({
|
||||
metamask: {
|
||||
networkConfigurations,
|
||||
},
|
||||
}),
|
||||
).toStrictEqual(networkConfigurations);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isHardwareWallet', () => {
|
||||
it('returns false if it is not a HW wallet', () => {
|
||||
mockState.metamask.keyrings[0].type = HardwareKeyringTypes.imported;
|
||||
|
@ -74,11 +74,12 @@ export const CLEAR_PENDING_TOKENS = 'CLEAR_PENDING_TOKENS';
|
||||
|
||||
export const SET_FIRST_TIME_FLOW_TYPE = 'SET_FIRST_TIME_FLOW_TYPE';
|
||||
|
||||
export const SET_SELECTED_SETTINGS_RPC_URL = 'SET_SELECTED_SETTINGS_RPC_URL';
|
||||
export const SET_SELECTED_NETWORK_CONFIGURATION_ID =
|
||||
'SET_SELECTED_NETWORK_CONFIGURATION_ID';
|
||||
export const SET_NEW_NETWORK_ADDED = 'SET_NEW_NETWORK_ADDED';
|
||||
|
||||
export const SET_NEW_NFT_ADDED_MESSAGE = 'SET_NEW_NFT_ADDED_MESSAGE';
|
||||
export const SET_REMOVE_NFT_MESSAGE = 'SET_REMOVE_NFT_MESSAGE';
|
||||
export const SET_NEW_CUSTOM_NETWORK_ADDED = 'SET_NEW_CUSTOM_NETWORK_ADDED';
|
||||
|
||||
export const LOADING_METHOD_DATA_STARTED = 'LOADING_METHOD_DATA_STARTED';
|
||||
export const LOADING_METHOD_DATA_FINISHED = 'LOADING_METHOD_DATA_FINISHED';
|
||||
|
@ -6,6 +6,8 @@ import MetaMaskController from '../../app/scripts/metamask-controller';
|
||||
import { TransactionStatus } from '../../shared/constants/transaction';
|
||||
import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets';
|
||||
import { GAS_LIMITS } from '../../shared/constants/gas';
|
||||
import { ORIGIN_METAMASK } from '../../shared/constants/app';
|
||||
import { EVENT } from '../../shared/constants/metametrics';
|
||||
import * as actions from './actions';
|
||||
import { _setBackgroundConnection } from './action-queue';
|
||||
|
||||
@ -1199,30 +1201,38 @@ describe('Actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setRpcTarget', () => {
|
||||
describe('#setActiveNetwork', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('calls setRpcTarget', async () => {
|
||||
it('calls setActiveNetwork in the background with the correct arguments', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
background.setCustomRpc.callsFake((_, __, ___, ____, cb) => cb());
|
||||
const setCurrentNetworkStub = sinon.stub().callsFake((_, cb) => cb());
|
||||
|
||||
_setBackgroundConnection(background);
|
||||
background.getApi.returns({
|
||||
setActiveNetwork: setCurrentNetworkStub,
|
||||
});
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
await store.dispatch(actions.setRpcTarget('http://localhost:8545'));
|
||||
expect(background.setCustomRpc.callCount).toStrictEqual(1);
|
||||
await store.dispatch(actions.setActiveNetwork('networkConfigurationId'));
|
||||
expect(
|
||||
setCurrentNetworkStub.calledOnceWith('networkConfigurationId'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('displays warning when setRpcTarget throws', async () => {
|
||||
it('displays warning when setActiveNetwork throws', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
background.setCustomRpc.callsFake((_, __, ___, ____, cb) =>
|
||||
cb(new Error('error')),
|
||||
);
|
||||
const setCurrentNetworkStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb(new Error('error')));
|
||||
|
||||
_setBackgroundConnection(background);
|
||||
background.getApi.returns({
|
||||
setActiveNetwork: setCurrentNetworkStub,
|
||||
});
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
@ -1231,11 +1241,287 @@ describe('Actions', () => {
|
||||
},
|
||||
];
|
||||
|
||||
await store.dispatch(actions.setRpcTarget());
|
||||
await store.dispatch(actions.setActiveNetwork());
|
||||
expect(store.getActions()).toStrictEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#editAndSetNetworkConfiguration', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('removes then re-adds the given network configuration', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const removeNetworkConfigurationStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb());
|
||||
|
||||
const upsertNetworkConfigurationStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb());
|
||||
|
||||
background.getApi.returns({
|
||||
removeNetworkConfiguration: removeNetworkConfigurationStub,
|
||||
upsertNetworkConfiguration: upsertNetworkConfigurationStub,
|
||||
});
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
const networkConfiguration = {
|
||||
rpcUrl: 'newRpc',
|
||||
chainId: '0x',
|
||||
ticker: 'ETH',
|
||||
nickname: 'nickname',
|
||||
rpcPrefs: { blockExplorerUrl: 'etherscan.io' },
|
||||
};
|
||||
|
||||
await store.dispatch(
|
||||
actions.editAndSetNetworkConfiguration(
|
||||
{
|
||||
...networkConfiguration,
|
||||
networkConfigurationId: 'networkConfigurationId',
|
||||
},
|
||||
{ source: 'https://test-dapp.com' },
|
||||
),
|
||||
);
|
||||
expect(
|
||||
removeNetworkConfigurationStub.calledOnceWith('networkConfigurationId'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
upsertNetworkConfigurationStub.calledOnceWith(networkConfiguration, {
|
||||
setActive: true,
|
||||
referrer: ORIGIN_METAMASK,
|
||||
source: 'https://test-dapp.com',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('displays warning when removeNetworkConfiguration throws', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const upsertNetworkConfigurationStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb());
|
||||
|
||||
const removeNetworkConfigurationStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb(new Error('error')));
|
||||
|
||||
background.getApi.returns({
|
||||
removeNetworkConfiguration: removeNetworkConfigurationStub,
|
||||
upsertNetworkConfiguration: upsertNetworkConfigurationStub,
|
||||
});
|
||||
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'DISPLAY_WARNING', payload: 'Had a problem removing network!' },
|
||||
];
|
||||
|
||||
await store.dispatch(
|
||||
actions.editAndSetNetworkConfiguration(
|
||||
{
|
||||
networkConfigurationId: 'networkConfigurationId',
|
||||
rpcUrl: 'newRpc',
|
||||
chainId: '0x',
|
||||
ticker: 'ETH',
|
||||
nickname: 'nickname',
|
||||
rpcPrefs: { blockExplorerUrl: 'etherscan.io' },
|
||||
},
|
||||
{ source: 'https://test-dapp.com' },
|
||||
),
|
||||
);
|
||||
expect(store.getActions()).toStrictEqual(expectedActions);
|
||||
});
|
||||
|
||||
it('throws when no options object is passed as a second argument', async () => {
|
||||
const store = mockStore();
|
||||
await expect(() =>
|
||||
store.dispatch(
|
||||
actions.editAndSetNetworkConfiguration({
|
||||
networkConfigurationId: 'networkConfigurationId',
|
||||
rpcUrl: 'newRpc',
|
||||
chainId: '0x',
|
||||
ticker: 'ETH',
|
||||
nickname: 'nickname',
|
||||
rpcPrefs: { blockExplorerUrl: 'etherscan.io' },
|
||||
}),
|
||||
),
|
||||
).toThrow(
|
||||
"Cannot destructure property 'source' of 'undefined' as it is undefined.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upsertNetworkConfiguration', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('calls upsertNetworkConfiguration in the background with the correct arguments', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const upsertNetworkConfigurationStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb());
|
||||
|
||||
background.getApi.returns({
|
||||
upsertNetworkConfiguration: upsertNetworkConfigurationStub,
|
||||
});
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
const networkConfiguration = {
|
||||
rpcUrl: 'newRpc',
|
||||
chainId: '0x',
|
||||
ticker: 'ETH',
|
||||
nickname: 'nickname',
|
||||
rpcPrefs: { blockExplorerUrl: 'etherscan.io' },
|
||||
};
|
||||
|
||||
await store.dispatch(
|
||||
actions.upsertNetworkConfiguration(networkConfiguration, {
|
||||
source: EVENT.SOURCE.NETWORK.CUSTOM_NETWORK_FORM,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
upsertNetworkConfigurationStub.calledOnceWith(networkConfiguration, {
|
||||
referrer: ORIGIN_METAMASK,
|
||||
source: EVENT.SOURCE.NETWORK.CUSTOM_NETWORK_FORM,
|
||||
setActive: undefined,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('throws when no options object is passed as a second argument', async () => {
|
||||
const store = mockStore();
|
||||
await expect(() =>
|
||||
store.dispatch(
|
||||
actions.upsertNetworkConfiguration({
|
||||
networkConfigurationId: 'networkConfigurationId',
|
||||
rpcUrl: 'newRpc',
|
||||
chainId: '0x',
|
||||
ticker: 'ETH',
|
||||
nickname: 'nickname',
|
||||
rpcPrefs: { blockExplorerUrl: 'etherscan.io' },
|
||||
}),
|
||||
),
|
||||
).toThrow(
|
||||
"Cannot destructure property 'setActive' of 'undefined' as it is undefined.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#requestUserApproval', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('calls requestUserApproval in the background with the correct arguments', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const requestUserApprovalStub = sinon.stub().callsFake((_, cb) => cb());
|
||||
|
||||
background.getApi.returns({
|
||||
requestUserApproval: requestUserApprovalStub,
|
||||
});
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
const networkConfiguration = {
|
||||
rpcUrl: 'newRpc',
|
||||
chainId: '0x',
|
||||
ticker: 'ETH',
|
||||
nickname: 'nickname',
|
||||
rpcPrefs: { blockExplorerUrl: 'etherscan.io' },
|
||||
};
|
||||
|
||||
await store.dispatch(
|
||||
actions.requestUserApproval({
|
||||
origin: ORIGIN_METAMASK,
|
||||
type: 'test',
|
||||
requestData: networkConfiguration,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
requestUserApprovalStub.calledOnceWith({
|
||||
origin: ORIGIN_METAMASK,
|
||||
type: 'test',
|
||||
requestData: networkConfiguration,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#removeNetworkConfiguration', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('calls removeNetworkConfiguration in the background with the correct arguments', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const removeNetworkConfigurationStub = sinon
|
||||
.stub()
|
||||
.callsFake((_, cb) => cb());
|
||||
|
||||
background.getApi.returns({
|
||||
removeNetworkConfiguration: removeNetworkConfigurationStub,
|
||||
});
|
||||
_setBackgroundConnection(background.getApi());
|
||||
|
||||
await store.dispatch(
|
||||
actions.removeNetworkConfiguration('testNetworkConfigurationId'),
|
||||
);
|
||||
|
||||
expect(
|
||||
removeNetworkConfigurationStub.calledOnceWith(
|
||||
'testNetworkConfigurationId',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setSelectedNetworkConfigurationId', () => {
|
||||
it('sets appState.networkConfigurationId to provided value', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const networkConfigurationId = 'testNetworkConfigurationId';
|
||||
|
||||
store.dispatch(
|
||||
actions.setSelectedNetworkConfigurationId(networkConfigurationId),
|
||||
);
|
||||
|
||||
const resultantActions = store.getActions();
|
||||
|
||||
expect(resultantActions[0]).toStrictEqual({
|
||||
type: 'SET_SELECTED_NETWORK_CONFIGURATION_ID',
|
||||
payload: networkConfigurationId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setNewNetworkAdded', () => {
|
||||
it('sets appState.setNewNetworkAdded to provided value', async () => {
|
||||
const store = mockStore();
|
||||
|
||||
const newNetworkAddedDetails = {
|
||||
networkConfigurationId: 'testNetworkConfigurationId',
|
||||
nickname: 'test-chain',
|
||||
};
|
||||
|
||||
store.dispatch(actions.setNewNetworkAdded(newNetworkAddedDetails));
|
||||
|
||||
const resultantActions = store.getActions();
|
||||
|
||||
expect(resultantActions[0]).toStrictEqual({
|
||||
type: 'SET_NEW_NETWORK_ADDED',
|
||||
payload: newNetworkAddedDetails,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addToAddressBook', () => {
|
||||
it('calls setAddressBook', async () => {
|
||||
const store = mockStore();
|
||||
|
@ -65,7 +65,6 @@ import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
import { NOTIFICATIONS_EXPIRATION_DELAY } from '../helpers/constants/notifications';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { setNewCustomNetworkAdded } from '../ducks/app/app';
|
||||
import {
|
||||
fetchLocale,
|
||||
loadRelativeTimeFormatLocaleData,
|
||||
@ -2457,47 +2456,75 @@ export function setProviderType(
|
||||
};
|
||||
}
|
||||
|
||||
export function updateAndSetCustomRpc(
|
||||
newRpcUrl: string,
|
||||
chainId: string,
|
||||
ticker: EtherDenomination,
|
||||
nickname: string,
|
||||
rpcPrefs: RPCDefinition['rpcPrefs'],
|
||||
export function upsertNetworkConfiguration(
|
||||
{
|
||||
rpcUrl,
|
||||
chainId,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
ticker = EtherDenomination.ETH,
|
||||
}: {
|
||||
rpcUrl: string;
|
||||
chainId: string;
|
||||
nickname: string;
|
||||
rpcPrefs: RPCDefinition['rpcPrefs'];
|
||||
ticker: string;
|
||||
},
|
||||
{
|
||||
setActive,
|
||||
source,
|
||||
}: {
|
||||
setActive: boolean;
|
||||
source: string;
|
||||
},
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
||||
return async (dispatch) => {
|
||||
log.debug(
|
||||
`background.updateAndSetCustomRpc: ${newRpcUrl} ${chainId} ${
|
||||
ticker ?? EtherDenomination.ETH
|
||||
} ${nickname}`,
|
||||
`background.upsertNetworkConfiguration: ${rpcUrl} ${chainId} ${ticker} ${nickname}`,
|
||||
);
|
||||
|
||||
let networkConfigurationId;
|
||||
try {
|
||||
await submitRequestToBackground('updateAndSetCustomRpc', [
|
||||
newRpcUrl,
|
||||
chainId,
|
||||
ticker ?? EtherDenomination.ETH,
|
||||
nickname || newRpcUrl,
|
||||
rpcPrefs,
|
||||
]);
|
||||
networkConfigurationId = await submitRequestToBackground(
|
||||
'upsertNetworkConfiguration',
|
||||
[
|
||||
{ rpcUrl, chainId, ticker, nickname: nickname || rpcUrl, rpcPrefs },
|
||||
{ setActive, source, referrer: ORIGIN_METAMASK },
|
||||
],
|
||||
);
|
||||
} catch (error) {
|
||||
logErrorWithMessage(error);
|
||||
dispatch(displayWarning('Had a problem changing networks!'));
|
||||
log.error(error);
|
||||
dispatch(displayWarning('Had a problem adding network!'));
|
||||
}
|
||||
return networkConfigurationId;
|
||||
};
|
||||
}
|
||||
|
||||
export function editRpc(
|
||||
oldRpcUrl: string,
|
||||
newRpcUrl: string,
|
||||
chainId: string,
|
||||
ticker: EtherDenomination,
|
||||
nickname: string,
|
||||
rpcPrefs: RPCDefinition['rpcPrefs'],
|
||||
export function editAndSetNetworkConfiguration(
|
||||
{
|
||||
networkConfigurationId,
|
||||
rpcUrl,
|
||||
chainId,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
ticker = EtherDenomination.ETH,
|
||||
}: {
|
||||
networkConfigurationId: string;
|
||||
rpcUrl: string;
|
||||
chainId: string;
|
||||
nickname: string;
|
||||
rpcPrefs: RPCDefinition['rpcPrefs'];
|
||||
ticker: string;
|
||||
},
|
||||
{ source }: { source: string },
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
||||
log.debug(`background.delRpcTarget: ${oldRpcUrl}`);
|
||||
return async (dispatch) => {
|
||||
log.debug(
|
||||
`background.removeNetworkConfiguration: ${networkConfigurationId}`,
|
||||
);
|
||||
try {
|
||||
submitRequestToBackground('delCustomRpc', [oldRpcUrl]);
|
||||
await submitRequestToBackground('removeNetworkConfiguration', [
|
||||
networkConfigurationId,
|
||||
]);
|
||||
} catch (error) {
|
||||
logErrorWithMessage(error);
|
||||
dispatch(displayWarning('Had a problem removing network!'));
|
||||
@ -2505,12 +2532,15 @@ export function editRpc(
|
||||
}
|
||||
|
||||
try {
|
||||
await submitRequestToBackground('updateAndSetCustomRpc', [
|
||||
newRpcUrl,
|
||||
chainId,
|
||||
ticker ?? EtherDenomination.ETH,
|
||||
nickname || newRpcUrl,
|
||||
rpcPrefs,
|
||||
await submitRequestToBackground('upsertNetworkConfiguration', [
|
||||
{
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname: nickname || rpcUrl,
|
||||
rpcPrefs,
|
||||
},
|
||||
{ setActive: true, referrer: ORIGIN_METAMASK, source },
|
||||
]);
|
||||
} catch (error) {
|
||||
logErrorWithMessage(error);
|
||||
@ -2519,23 +2549,14 @@ export function editRpc(
|
||||
};
|
||||
}
|
||||
|
||||
export function setRpcTarget(
|
||||
newRpcUrl: string,
|
||||
chainId: string,
|
||||
ticker?: EtherDenomination,
|
||||
nickname?: string,
|
||||
export function setActiveNetwork(
|
||||
networkConfigurationId: string,
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
||||
log.debug(
|
||||
`background.setRpcTarget: ${newRpcUrl} ${chainId} ${ticker} ${nickname}`,
|
||||
);
|
||||
|
||||
return async (dispatch) => {
|
||||
log.debug(`background.setActiveNetwork: ${networkConfigurationId}`);
|
||||
try {
|
||||
await submitRequestToBackground('setCustomRpc', [
|
||||
newRpcUrl,
|
||||
chainId,
|
||||
ticker ?? EtherDenomination.ETH,
|
||||
nickname || newRpcUrl,
|
||||
await submitRequestToBackground('setActiveNetwork', [
|
||||
networkConfigurationId,
|
||||
]);
|
||||
} catch (error) {
|
||||
logErrorWithMessage(error);
|
||||
@ -2560,21 +2581,27 @@ export function rollbackToPreviousProvider(): ThunkAction<
|
||||
};
|
||||
}
|
||||
|
||||
export function delRpcTarget(
|
||||
oldRpcUrl: string,
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return (dispatch: MetaMaskReduxDispatch) => {
|
||||
log.debug(`background.delRpcTarget: ${oldRpcUrl}`);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
callBackgroundMethod('delCustomRpc', [oldRpcUrl], (err) => {
|
||||
if (err) {
|
||||
logErrorWithMessage(err);
|
||||
dispatch(displayWarning('Had a problem removing network!'));
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
export function removeNetworkConfiguration(
|
||||
networkConfigurationId: string,
|
||||
): ThunkAction<Promise<void>, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return (dispatch) => {
|
||||
log.debug(
|
||||
`background.removeNetworkConfiguration: ${networkConfigurationId}`,
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
callBackgroundMethod(
|
||||
'removeNetworkConfiguration',
|
||||
[networkConfigurationId],
|
||||
(err) => {
|
||||
if (err) {
|
||||
logErrorWithMessage(err);
|
||||
dispatch(displayWarning('Had a problem removing network!'));
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -3784,21 +3811,25 @@ export function setFirstTimeFlowType(
|
||||
};
|
||||
}
|
||||
|
||||
export function setSelectedSettingsRpcUrl(
|
||||
newRpcUrl: string,
|
||||
export function setSelectedNetworkConfigurationId(
|
||||
networkConfigurationId: string,
|
||||
): PayloadAction<string> {
|
||||
return {
|
||||
type: actionConstants.SET_SELECTED_SETTINGS_RPC_URL,
|
||||
payload: newRpcUrl,
|
||||
type: actionConstants.SET_SELECTED_NETWORK_CONFIGURATION_ID,
|
||||
payload: networkConfigurationId,
|
||||
};
|
||||
}
|
||||
|
||||
export function setNewNetworkAdded(
|
||||
newNetworkAdded: string,
|
||||
): PayloadAction<string> {
|
||||
export function setNewNetworkAdded({
|
||||
networkConfigurationId,
|
||||
nickname,
|
||||
}: {
|
||||
networkConfigurationId: string;
|
||||
nickname: string;
|
||||
}): PayloadAction<object> {
|
||||
return {
|
||||
type: actionConstants.SET_NEW_NETWORK_ADDED,
|
||||
payload: newNetworkAdded,
|
||||
payload: { networkConfigurationId, nickname },
|
||||
};
|
||||
}
|
||||
|
||||
@ -4592,36 +4623,27 @@ export function cancelQRHardwareSignRequest(): ThunkAction<
|
||||
};
|
||||
}
|
||||
|
||||
export function addCustomNetwork(
|
||||
customRpc: RPCDefinition,
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
export function requestUserApproval({
|
||||
origin,
|
||||
type,
|
||||
requestData,
|
||||
}: {
|
||||
origin: string;
|
||||
type: string;
|
||||
requestData: object;
|
||||
}): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
||||
try {
|
||||
dispatch(setNewCustomNetworkAdded(customRpc));
|
||||
await submitRequestToBackground('addCustomNetwork', [
|
||||
customRpc,
|
||||
generateActionId(),
|
||||
await submitRequestToBackground('requestUserApproval', [
|
||||
{
|
||||
origin,
|
||||
type,
|
||||
requestData,
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
logErrorWithMessage(error);
|
||||
dispatch(displayWarning('Had a problem changing networks!'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function requestAddNetworkApproval(
|
||||
customRpc: RPCDefinition,
|
||||
originIsMetaMask: boolean,
|
||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
||||
try {
|
||||
await submitRequestToBackground('requestAddNetworkApproval', [
|
||||
customRpc,
|
||||
originIsMetaMask,
|
||||
]);
|
||||
} catch (error) {
|
||||
logErrorWithMessage(error);
|
||||
dispatch(displayWarning('Had a problem changing networks!'));
|
||||
dispatch(displayWarning('Had trouble requesting user approval'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user