1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

NetworkController: Split network into networkId and networkStatus (#17556)

The `network` store of the network controller crams two types of data
into one place. It roughly tracks whether we have enough information to
make requests to the network and whether the network is capable of
receiving requests, but it also stores the ID of the network (as
obtained via `net_version`).

Generally we shouldn't be using the network ID for anything, as it has
been completely replaced by chain ID, which all custom RPC endpoints
have been required to support for over a year now. However, as the
network ID is used in various places within the extension codebase,
removing it entirely would be a non-trivial effort. So, minimally, this
commit splits `network` into two stores: `networkId` and
`networkStatus`. But it also expands the concept of network status.

Previously, the network was in one of two states: "loading" and
"not-loading". But now it can be in one of four states:

- `available`: The network is able to receive and respond to requests.
- `unavailable`: The network is not able to receive and respond to
  requests for unknown reasons.
- `blocked`: The network is actively blocking requests based on the
  user's geolocation. (This is specific to Infura.)
- `unknown`: We don't know whether the network can receive and respond
  to requests, either because we haven't checked or we tried to check
  and were unsuccessful.

This commit also changes how the network status is determined —
specifically, how many requests are used to determine that status, when
they occur, and whether they are awaited. Previously, the network
controller would make 2 to 3 requests during the course of running
`lookupNetwork`.

* First, if it was an Infura network, it would make a request for
  `eth_blockNumber` to determine whether Infura was blocking requests or
  not, then emit an appropriate event. This operation was not awaited.
* Then, regardless of the network, it would fetch the network ID via
  `net_version`. This operation was awaited.
* Finally, regardless of the network, it would fetch the latest block
  via `eth_getBlockByNumber`, then use the result to determine whether
  the network supported EIP-1559. This operation was awaited.

Now:

* One fewer request is made, specifically `eth_blockNumber`, as we don't
  need to make an extra request to determine whether Infura is blocking
  requests; we can reuse `eth_getBlockByNumber`;
* All requests are awaited, which makes `lookupNetwork` run fully
  in-band instead of partially out-of-band; and
* Both requests for `net_version` and `eth_getBlockByNumber` are
  performed in parallel to make `lookupNetwork` run slightly faster.
This commit is contained in:
Elliot Winkler 2023-03-30 16:49:12 -06:00 committed by GitHub
parent a53b9fb489
commit ed3cc404f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 3809 additions and 1985 deletions

View File

@ -223,7 +223,8 @@ browser.runtime.onConnectExternal.addListener(async (...args) => {
* @property {object} provider - The current selected network provider. * @property {object} provider - The current selected network provider.
* @property {string} provider.rpcUrl - The address for the RPC API, if using an RPC API. * @property {string} provider.rpcUrl - The address for the RPC API, if using an RPC API.
* @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks. * @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
* @property {string} network - A stringified number of the current network ID. * @property {string} networkId - The stringified number of the current network ID.
* @property {string} networkStatus - Either "unknown", "available", "unavailable", or "blocked", depending on the status of the currently selected network.
* @property {object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values. * @property {object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
* @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string. * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string.
* @property {TransactionMeta[]} currentNetworkTxList - An array of transactions associated with the currently selected network. * @property {TransactionMeta[]} currentNetworkTxList - An array of transactions associated with the currently selected network.

View File

@ -17,8 +17,8 @@ import NetworkController, { NetworkControllerEventTypes } from './network';
import PreferencesController from './preferences'; import PreferencesController from './preferences';
describe('DetectTokensController', function () { describe('DetectTokensController', function () {
const sandbox = sinon.createSandbox(); let sandbox,
let assetsContractController, assetsContractController,
keyringMemStore, keyringMemStore,
network, network,
preferences, preferences,
@ -32,87 +32,94 @@ describe('DetectTokensController', function () {
getAccounts: noop, getAccounts: noop,
}; };
const infuraProjectId = 'infura-project-id';
beforeEach(async function () { beforeEach(async function () {
keyringMemStore = new ObservableStore({ isUnlocked: false }); sandbox = sinon.createSandbox();
const networkControllerMessenger = new ControllerMessenger(); // Disable all requests, even those to localhost
network = new NetworkController({ nock.disableNetConnect();
messenger: networkControllerMessenger, nock('https://mainnet.infura.io')
infuraProjectId: 'foo', .post(`/v3/${infuraProjectId}`)
}); .reply(200, (_uri, requestBody) => {
network.initializeProvider(networkControllerProviderConfig); if (requestBody.method === 'eth_getBlockByNumber') {
provider = network.getProviderAndBlockTracker().provider; return {
id: requestBody.id,
const tokenListMessenger = new ControllerMessenger().getRestricted({ jsonrpc: '2.0',
name: 'TokenListController', result: {
}); number: '0x42',
tokenListController = new TokenListController({
chainId: '1',
preventPollingOnNetworkRestart: false,
onNetworkStateChange: sinon.spy(),
onPreferencesStateChange: sinon.spy(),
messenger: tokenListMessenger,
});
await tokenListController.start();
preferences = new PreferencesController({
network,
provider,
tokenListController,
onInfuraIsBlocked: sinon.stub(),
onInfuraIsUnblocked: sinon.stub(),
});
preferences.setAddresses([
'0x7e57e2',
'0xbc86727e770de68b1060c91f6bb6945c73e10388',
]);
preferences.setUseTokenDetection(true);
tokensController = new TokensController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: (cb) =>
network.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
}, },
}; };
return cb(modifiedNetworkState); }
}),
});
assetsContractController = new AssetsContractController({ if (requestBody.method === 'eth_blockNumber') {
onPreferencesStateChange: preferences.store.subscribe.bind( return {
preferences.store, id: requestBody.id,
), jsonrpc: '2.0',
onNetworkStateChange: (cb) => result: '0x42',
networkControllerMessenger.subscribe( };
NetworkControllerEventTypes.NetworkDidChange, }
() => {
const networkState = network.store.getState();
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
chainId: convertHexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
},
),
});
sandbox throw new Error(`(Infura) Mock not defined for ${requestBody.method}`);
.stub(network, '_getLatestBlock') })
.callsFake(() => Promise.resolve({})); .persist();
sandbox nock('https://sepolia.infura.io')
.stub(tokensController, '_instantiateNewEthersProvider') .post(`/v3/${infuraProjectId}`)
.returns(null); .reply(200, (_uri, requestBody) => {
sandbox if (requestBody.method === 'eth_getBlockByNumber') {
.stub(tokensController, '_detectIsERC721') return {
.returns(Promise.resolve(false)); id: requestBody.id,
jsonrpc: '2.0',
result: {
number: '0x42',
},
};
}
if (requestBody.method === 'eth_blockNumber') {
return {
id: requestBody.id,
jsonrpc: '2.0',
result: '0x42',
};
}
throw new Error(`(Infura) Mock not defined for ${requestBody.method}`);
})
.persist();
nock('http://localhost:8545')
.post('/')
.reply(200, (_uri, requestBody) => {
if (requestBody.method === 'eth_getBlockByNumber') {
return {
id: requestBody.id,
jsonrpc: '2.0',
result: {
number: '0x42',
},
};
}
if (requestBody.method === 'eth_blockNumber') {
return {
id: requestBody.id,
jsonrpc: '2.0',
result: '0x42',
};
}
if (requestBody.method === 'net_version') {
return {
id: requestBody.id,
jsonrpc: '2.0',
result: '1337',
};
}
throw new Error(
`(localhost) Mock not defined for ${requestBody.method}`,
);
})
.persist();
nock('https://token-api.metaswap.codefi.network') nock('https://token-api.metaswap.codefi.network')
.get(`/tokens/1`) .get(`/tokens/1`)
.reply(200, [ .reply(200, [
@ -183,9 +190,82 @@ describe('DetectTokensController', function () {
.get(`/tokens/3`) .get(`/tokens/3`)
.reply(200, { error: 'ChainId 3 is not supported' }) .reply(200, { error: 'ChainId 3 is not supported' })
.persist(); .persist();
keyringMemStore = new ObservableStore({ isUnlocked: false });
const networkControllerMessenger = new ControllerMessenger();
network = new NetworkController({
messenger: networkControllerMessenger,
infuraProjectId,
});
await network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
const tokenListMessenger = new ControllerMessenger().getRestricted({
name: 'TokenListController',
});
tokenListController = new TokenListController({
chainId: '1',
preventPollingOnNetworkRestart: false,
onNetworkStateChange: sinon.spy(),
onPreferencesStateChange: sinon.spy(),
messenger: tokenListMessenger,
});
await tokenListController.start();
preferences = new PreferencesController({
network,
provider,
tokenListController,
onInfuraIsBlocked: sinon.stub(),
onInfuraIsUnblocked: sinon.stub(),
});
preferences.setAddresses([
'0x7e57e2',
'0xbc86727e770de68b1060c91f6bb6945c73e10388',
]);
preferences.setUseTokenDetection(true);
tokensController = new TokensController({
config: { provider },
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: (cb) =>
network.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
},
};
return cb(modifiedNetworkState);
}),
});
assetsContractController = new AssetsContractController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: (cb) =>
networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
() => {
const networkState = network.store.getState();
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
chainId: convertHexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
},
),
});
}); });
after(function () { afterEach(function () {
nock.enableNetConnect('localhost');
sandbox.restore(); sandbox.restore();
}); });

View File

@ -11,6 +11,8 @@ import EthQuery from 'eth-query';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { ControllerMessenger } from '@metamask/base-controller'; import { ControllerMessenger } from '@metamask/base-controller';
import { v4 as random } from 'uuid'; import { v4 as random } from 'uuid';
import { hasProperty, isPlainObject } from '@metamask/utils';
import { errorCodes } from 'eth-rpc-errors';
import { import {
INFURA_PROVIDER_TYPES, INFURA_PROVIDER_TYPES,
BUILT_IN_NETWORKS, BUILT_IN_NETWORKS,
@ -18,8 +20,8 @@ import {
TEST_NETWORK_TICKER_MAP, TEST_NETWORK_TICKER_MAP,
CHAIN_IDS, CHAIN_IDS,
NETWORK_TYPES, NETWORK_TYPES,
NetworkStatus,
} from '../../../../shared/constants/network'; } from '../../../../shared/constants/network';
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout';
import { import {
isPrefixedFormattedHexString, isPrefixedFormattedHexString,
isSafeChainId, isSafeChainId,
@ -36,40 +38,57 @@ import { createNetworkClient } from './create-network-client';
* @property {string} [nickname] - Personalized network name. * @property {string} [nickname] - Personalized network name.
*/ */
const env = process.env.METAMASK_ENV; function buildDefaultProviderConfigState() {
const fetchWithTimeout = getFetchWithTimeout(); if (process.env.IN_TEST) {
return {
type: NETWORK_TYPES.RPC,
rpcUrl: 'http://localhost:8545',
chainId: '0x539',
nickname: 'Localhost 8545',
ticker: 'ETH',
};
} else if (
process.env.METAMASK_DEBUG ||
process.env.METAMASK_ENV === 'test'
) {
return {
type: NETWORK_TYPES.GOERLI,
chainId: CHAIN_IDS.GOERLI,
ticker: TEST_NETWORK_TICKER_MAP.GOERLI,
};
}
const name = 'NetworkController'; return {
let defaultProviderConfigOpts;
if (process.env.IN_TEST) {
defaultProviderConfigOpts = {
type: NETWORK_TYPES.RPC,
rpcUrl: 'http://localhost:8545',
chainId: '0x539',
nickname: 'Localhost 8545',
};
} else if (process.env.METAMASK_DEBUG || env === 'test') {
defaultProviderConfigOpts = {
type: NETWORK_TYPES.GOERLI,
chainId: CHAIN_IDS.GOERLI,
ticker: TEST_NETWORK_TICKER_MAP.GOERLI,
};
} else {
defaultProviderConfigOpts = {
type: NETWORK_TYPES.MAINNET, type: NETWORK_TYPES.MAINNET,
chainId: CHAIN_IDS.MAINNET, chainId: CHAIN_IDS.MAINNET,
ticker: 'ETH',
}; };
} }
const defaultProviderConfig = { function buildDefaultNetworkIdState() {
ticker: 'ETH', return null;
...defaultProviderConfigOpts, }
};
const defaultNetworkDetailsState = { function buildDefaultNetworkStatusState() {
EIPS: { 1559: undefined }, return NetworkStatus.Unknown;
}; }
function buildDefaultNetworkDetailsState() {
return {
EIPS: {
1559: undefined,
},
};
}
function buildDefaultNetworkConfigurationsState() {
return {};
}
/**
* The name of the controller.
*/
const name = 'NetworkController';
/** /**
* The set of event types that this controller can publish via its messenger. * The set of event types that this controller can publish via its messenger.
@ -98,8 +117,6 @@ export const NetworkControllerEventTypes = {
}; };
export default class NetworkController extends EventEmitter { export default class NetworkController extends EventEmitter {
static defaultProviderConfig = defaultProviderConfig;
/** /**
* Construct a NetworkController. * Construct a NetworkController.
* *
@ -121,31 +138,33 @@ export default class NetworkController extends EventEmitter {
// create stores // create stores
this.providerStore = new ObservableStore( this.providerStore = new ObservableStore(
state.provider || { ...defaultProviderConfig }, state.provider || buildDefaultProviderConfigState(),
); );
this.previousProviderStore = new ObservableStore( this.previousProviderStore = new ObservableStore(
this.providerStore.getState(), this.providerStore.getState(),
); );
this.networkStore = new ObservableStore('loading'); this.networkIdStore = new ObservableStore(buildDefaultNetworkIdState());
// We need to keep track of a few details about the current network this.networkStatusStore = new ObservableStore(
// Ideally we'd merge this.networkStore with this new store, but doing so buildDefaultNetworkStatusState(),
// will require a decent sized refactor of how we're accessing network );
// state. Currently this is only used for detecting EIP 1559 support but // We need to keep track of a few details about the current network.
// can be extended to track other network details. // Ideally we'd merge this.networkStatusStore with this new store, but doing
// so will require a decent sized refactor of how we're accessing network
// state. Currently this is only used for detecting EIP-1559 support but can
// be extended to track other network details.
this.networkDetails = new ObservableStore( this.networkDetails = new ObservableStore(
state.networkDetails || { state.networkDetails || buildDefaultNetworkDetailsState(),
...defaultNetworkDetailsState,
},
); );
this.networkConfigurationsStore = new ObservableStore( this.networkConfigurationsStore = new ObservableStore(
state.networkConfigurations || {}, state.networkConfigurations || buildDefaultNetworkConfigurationsState(),
); );
this.store = new ComposedStore({ this.store = new ComposedStore({
provider: this.providerStore, provider: this.providerStore,
previousProviderStore: this.previousProviderStore, previousProviderStore: this.previousProviderStore,
network: this.networkStore, networkId: this.networkIdStore,
networkStatus: this.networkStatusStore,
networkDetails: this.networkDetails, networkDetails: this.networkDetails,
networkConfigurations: this.networkConfigurationsStore, networkConfigurations: this.networkConfigurationsStore,
}); });
@ -189,10 +208,12 @@ export default class NetworkController extends EventEmitter {
} }
/** /**
* Method to check if the block header contains fields that indicate EIP 1559 * Determines whether the network supports EIP-1559 by checking whether the
* support (baseFeePerGas). * latest block has a `baseFeePerGas` property, then updates state
* appropriately.
* *
* @returns {Promise<boolean>} true if current network supports EIP 1559 * @returns {Promise<boolean>} A promise that resolves to true if the network
* supports EIP-1559 and false otherwise.
*/ */
async getEIP1559Compatibility() { async getEIP1559Compatibility() {
const { EIPS } = this.networkDetails.getState(); const { EIPS } = this.networkDetails.getState();
@ -201,15 +222,28 @@ export default class NetworkController extends EventEmitter {
if (EIPS[1559] !== undefined) { if (EIPS[1559] !== undefined) {
return EIPS[1559]; return EIPS[1559];
} }
const latestBlock = await this._getLatestBlock(); const supportsEIP1559 = await this._determineEIP1559Compatibility();
const supportsEIP1559 = this.networkDetails.updateState({
latestBlock && latestBlock.baseFeePerGas !== undefined; EIPS: {
this._setNetworkEIPSupport(1559, supportsEIP1559); ...this.networkDetails.getState().EIPS,
1559: supportsEIP1559,
},
});
return supportsEIP1559; return supportsEIP1559;
} }
/**
* Captures information about the currently selected network namely,
* the network ID and whether the network supports EIP-1559 and then uses
* the results of these requests to determine the status of the network.
*/
async lookupNetwork() { async lookupNetwork() {
// Prevent firing when provider is not defined. const { chainId, type } = this.providerStore.getState();
let networkChanged = false;
let networkId;
let supportsEIP1559;
let networkStatus;
if (!this._provider) { if (!this._provider) {
log.warn( log.warn(
'NetworkController - lookupNetwork aborted due to missing provider', 'NetworkController - lookupNetwork aborted due to missing provider',
@ -217,46 +251,102 @@ export default class NetworkController extends EventEmitter {
return; return;
} }
const { chainId } = this.providerStore.getState();
if (!chainId) { if (!chainId) {
log.warn( log.warn(
'NetworkController - lookupNetwork aborted due to missing chainId', 'NetworkController - lookupNetwork aborted due to missing chainId',
); );
this._setNetworkState('loading'); this._resetNetworkId();
this._clearNetworkDetails(); this._resetNetworkStatus();
this._resetNetworkDetails();
return; return;
} }
// Ping the RPC endpoint so we can confirm that it works
const initialNetwork = this.networkStore.getState();
const { type } = this.providerStore.getState();
const isInfura = INFURA_PROVIDER_TYPES.includes(type); const isInfura = INFURA_PROVIDER_TYPES.includes(type);
if (isInfura) { const listener = () => {
this._checkInfuraAvailability(type); networkChanged = true;
} else { this.messenger.unsubscribe(
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked); NetworkControllerEventTypes.NetworkDidChange,
listener,
);
};
this.messenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
listener,
);
try {
const results = await Promise.all([
this._getNetworkId(),
this._determineEIP1559Compatibility(),
]);
networkId = results[0];
supportsEIP1559 = results[1];
networkStatus = NetworkStatus.Available;
} catch (error) {
if (hasProperty(error, 'code')) {
let responseBody;
try {
responseBody = JSON.parse(error.message);
} catch {
// error.message must not be JSON
}
if (
isPlainObject(responseBody) &&
responseBody.error === INFURA_BLOCKED_KEY
) {
networkStatus = NetworkStatus.Blocked;
} else if (error.code === errorCodes.rpc.internal) {
networkStatus = NetworkStatus.Unknown;
} else {
networkStatus = NetworkStatus.Unavailable;
}
} else {
log.warn(
'NetworkController - could not determine network status',
error,
);
networkStatus = NetworkStatus.Unknown;
}
} }
let networkVersion; if (networkChanged) {
let networkVersionError; // If the network has changed, then `lookupNetwork` either has been or is
try { // in the process of being called, so we don't need to go further.
networkVersion = await this._getNetworkId();
} catch (error) {
networkVersionError = error;
}
if (initialNetwork !== this.networkStore.getState()) {
return; return;
} }
this.messenger.unsubscribe(
NetworkControllerEventTypes.NetworkDidChange,
listener,
);
if (networkVersionError) { this.networkStatusStore.putState(networkStatus);
this._setNetworkState('loading');
// keep network details in sync with network state if (networkStatus === NetworkStatus.Available) {
this._clearNetworkDetails(); this.networkIdStore.putState(networkId);
this.networkDetails.updateState({
EIPS: {
...this.networkDetails.getState().EIPS,
1559: supportsEIP1559,
},
});
} else { } else {
this._setNetworkState(networkVersion); this._resetNetworkId();
// look up EIP-1559 support this._resetNetworkDetails();
await this.getEIP1559Compatibility(); }
if (isInfura) {
if (networkStatus === NetworkStatus.Available) {
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
} else if (networkStatus === NetworkStatus.Blocked) {
this.messenger.publish(NetworkControllerEventTypes.InfuraIsBlocked);
}
} else {
// Always publish infuraIsUnblocked regardless of network status to
// prevent consumers from being stuck in a blocked state if they were
// previously connected to an Infura network that was blocked
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
} }
} }
@ -319,13 +409,38 @@ export default class NetworkController extends EventEmitter {
// Private // Private
// //
/**
* Method to return the latest block for the current network
*
* @returns {object} Block header
*/
_getLatestBlock() {
const { provider } = this.getProviderAndBlockTracker();
const ethQuery = new EthQuery(provider);
return new Promise((resolve, reject) => {
ethQuery.sendAsync(
{ method: 'eth_getBlockByNumber', params: ['latest', false] },
(error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
},
);
});
}
/** /**
* Get the network ID for the current selected network * Get the network ID for the current selected network
* *
* @returns {string} The network ID for the current network. * @returns {string} The network ID for the current network.
*/ */
async _getNetworkId() { async _getNetworkId() {
const ethQuery = new EthQuery(this._provider); const { provider } = this.getProviderAndBlockTracker();
const ethQuery = new EthQuery(provider);
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { ethQuery.sendAsync({ method: 'net_version' }, (error, result) => {
if (error) { if (error) {
@ -338,49 +453,24 @@ export default class NetworkController extends EventEmitter {
} }
/** /**
* Method to return the latest block for the current network * Clears the stored network ID.
*
* @returns {object} Block header
*/ */
_getLatestBlock() { _resetNetworkId() {
return new Promise((resolve, reject) => { this.networkIdStore.putState(buildDefaultNetworkIdState());
const { provider } = this.getProviderAndBlockTracker();
const ethQuery = new EthQuery(provider);
ethQuery.sendAsync(
{ method: 'eth_getBlockByNumber', params: ['latest', false] },
(err, block) => {
if (err) {
return reject(err);
}
return resolve(block);
},
);
});
}
_setNetworkState(network) {
this.networkStore.putState(network);
} }
/** /**
* Set EIP support indication in the networkDetails store * Resets network status to the default ("unknown").
*
* @param {number} EIPNumber - The number of the EIP to mark support for
* @param {boolean} isSupported - True if the EIP is supported
*/ */
_setNetworkEIPSupport(EIPNumber, isSupported) { _resetNetworkStatus() {
this.networkDetails.putState({ this.networkStatusStore.putState(buildDefaultNetworkStatusState());
EIPS: {
[EIPNumber]: isSupported,
},
});
} }
/** /**
* Reset EIP support to default (no support) * Clears details previously stored for the network.
*/ */
_clearNetworkDetails() { _resetNetworkDetails() {
this.networkDetails.putState({ ...defaultNetworkDetailsState }); this.networkDetails.putState(buildDefaultNetworkDetailsState());
} }
/** /**
@ -394,67 +484,26 @@ export default class NetworkController extends EventEmitter {
this._switchNetwork(config); this._switchNetwork(config);
} }
async _checkInfuraAvailability(network) { /**
const rpcUrl = `https://${network}.infura.io/v3/${this._infuraProjectId}`; * Retrieves the latest block from the currently selected network; if the
* block has a `baseFeePerGas` property, then we know that the network
let networkChanged = false; * supports EIP-1559; otherwise it doesn't.
const listener = () => { *
networkChanged = true; * @returns {Promise<boolean>} A promise that resolves to true if the network
this.messenger.unsubscribe( * supports EIP-1559 and false otherwise.
NetworkControllerEventTypes.NetworkDidChange, */
listener, async _determineEIP1559Compatibility() {
); const latestBlock = await this._getLatestBlock();
}; return latestBlock && latestBlock.baseFeePerGas !== undefined;
this.messenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
listener,
);
try {
const response = await fetchWithTimeout(rpcUrl, {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_blockNumber',
params: [],
id: 1,
}),
});
if (networkChanged) {
return;
}
if (response.ok) {
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
} else {
const responseMessage = await response.json();
if (networkChanged) {
return;
}
if (responseMessage.error === INFURA_BLOCKED_KEY) {
this.messenger.publish(NetworkControllerEventTypes.InfuraIsBlocked);
}
}
} catch (err) {
log.warn(`MetaMask - Infura availability check failed`, err);
}
} }
_switchNetwork(opts) { _switchNetwork(opts) {
// Indicate to subscribers that network is about to change
this.messenger.publish(NetworkControllerEventTypes.NetworkWillChange); this.messenger.publish(NetworkControllerEventTypes.NetworkWillChange);
// Set loading state this._resetNetworkId();
this._setNetworkState('loading'); this._resetNetworkStatus();
// Reset network details this._resetNetworkDetails();
this._clearNetworkDetails();
// Configure the provider appropriately
this._configureProvider(opts); this._configureProvider(opts);
// Notify subscribers that network has changed this.messenger.publish(NetworkControllerEventTypes.NetworkDidChange);
this.messenger.publish(
NetworkControllerEventTypes.NetworkDidChange,
opts.type,
);
this.lookupNetwork(); this.lookupNetwork();
} }

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ import {
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
} from '../../../shared/constants/swaps'; } from '../../../shared/constants/swaps';
import { GasEstimateTypes } from '../../../shared/constants/gas'; import { GasEstimateTypes } from '../../../shared/constants/gas';
import { CHAIN_IDS } from '../../../shared/constants/network'; import { CHAIN_IDS, NetworkStatus } from '../../../shared/constants/network';
import { import {
FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
FALLBACK_SMART_TRANSACTIONS_DEADLINE, FALLBACK_SMART_TRANSACTIONS_DEADLINE,
@ -136,10 +136,14 @@ export default class SwapsController {
this.indexOfNewestCallInFlight = 0; this.indexOfNewestCallInFlight = 0;
this.ethersProvider = new Web3Provider(provider); this.ethersProvider = new Web3Provider(provider);
this._currentNetwork = networkController.store.getState().network; this._currentNetworkId = networkController.store.getState().networkId;
onNetworkDidChange((network) => { onNetworkDidChange(() => {
if (network !== 'loading' && network !== this._currentNetwork) { const { networkId, networkStatus } = networkController.store.getState();
this._currentNetwork = network; if (
networkStatus === NetworkStatus.Available &&
networkId !== this._currentNetworkId
) {
this._currentNetworkId = networkId;
this.ethersProvider = new Web3Provider(provider); this.ethersProvider = new Web3Provider(provider);
} }
}); });

View File

@ -4,7 +4,11 @@ import sinon from 'sinon';
import { BigNumber } from '@ethersproject/bignumber'; import { BigNumber } from '@ethersproject/bignumber';
import { mapValues } from 'lodash'; import { mapValues } from 'lodash';
import BigNumberjs from 'bignumber.js'; import BigNumberjs from 'bignumber.js';
import { CHAIN_IDS, NETWORK_IDS } from '../../../shared/constants/network'; import {
CHAIN_IDS,
NETWORK_IDS,
NetworkStatus,
} from '../../../shared/constants/network';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
import { createTestProviderTools } from '../../../test/stub/provider'; import { createTestProviderTools } from '../../../test/stub/provider';
import { SECOND } from '../../../shared/constants/time'; import { SECOND } from '../../../shared/constants/time';
@ -97,11 +101,10 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({
function getMockNetworkController() { function getMockNetworkController() {
return { return {
store: { store: {
getState: () => { getState: sinon.stub().returns({
return { networkId: NETWORK_IDS.GOERLI,
network: NETWORK_IDS.GOERLI, networkStatus: NetworkStatus.Available,
}; }),
},
}, },
}; };
} }
@ -219,6 +222,10 @@ describe('SwapsController', function () {
const currentEthersInstance = swapsController.ethersProvider; const currentEthersInstance = swapsController.ethersProvider;
const changeNetwork = onNetworkDidChange.getCall(0).args[0]; const changeNetwork = onNetworkDidChange.getCall(0).args[0];
networkController.store.getState.returns({
networkId: NETWORK_IDS.MAINNET,
networkStatus: NetworkStatus.Available,
});
changeNetwork(NETWORK_IDS.MAINNET); changeNetwork(NETWORK_IDS.MAINNET);
const newEthersInstance = swapsController.ethersProvider; const newEthersInstance = swapsController.ethersProvider;
@ -245,6 +252,10 @@ describe('SwapsController', function () {
const currentEthersInstance = swapsController.ethersProvider; const currentEthersInstance = swapsController.ethersProvider;
const changeNetwork = onNetworkDidChange.getCall(0).args[0]; const changeNetwork = onNetworkDidChange.getCall(0).args[0];
networkController.store.getState.returns({
networkId: null,
networkStatus: NetworkStatus.Unknown,
});
changeNetwork('loading'); changeNetwork('loading');
const newEthersInstance = swapsController.ethersProvider; const newEthersInstance = swapsController.ethersProvider;
@ -271,6 +282,10 @@ describe('SwapsController', function () {
const currentEthersInstance = swapsController.ethersProvider; const currentEthersInstance = swapsController.ethersProvider;
const changeNetwork = onNetworkDidChange.getCall(0).args[0]; const changeNetwork = onNetworkDidChange.getCall(0).args[0];
networkController.store.getState.returns({
networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available,
});
changeNetwork(NETWORK_IDS.GOERLI); changeNetwork(NETWORK_IDS.GOERLI);
const newEthersInstance = swapsController.ethersProvider; const newEthersInstance = swapsController.ethersProvider;

View File

@ -44,6 +44,7 @@ import {
HARDFORKS, HARDFORKS,
CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP,
NETWORK_TYPES, NETWORK_TYPES,
NetworkStatus,
} from '../../../../shared/constants/network'; } from '../../../../shared/constants/network';
import { import {
determineTransactionAssetType, determineTransactionAssetType,
@ -115,7 +116,8 @@ const METRICS_STATUS_FAILED = 'failed on-chain';
* *
* @param {object} opts * @param {object} opts
* @param {object} opts.initState - initial transaction list default is an empty array * @param {object} opts.initState - initial transaction list default is an empty array
* @param {Function} opts.getNetworkState - Get the current network state. * @param {Function} opts.getNetworkId - Get the current network ID.
* @param {Function} opts.getNetworkStatus - Get the current network status.
* @param {Function} opts.onNetworkStateChange - Subscribe to network state change events. * @param {Function} opts.onNetworkStateChange - Subscribe to network state change events.
* @param {object} opts.blockTracker - An instance of eth-blocktracker * @param {object} opts.blockTracker - An instance of eth-blocktracker
* @param {object} opts.provider - A network provider. * @param {object} opts.provider - A network provider.
@ -129,7 +131,8 @@ const METRICS_STATUS_FAILED = 'failed on-chain';
export default class TransactionController extends EventEmitter { export default class TransactionController extends EventEmitter {
constructor(opts) { constructor(opts) {
super(); super();
this.getNetworkState = opts.getNetworkState; this.getNetworkId = opts.getNetworkId;
this.getNetworkStatus = opts.getNetworkStatus;
this._getCurrentChainId = opts.getCurrentChainId; this._getCurrentChainId = opts.getCurrentChainId;
this.getProviderConfig = opts.getProviderConfig; this.getProviderConfig = opts.getProviderConfig;
this._getCurrentNetworkEIP1559Compatibility = this._getCurrentNetworkEIP1559Compatibility =
@ -167,7 +170,8 @@ export default class TransactionController extends EventEmitter {
this.txStateManager = new TransactionStateManager({ this.txStateManager = new TransactionStateManager({
initState: opts.initState, initState: opts.initState,
txHistoryLimit: opts.txHistoryLimit, txHistoryLimit: opts.txHistoryLimit,
getNetworkState: this.getNetworkState, getNetworkId: this.getNetworkId,
getNetworkStatus: this.getNetworkStatus,
getCurrentChainId: opts.getCurrentChainId, getCurrentChainId: opts.getCurrentChainId,
}); });
@ -226,10 +230,13 @@ export default class TransactionController extends EventEmitter {
* @returns {number} The numerical chainId. * @returns {number} The numerical chainId.
*/ */
getChainId() { getChainId() {
const networkState = this.getNetworkState(); const networkStatus = this.getNetworkStatus();
const chainId = this._getCurrentChainId(); const chainId = this._getCurrentChainId();
const integerChainId = parseInt(chainId, 16); const integerChainId = parseInt(chainId, 16);
if (networkState === 'loading' || Number.isNaN(integerChainId)) { if (
networkStatus !== NetworkStatus.Available ||
Number.isNaN(integerChainId)
) {
return 0; return 0;
} }
return integerChainId; return integerChainId;
@ -272,12 +279,13 @@ export default class TransactionController extends EventEmitter {
}); });
} }
// For 'rpc' we need to use the same basic configuration as mainnet, // For 'rpc' we need to use the same basic configuration as mainnet, since
// since we only support EVM compatible chains, and then override the // we only support EVM compatible chains, and then override the
// name, chainId and networkId properties. This is done using the // name, chainId and networkId properties. This is done using the
// `forCustomChain` static method on the Common class. // `forCustomChain` static method on the Common class.
const chainId = parseInt(this._getCurrentChainId(), 16); const chainId = parseInt(this._getCurrentChainId(), 16);
const networkId = this.getNetworkState(); const networkStatus = this.getNetworkStatus();
const networkId = this.getNetworkId();
const customChainParams = { const customChainParams = {
name, name,
@ -291,7 +299,8 @@ export default class TransactionController extends EventEmitter {
// on a custom network that requires valid network id. I have not ran // on a custom network that requires valid network id. I have not ran
// into this limitation on any network I have attempted, even when // into this limitation on any network I have attempted, even when
// hardcoding networkId to 'loading'. // hardcoding networkId to 'loading'.
networkId: networkId === 'loading' ? 0 : parseInt(networkId, 10), networkId:
networkStatus === NetworkStatus.Available ? parseInt(networkId, 10) : 0,
}; };
return Common.forCustomChain( return Common.forCustomChain(

View File

@ -27,12 +27,14 @@ import {
} from '../../../../shared/constants/gas'; } from '../../../../shared/constants/gas';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import { NetworkStatus } from '../../../../shared/constants/network';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils';
import TransactionController from '.'; import TransactionController from '.';
const noop = () => true; const noop = () => true;
const currentNetworkId = '5'; const currentNetworkId = '5';
const currentChainId = '0x5'; const currentChainId = '0x5';
const currentNetworkStatus = NetworkStatus.Available;
const providerConfig = { const providerConfig = {
type: 'goerli', type: 'goerli',
}; };
@ -46,7 +48,8 @@ describe('Transaction Controller', function () {
providerResultStub, providerResultStub,
fromAccount, fromAccount,
fragmentExists, fragmentExists,
networkStore; networkStatusStore,
getCurrentChainId;
beforeEach(function () { beforeEach(function () {
fragmentExists = false; fragmentExists = false;
@ -59,22 +62,27 @@ describe('Transaction Controller', function () {
provider = createTestProviderTools({ provider = createTestProviderTools({
scaffold: providerResultStub, scaffold: providerResultStub,
networkId: currentNetworkId, networkId: currentNetworkId,
chainId: currentNetworkId, chainId: parseInt(currentChainId, 16),
}).provider; }).provider;
networkStore = new ObservableStore(currentNetworkId); networkStatusStore = new ObservableStore(currentNetworkStatus);
fromAccount = getTestAccounts()[0]; fromAccount = getTestAccounts()[0];
const blockTrackerStub = new EventEmitter(); const blockTrackerStub = new EventEmitter();
blockTrackerStub.getCurrentBlock = noop; blockTrackerStub.getCurrentBlock = noop;
blockTrackerStub.getLatestBlock = noop; blockTrackerStub.getLatestBlock = noop;
getCurrentChainId = sinon.stub().callsFake(() => currentChainId);
txController = new TransactionController({ txController = new TransactionController({
provider, provider,
getGasPrice() { getGasPrice() {
return '0xee6b2800'; return '0xee6b2800';
}, },
getNetworkState: () => networkStore.getState(), getNetworkId: () => currentNetworkId,
onNetworkStateChange: (listener) => networkStore.subscribe(listener), getNetworkStatus: () => networkStatusStore.getState(),
onNetworkStateChange: (listener) =>
networkStatusStore.subscribe(listener),
getCurrentNetworkEIP1559Compatibility: () => Promise.resolve(false), getCurrentNetworkEIP1559Compatibility: () => Promise.resolve(false),
getCurrentAccountEIP1559Compatibility: () => false, getCurrentAccountEIP1559Compatibility: () => false,
txHistoryLimit: 10, txHistoryLimit: 10,
@ -85,7 +93,7 @@ describe('Transaction Controller', function () {
}), }),
getProviderConfig: () => providerConfig, getProviderConfig: () => providerConfig,
getPermittedAccounts: () => undefined, getPermittedAccounts: () => undefined,
getCurrentChainId: () => currentChainId, getCurrentChainId,
getParticipateInMetrics: () => false, getParticipateInMetrics: () => false,
trackMetaMetricsEvent: () => undefined, trackMetaMetricsEvent: () => undefined,
createEventFragment: () => undefined, createEventFragment: () => undefined,
@ -467,8 +475,8 @@ describe('Transaction Controller', function () {
); );
}); });
it('should fail if netId is loading', async function () { it('should fail if the network status is not "available"', async function () {
networkStore.putState('loading'); networkStatusStore.putState(NetworkStatus.Unknown);
await assert.rejects( await assert.rejects(
() => () =>
txController.addUnapprovedTransaction(undefined, { txController.addUnapprovedTransaction(undefined, {
@ -1079,8 +1087,19 @@ describe('Transaction Controller', function () {
}); });
describe('#getChainId', function () { describe('#getChainId', function () {
it('returns 0 when the chainId is NaN', function () { it('returns the chain ID of the network when it is available', function () {
networkStore.putState('loading'); networkStatusStore.putState(NetworkStatus.Available);
assert.equal(txController.getChainId(), 5);
});
it('returns 0 when the network is not available', function () {
networkStatusStore.putState('asdflsfadf');
assert.equal(txController.getChainId(), 0);
});
it('returns 0 when the chain ID cannot be parsed as a hex string', function () {
networkStatusStore.putState(NetworkStatus.Available);
getCurrentChainId.returns('$fdsjfldf');
assert.equal(txController.getChainId(), 0); assert.equal(txController.getChainId(), 0);
}); });
}); });

View File

@ -7,6 +7,7 @@ import { TransactionStatus } from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils'; import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import { NetworkStatus } from '../../../../shared/constants/network';
import { import {
generateHistoryEntry, generateHistoryEntry,
replayHistory, replayHistory,
@ -54,13 +55,15 @@ export const ERROR_SUBMITTING =
* transactions list keyed by id * transactions list keyed by id
* @param {number} [opts.txHistoryLimit] - limit for how many finished * @param {number} [opts.txHistoryLimit] - limit for how many finished
* transactions can hang around in state * transactions can hang around in state
* @param {Function} opts.getNetworkState - Get the current network state. * @param {Function} opts.getNetworkId - Get the current network Id.
* @param {Function} opts.getNetworkStatus - Get the current network status.
*/ */
export default class TransactionStateManager extends EventEmitter { export default class TransactionStateManager extends EventEmitter {
constructor({ constructor({
initState, initState,
txHistoryLimit, txHistoryLimit,
getNetworkState, getNetworkId,
getNetworkStatus,
getCurrentChainId, getCurrentChainId,
}) { }) {
super(); super();
@ -70,7 +73,8 @@ export default class TransactionStateManager extends EventEmitter {
...initState, ...initState,
}); });
this.txHistoryLimit = txHistoryLimit; this.txHistoryLimit = txHistoryLimit;
this.getNetworkState = getNetworkState; this.getNetworkId = getNetworkId;
this.getNetworkStatus = getNetworkStatus;
this.getCurrentChainId = getCurrentChainId; this.getCurrentChainId = getCurrentChainId;
} }
@ -86,9 +90,10 @@ export default class TransactionStateManager extends EventEmitter {
* @returns {TransactionMeta} the default txMeta object * @returns {TransactionMeta} the default txMeta object
*/ */
generateTxMeta(opts = {}) { generateTxMeta(opts = {}) {
const netId = this.getNetworkState(); const networkId = this.getNetworkId();
const networkStatus = this.getNetworkStatus();
const chainId = this.getCurrentChainId(); const chainId = this.getCurrentChainId();
if (netId === 'loading') { if (networkStatus !== NetworkStatus.Available) {
throw new Error('MetaMask is having trouble connecting to the network'); throw new Error('MetaMask is having trouble connecting to the network');
} }
@ -128,7 +133,7 @@ export default class TransactionStateManager extends EventEmitter {
id: createId(), id: createId(),
time: new Date().getTime(), time: new Date().getTime(),
status: TransactionStatus.unapproved, status: TransactionStatus.unapproved,
metamaskNetworkId: netId, metamaskNetworkId: networkId,
originalGasEstimate: opts.txParams?.gas, originalGasEstimate: opts.txParams?.gas,
userEditedGasLimit: false, userEditedGasLimit: false,
chainId, chainId,
@ -149,12 +154,12 @@ export default class TransactionStateManager extends EventEmitter {
*/ */
getUnapprovedTxList() { getUnapprovedTxList() {
const chainId = this.getCurrentChainId(); const chainId = this.getCurrentChainId();
const network = this.getNetworkState(); const networkId = this.getNetworkId();
return pickBy( return pickBy(
this.store.getState().transactions, this.store.getState().transactions,
(transaction) => (transaction) =>
transaction.status === TransactionStatus.unapproved && transaction.status === TransactionStatus.unapproved &&
transactionMatchesNetwork(transaction, chainId, network), transactionMatchesNetwork(transaction, chainId, networkId),
); );
} }
@ -413,7 +418,7 @@ export default class TransactionStateManager extends EventEmitter {
limit, limit,
} = {}) { } = {}) {
const chainId = this.getCurrentChainId(); const chainId = this.getCurrentChainId();
const network = this.getNetworkState(); const networkId = this.getNetworkId();
// searchCriteria is an object that might have values that aren't predicate // searchCriteria is an object that might have values that aren't predicate
// methods. When providing any other value type (string, number, etc), we // methods. When providing any other value type (string, number, etc), we
// consider this shorthand for "check the value at key for strict equality // consider this shorthand for "check the value at key for strict equality
@ -442,7 +447,7 @@ export default class TransactionStateManager extends EventEmitter {
// when filterToCurrentNetwork is true. // when filterToCurrentNetwork is true.
if ( if (
filterToCurrentNetwork && filterToCurrentNetwork &&
transactionMatchesNetwork(transaction, chainId, network) === false transactionMatchesNetwork(transaction, chainId, networkId) === false
) { ) {
return false; return false;
} }
@ -596,8 +601,7 @@ export default class TransactionStateManager extends EventEmitter {
} }
/** /**
* Removes all transactions for the given address on the current network, * Removes all transactions for the given address on the current network.
* preferring chainId for comparison over networkId.
* *
* @param {string} address - hex string of the from address on the txParams * @param {string} address - hex string of the from address on the txParams
* to remove * to remove
@ -605,8 +609,8 @@ export default class TransactionStateManager extends EventEmitter {
wipeTransactions(address) { wipeTransactions(address) {
// network only tx // network only tx
const { transactions } = this.store.getState(); const { transactions } = this.store.getState();
const network = this.getNetworkState();
const chainId = this.getCurrentChainId(); const chainId = this.getCurrentChainId();
const networkId = this.getNetworkId();
// Update state // Update state
this.store.updateState({ this.store.updateState({
@ -614,7 +618,7 @@ export default class TransactionStateManager extends EventEmitter {
transactions, transactions,
(transaction) => (transaction) =>
transaction.txParams.from === address && transaction.txParams.from === address &&
transactionMatchesNetwork(transaction, chainId, network), transactionMatchesNetwork(transaction, chainId, networkId),
), ),
}); });
} }

View File

@ -4,7 +4,11 @@ import {
TransactionStatus, TransactionStatus,
TransactionType, TransactionType,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
import { CHAIN_IDS, NETWORK_IDS } from '../../../../shared/constants/network'; import {
CHAIN_IDS,
NETWORK_IDS,
NetworkStatus,
} from '../../../../shared/constants/network';
import { GAS_LIMITS } from '../../../../shared/constants/gas'; import { GAS_LIMITS } from '../../../../shared/constants/gas';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import TxStateManager, { ERROR_SUBMITTING } from './tx-state-manager'; import TxStateManager, { ERROR_SUBMITTING } from './tx-state-manager';
@ -45,6 +49,7 @@ function generateTransactions(
describe('TransactionStateManager', function () { describe('TransactionStateManager', function () {
let txStateManager; let txStateManager;
const currentNetworkId = NETWORK_IDS.GOERLI; const currentNetworkId = NETWORK_IDS.GOERLI;
const currentNetworkStatus = NetworkStatus.Available;
const currentChainId = CHAIN_IDS.MAINNET; const currentChainId = CHAIN_IDS.MAINNET;
const otherNetworkId = '2'; const otherNetworkId = '2';
@ -54,7 +59,8 @@ describe('TransactionStateManager', function () {
transactions: {}, transactions: {},
}, },
txHistoryLimit: 10, txHistoryLimit: 10,
getNetworkState: () => currentNetworkId, getNetworkId: () => currentNetworkId,
getNetworkStatus: () => currentNetworkStatus,
getCurrentChainId: () => currentChainId, getCurrentChainId: () => currentChainId,
}); });
}); });
@ -181,7 +187,8 @@ describe('TransactionStateManager', function () {
[confirmedTx.id]: confirmedTx, [confirmedTx.id]: confirmedTx,
}, },
}, },
getNetworkState: () => currentNetworkId, getNetworkId: () => currentNetworkId,
getNetworkStatus: () => currentNetworkStatus,
getCurrentChainId: () => currentChainId, getCurrentChainId: () => currentChainId,
}); });
@ -246,7 +253,8 @@ describe('TransactionStateManager', function () {
[confirmedTx3.id]: confirmedTx3, [confirmedTx3.id]: confirmedTx3,
}, },
}, },
getNetworkState: () => currentNetworkId, getNetworkId: () => currentNetworkId,
getNetworkStatus: () => currentNetworkStatus,
getCurrentChainId: () => currentChainId, getCurrentChainId: () => currentChainId,
}); });
@ -355,7 +363,8 @@ describe('TransactionStateManager', function () {
[failedTx3Dupe.id]: failedTx3Dupe, [failedTx3Dupe.id]: failedTx3Dupe,
}, },
}, },
getNetworkState: () => currentNetworkId, getNetworkId: () => currentNetworkId,
getNetworkStatus: () => currentNetworkStatus,
getCurrentChainId: () => currentChainId, getCurrentChainId: () => currentChainId,
}); });

View File

@ -52,7 +52,8 @@ export const SENTRY_STATE = {
isUnlocked: true, isUnlocked: true,
metaMetricsId: true, metaMetricsId: true,
nativeCurrency: true, nativeCurrency: true,
network: true, networkId: true,
networkStatus: true,
nextNonce: true, nextNonce: true,
participateInMetaMetrics: true, participateInMetaMetrics: true,
preferences: true, preferences: true,

View File

@ -74,7 +74,11 @@ import {
GAS_DEV_API_BASE_URL, GAS_DEV_API_BASE_URL,
SWAPS_CLIENT_ID, SWAPS_CLIENT_ID,
} from '../../shared/constants/swaps'; } from '../../shared/constants/swaps';
import { CHAIN_IDS, NETWORK_TYPES } from '../../shared/constants/network'; import {
CHAIN_IDS,
NETWORK_TYPES,
NetworkStatus,
} from '../../shared/constants/network';
import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets';
import { KeyringType } from '../../shared/constants/keyring'; import { KeyringType } from '../../shared/constants/keyring';
import { import {
@ -334,7 +338,7 @@ export default class MetamaskController extends EventEmitter {
{ {
onPreferencesStateChange: (listener) => onPreferencesStateChange: (listener) =>
this.preferencesController.store.subscribe(listener), this.preferencesController.store.subscribe(listener),
onNetworkStateChange: (cb) => onNetworkStateChange: (cb) => {
this.networkController.store.subscribe((networkState) => { this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = { const modifiedNetworkState = {
...networkState, ...networkState,
@ -344,7 +348,8 @@ export default class MetamaskController extends EventEmitter {
}, },
}; };
return cb(modifiedNetworkState); return cb(modifiedNetworkState);
}), });
},
}, },
{ {
provider: this.provider, provider: this.provider,
@ -478,6 +483,8 @@ export default class MetamaskController extends EventEmitter {
clientId: SWAPS_CLIENT_ID, clientId: SWAPS_CLIENT_ID,
getProvider: () => getProvider: () =>
this.networkController.getProviderAndBlockTracker().provider, this.networkController.getProviderAndBlockTracker().provider,
// NOTE: This option is inaccurately named; it should be called
// onNetworkDidChange
onNetworkStateChange: networkControllerMessenger.subscribe.bind( onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventTypes.NetworkDidChange,
@ -944,9 +951,11 @@ export default class MetamaskController extends EventEmitter {
), ),
getCurrentAccountEIP1559Compatibility: getCurrentAccountEIP1559Compatibility:
this.getCurrentAccountEIP1559Compatibility.bind(this), this.getCurrentAccountEIP1559Compatibility.bind(this),
getNetworkState: () => this.networkController.store.getState().network, getNetworkId: () => this.networkController.store.getState().networkId,
getNetworkStatus: () =>
this.networkController.store.getState().networkStatus,
onNetworkStateChange: (listener) => onNetworkStateChange: (listener) =>
this.networkController.networkStore.subscribe(listener), this.networkController.networkIdStore.subscribe(listener),
getCurrentChainId: () => getCurrentChainId: () =>
this.networkController.store.getState().provider.chainId, this.networkController.store.getState().provider.chainId,
preferencesStore: this.preferencesController.store, preferencesStore: this.preferencesController.store,
@ -1144,7 +1153,8 @@ export default class MetamaskController extends EventEmitter {
return cb(modifiedNetworkState); return cb(modifiedNetworkState);
}); });
}, },
getNetwork: () => this.networkController.store.getState().network, getNetwork: () =>
this.networkController.store.getState().networkId ?? 'loading',
getNonceLock: this.txController.nonceTracker.getNonceLock.bind( getNonceLock: this.txController.nonceTracker.getNonceLock.bind(
this.txController.nonceTracker, this.txController.nonceTracker,
), ),
@ -1666,16 +1676,16 @@ export default class MetamaskController extends EventEmitter {
function updatePublicConfigStore(memState) { function updatePublicConfigStore(memState) {
const { chainId } = networkController.store.getState().provider; const { chainId } = networkController.store.getState().provider;
if (memState.network !== 'loading') { if (memState.networkStatus === NetworkStatus.Available) {
publicConfigStore.putState(selectPublicState(chainId, memState)); publicConfigStore.putState(selectPublicState(chainId, memState));
} }
} }
function selectPublicState(chainId, { isUnlocked, network }) { function selectPublicState(chainId, { isUnlocked, networkId }) {
return { return {
isUnlocked, isUnlocked,
chainId, chainId,
networkVersion: network, networkVersion: networkId ?? 'loading',
}; };
} }
@ -1686,12 +1696,7 @@ export default class MetamaskController extends EventEmitter {
* Gets relevant state for the provider of an external origin. * Gets relevant state for the provider of an external origin.
* *
* @param {string} origin - The origin to get the provider state for. * @param {string} origin - The origin to get the provider state for.
* @returns {Promise<{ * @returns {Promise<{ isUnlocked: boolean, networkVersion: string, chainId: string, accounts: string[] }>} An object with relevant state properties.
* isUnlocked: boolean,
* networkVersion: string,
* chainId: string,
* accounts: string[],
* }>} An object with relevant state properties.
*/ */
async getProviderState(origin) { async getProviderState(origin) {
return { return {
@ -1709,10 +1714,10 @@ export default class MetamaskController extends EventEmitter {
* @returns {object} An object with relevant network state properties. * @returns {object} An object with relevant network state properties.
*/ */
getProviderNetworkState(memState) { getProviderNetworkState(memState) {
const { network } = memState || this.getState(); const { networkId } = memState || this.getState();
return { return {
chainId: this.networkController.store.getState().provider.chainId, chainId: this.networkController.store.getState().provider.chainId,
networkVersion: network, networkVersion: networkId ?? 'loading',
}; };
} }

View File

@ -779,13 +779,13 @@ describe('MetaMaskController', function () {
metamaskController.preferencesController, metamaskController.preferencesController,
'getSelectedAddress', 'getSelectedAddress',
); );
const getNetworkstub = sinon.stub( const getNetworkIdStub = sinon.stub(
metamaskController.txController.txStateManager, metamaskController.txController.txStateManager,
'getNetworkState', 'getNetworkId',
); );
selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'); selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc');
getNetworkstub.returns(42); getNetworkIdStub.returns(42);
metamaskController.txController.txStateManager._addTransactionsToState([ metamaskController.txController.txStateManager._addTransactionsToState([
createTxMeta({ createTxMeta({

View File

@ -0,0 +1,103 @@
import { migrate } from './083';
describe('migration #83', () => {
it('updates the version metadata', async () => {
const originalVersionedData = buildOriginalVersionedData({
meta: {
version: 9999999,
},
});
const newVersionedData = await migrate(originalVersionedData);
expect(newVersionedData.meta).toStrictEqual({
version: 83,
});
});
it('does not change the state if the network controller state does not exist', async () => {
const originalVersionedData = buildOriginalVersionedData({
data: {
test: '123',
},
});
const newVersionedData = await migrate(originalVersionedData);
expect(newVersionedData.data).toStrictEqual(originalVersionedData.data);
});
const nonObjects = [undefined, null, 'test', 1, ['test']];
for (const invalidState of nonObjects) {
it(`does not change the state if the network controller state is ${invalidState}`, async () => {
const originalVersionedData = buildOriginalVersionedData({
data: {
NetworkController: invalidState,
},
});
const newVersionedData = await migrate(originalVersionedData);
expect(newVersionedData.data).toStrictEqual(originalVersionedData.data);
});
}
it('does not change the state if the network controller state does not include "network"', async () => {
const originalVersionedData = buildOriginalVersionedData({
data: {
NetworkController: {
test: '123',
},
},
});
const newVersionedData = await migrate(originalVersionedData);
expect(newVersionedData.data).toStrictEqual(originalVersionedData.data);
});
it('replaces "network" in the network controller state with "networkId": null, "networkStatus": "unknown" if it is "loading"', async () => {
const originalVersionedData = buildOriginalVersionedData({
data: {
NetworkController: {
network: 'loading',
},
},
});
const newVersionedData = await migrate(originalVersionedData);
expect(newVersionedData.data).toStrictEqual({
NetworkController: {
networkId: null,
networkStatus: 'unknown',
},
});
});
it('replaces "network" in the network controller state with "networkId": network, "networkStatus": "available" if it is not "loading"', async () => {
const originalVersionedData = buildOriginalVersionedData({
data: {
NetworkController: {
network: '12345',
},
},
});
const newVersionedData = await migrate(originalVersionedData);
expect(newVersionedData.data).toStrictEqual({
NetworkController: {
networkId: '12345',
networkStatus: 'available',
},
});
});
});
function buildOriginalVersionedData({ meta = {}, data = {} } = {}) {
return {
meta: { version: 999999, ...meta },
data: { ...data },
};
}

View File

@ -0,0 +1,47 @@
import { cloneDeep } from 'lodash';
import { hasProperty, isObject } from '@metamask/utils';
export const version = 83;
/**
* The `network` property in state was replaced with `networkId` and `networkStatus`.
*
* @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, 'NetworkController') ||
!isObject(state.NetworkController) ||
!hasProperty(state.NetworkController, 'network')
) {
return state;
}
const NetworkController = { ...state.NetworkController };
if (NetworkController.network === 'loading') {
NetworkController.networkId = null;
NetworkController.networkStatus = 'unknown';
} else {
NetworkController.networkId = NetworkController.network;
NetworkController.networkStatus = 'available';
}
delete NetworkController.network;
return { ...state, NetworkController };
}

View File

@ -86,6 +86,7 @@ import m079 from './079';
import m080 from './080'; import m080 from './080';
import * as m081 from './081'; import * as m081 from './081';
import * as m082 from './082'; import * as m082 from './082';
import * as m083 from './083';
const migrations = [ const migrations = [
m002, m002,
@ -169,6 +170,7 @@ const migrations = [
m080, m080,
m081, m081,
m082, m082,
m083,
]; ];
export default migrations; export default migrations;

View File

@ -689,3 +689,30 @@ export const FEATURED_RPCS: RPCDefinition[] = [
export const SHOULD_SHOW_LINEA_TESTNET_NETWORK = export const SHOULD_SHOW_LINEA_TESTNET_NETWORK =
new Date().getTime() > Date.UTC(2023, 2, 28, 8); new Date().getTime() > Date.UTC(2023, 2, 28, 8);
/**
* Represents the availability state of the currently selected network.
*/
export enum NetworkStatus {
/**
* The network may or may not be able to receive requests, but either no
* attempt has been made to determine this, or an attempt was made but was
* unsuccessful.
*/
Unknown = 'unknown',
/**
* The network is able to receive and respond to requests.
*/
Available = 'available',
/**
* The network is unable to receive and respond to requests for unknown
* reasons.
*/
Unavailable = 'unavailable',
/**
* The network is not only unavailable, but is also inaccessible for the user
* specifically based on their location. This state only applies to Infura
* networks.
*/
Blocked = 'blocked',
}

View File

@ -72,7 +72,8 @@
"featureFlags": { "featureFlags": {
"showIncomingTransactions": true "showIncomingTransactions": true
}, },
"network": "5", "networkId": "5",
"networkStatus": "available",
"provider": { "provider": {
"type": "rpc", "type": "rpc",
"chainId": "0x5", "chainId": "0x5",

View File

@ -180,7 +180,8 @@ function defaultFixture() {
traits: {}, traits: {},
}, },
NetworkController: { NetworkController: {
network: '1337', networkId: '1337',
networkStatus: 'available',
provider: { provider: {
chainId: CHAIN_IDS.LOCALHOST, chainId: CHAIN_IDS.LOCALHOST,
nickname: 'Localhost 8545', nickname: 'Localhost 8545',
@ -310,7 +311,8 @@ function onboardingFixture() {
}, },
}, },
NetworkController: { NetworkController: {
network: '1337', networkId: '1337',
networkStatus: 'available',
provider: { provider: {
ticker: 'ETH', ticker: 'ETH',
type: 'rpc', type: 'rpc',
@ -487,15 +489,6 @@ class FixtureBuilder {
return this; return this;
} }
withNetworkControllerSupportEIP1559() {
merge(this.fixture.data.NetworkController, {
networkDetails: {
EIPS: { 1559: true },
},
});
return this;
}
withNftController(data) { withNftController(data) {
merge( merge(
this.fixture.data.NftController this.fixture.data.NftController

View File

@ -25,6 +25,34 @@ describe('ENS', function () {
}; };
}); });
await mockServer
.forPost(infuraUrl)
.withJsonBodyIncluding({ method: 'eth_getBalance' })
.thenCallback(() => {
return {
statusCode: 200,
json: {
jsonrpc: '2.0',
id: '1111111111111111',
result: '0x1',
},
};
});
await mockServer
.forPost(infuraUrl)
.withJsonBodyIncluding({ method: 'eth_getBlockByNumber' })
.thenCallback(() => {
return {
statusCode: 200,
json: {
jsonrpc: '2.0',
id: '1111111111111111',
result: {},
},
};
});
await mockServer await mockServer
.forPost(infuraUrl) .forPost(infuraUrl)
.withJsonBodyIncluding({ method: 'eth_call' }) .withJsonBodyIncluding({ method: 'eth_call' })

View File

@ -298,9 +298,11 @@ describe('Send ETH from dapp using advanced gas controls', function () {
dapp: true, dapp: true,
fixtures: new FixtureBuilder() fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToTestDapp() .withPermissionControllerConnectedToTestDapp()
.withNetworkControllerSupportEIP1559()
.build(), .build(),
ganacheOptions, ganacheOptions: {
...ganacheOptions,
hardfork: 'london',
},
title: this.test.title, title: this.test.title,
}, },
async ({ driver }) => { async ({ driver }) => {

View File

@ -11,10 +11,8 @@ jest.mock('../../../../../app/scripts/lib/util', () => ({
describe('Confirm Detail Row Component', () => { describe('Confirm Detail Row Component', () => {
const mockState = { const mockState = {
appState: {
isLoading: false,
},
metamask: { metamask: {
networkStatus: 'available',
provider: { provider: {
type: 'rpc', type: 'rpc',
chainId: '0x5', chainId: '0x5',

View File

@ -21,7 +21,8 @@ describe('Network Dropdown', () => {
describe('NetworkDropdown in appState in false', () => { describe('NetworkDropdown in appState in false', () => {
const mockState = { const mockState = {
metamask: { metamask: {
network: '1', networkId: '1',
networkStatus: 'available',
provider: { provider: {
type: 'test', type: 'test',
}, },
@ -55,7 +56,8 @@ describe('Network Dropdown', () => {
describe('NetworkDropdown in appState is true and show test networks is true', () => { describe('NetworkDropdown in appState is true and show test networks is true', () => {
const mockState = { const mockState = {
metamask: { metamask: {
network: '1', networkId: '1',
networkStatus: 'available',
provider: { provider: {
type: 'test', type: 'test',
}, },
@ -133,7 +135,8 @@ describe('Network Dropdown', () => {
describe('NetworkDropdown in appState is true and show test networks is false', () => { describe('NetworkDropdown in appState is true and show test networks is false', () => {
const mockState = { const mockState = {
metamask: { metamask: {
network: '1', networkId: '1',
networkStatus: 'available',
provider: { provider: {
type: 'test', type: 'test',
}, },

View File

@ -18,7 +18,7 @@ const customTransaction = ({
userFeeLevel: estimateUsed ? 'low' : 'medium', userFeeLevel: estimateUsed ? 'low' : 'medium',
blockNumber: `${10902987 + i}`, blockNumber: `${10902987 + i}`,
id: 4678200543090545 + i, id: 4678200543090545 + i,
metamaskNetworkId: testData?.metamask?.network, metamaskNetworkId: testData?.metamask?.networkId,
chainId: testData?.metamask?.provider?.chainId, chainId: testData?.metamask?.provider?.chainId,
status: 'confirmed', status: 'confirmed',
time: 1600654021000, time: 1600654021000,

View File

@ -343,7 +343,8 @@ describe('Confirm Transaction Duck', () => {
metamask: { metamask: {
conversionRate: 468.58, conversionRate: 468.58,
currentCurrency: 'usd', currentCurrency: 'usd',
network: '5', networkId: '5',
networkStatus: 'available',
provider: { provider: {
chainId: '0x5', chainId: '0x5',
}, },
@ -368,7 +369,6 @@ describe('Confirm Transaction Duck', () => {
}, },
confirmTransaction: {}, confirmTransaction: {},
}; };
const middlewares = [thunk]; const middlewares = [thunk];
const mockStore = configureMockStore(middlewares); const mockStore = configureMockStore(middlewares);
const store = mockStore(mockState); const store = mockStore(mockState);

View File

@ -41,7 +41,8 @@ describe('MetaMask Reducers', () => {
conversionRate: 1200.88200327, conversionRate: 1200.88200327,
nativeCurrency: 'ETH', nativeCurrency: 'ETH',
useCurrencyRateCheck: true, useCurrencyRateCheck: true,
network: '5', networkId: '5',
networkStatus: 'available',
provider: { provider: {
type: 'testnet', type: 'testnet',
chainId: '0x5', chainId: '0x5',

View File

@ -9,7 +9,7 @@ export default function txHelper(
decryptMsgs: Record<string, any> | null, decryptMsgs: Record<string, any> | null,
encryptionPublicKeyMsgs: Record<string, any> | null, encryptionPublicKeyMsgs: Record<string, any> | null,
typedMessages: Record<string, any> | null, typedMessages: Record<string, any> | null,
network?: string, networkId?: string | null,
chainId?: string, chainId?: string,
): Record<string, any> { ): Record<string, any> {
log.debug('tx-helper called with params:'); log.debug('tx-helper called with params:');
@ -20,13 +20,13 @@ export default function txHelper(
decryptMsgs, decryptMsgs,
encryptionPublicKeyMsgs, encryptionPublicKeyMsgs,
typedMessages, typedMessages,
network, networkId,
chainId, chainId,
}); });
const txValues = network const txValues = networkId
? valuesFor(unapprovedTxs).filter((txMeta) => ? valuesFor(unapprovedTxs).filter((txMeta) =>
transactionMatchesNetwork(txMeta, chainId, network), transactionMatchesNetwork(txMeta, chainId, networkId),
) )
: valuesFor(unapprovedTxs); : valuesFor(unapprovedTxs);
log.debug(`tx helper found ${txValues.length} unapproved txs`); log.debug(`tx helper found ${txValues.length} unapproved txs`);

View File

@ -160,7 +160,7 @@ async function startApp(metamaskState, backgroundConnection, opts) {
metamaskState.unapprovedDecryptMsgs, metamaskState.unapprovedDecryptMsgs,
metamaskState.unapprovedEncryptionPublicKeyMsgs, metamaskState.unapprovedEncryptionPublicKeyMsgs,
metamaskState.unapprovedTypedMessages, metamaskState.unapprovedTypedMessages,
metamaskState.network, metamaskState.networkId,
metamaskState.provider.chainId, metamaskState.provider.chainId,
); );
const numberOfUnapprovedTx = unapprovedTxsAll.length; const numberOfUnapprovedTx = unapprovedTxsAll.length;

View File

@ -63,7 +63,7 @@ const ConfirmTxScreen = ({ match }) => {
unapprovedMsgs, unapprovedMsgs,
unapprovedPersonalMsgs, unapprovedPersonalMsgs,
unapprovedTypedMessages, unapprovedTypedMessages,
network, networkId,
blockGasLimit, blockGasLimit,
provider: { chainId }, provider: { chainId },
} = useSelector((state) => state.metamask); } = useSelector((state) => state.metamask);
@ -76,7 +76,7 @@ const ConfirmTxScreen = ({ match }) => {
{}, {},
{}, {},
{}, {},
network, networkId,
chainId, chainId,
); );
if (unconfTxList.length === 0 && !sendTo && unapprovedMessagesTotal === 0) { if (unconfTxList.length === 0 && !sendTo && unapprovedMessagesTotal === 0) {
@ -101,7 +101,7 @@ const ConfirmTxScreen = ({ match }) => {
{}, {},
{}, {},
{}, {},
network, networkId,
chainId, chainId,
); );
const prevTxData = prevUnconfTxList[prevIndex] || {}; const prevTxData = prevUnconfTxList[prevIndex] || {};
@ -114,7 +114,7 @@ const ConfirmTxScreen = ({ match }) => {
{}, {},
{}, {},
{}, {},
network, networkId,
chainId, chainId,
); );
@ -137,7 +137,7 @@ const ConfirmTxScreen = ({ match }) => {
chainId, chainId,
currentNetworkTxList, currentNetworkTxList,
match, match,
network, networkId,
sendTo, sendTo,
unapprovedMessagesTotal, unapprovedMessagesTotal,
unapprovedTxs, unapprovedTxs,
@ -151,7 +151,7 @@ const ConfirmTxScreen = ({ match }) => {
unapprovedMsgs, unapprovedMsgs,
unapprovedPersonalMsgs, unapprovedPersonalMsgs,
unapprovedTypedMessages, unapprovedTypedMessages,
network, networkId,
chainId, chainId,
); );

View File

@ -101,7 +101,7 @@ const mapStateToProps = (state, ownProps) => {
conversionRate, conversionRate,
identities, identities,
addressBook, addressBook,
network, networkId,
unapprovedTxs, unapprovedTxs,
nextNonce, nextNonce,
provider: { chainId }, provider: { chainId },
@ -160,7 +160,7 @@ const mapStateToProps = (state, ownProps) => {
const currentNetworkUnapprovedTxs = Object.keys(unapprovedTxs) const currentNetworkUnapprovedTxs = Object.keys(unapprovedTxs)
.filter((key) => .filter((key) =>
transactionMatchesNetwork(unapprovedTxs[key], chainId, network), transactionMatchesNetwork(unapprovedTxs[key], chainId, networkId),
) )
.reduce((acc, key) => ({ ...acc, [key]: unapprovedTxs[key] }), {}); .reduce((acc, key) => ({ ...acc, [key]: unapprovedTxs[key] }), {});
const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length; const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length;

View File

@ -33,6 +33,7 @@ const baseStore = {
unapprovedTxs: { unapprovedTxs: {
1: { 1: {
id: 1, id: 1,
metamaskNetworkId: '5',
txParams: { txParams: {
from: '0x0', from: '0x0',
to: '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e', to: '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e',
@ -58,6 +59,7 @@ const baseStore = {
accounts: ['0x0'], accounts: ['0x0'],
}, },
], ],
networkId: '5',
networkDetails: { networkDetails: {
EIPS: {}, EIPS: {},
}, },

View File

@ -31,7 +31,7 @@ describe('Confirm Transaction Selector', () => {
unapprovedMsgCount: 1, unapprovedMsgCount: 1,
unapprovedPersonalMsgCount: 1, unapprovedPersonalMsgCount: 1,
unapprovedTypedMessagesCount: 1, unapprovedTypedMessagesCount: 1,
network: '5', networkId: '5',
provider: { provider: {
chainId: '0x5', chainId: '0x5',
}, },

View File

@ -24,6 +24,7 @@ import {
CHAIN_ID_TO_RPC_URL_MAP, CHAIN_ID_TO_RPC_URL_MAP,
CHAIN_IDS, CHAIN_IDS,
NETWORK_TYPES, NETWORK_TYPES,
NetworkStatus,
} from '../../shared/constants/network'; } from '../../shared/constants/network';
import { import {
WebHIDConnectedStatuses, WebHIDConnectedStatuses,
@ -77,16 +78,13 @@ import { getPermissionSubjects } from './permissions';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
/** /**
* One of the only remaining valid uses of selecting the network subkey of the * Returns true if the currently selected network is inaccessible or whether no
* metamask state tree is to determine if the network is currently 'loading'. * provider has been set yet for the currently selected network.
* *
* This will be used for all cases where this state key is accessed only for that * @param {object} state - Redux state object.
* purpose.
*
* @param {object} state - redux state object
*/ */
export function isNetworkLoading(state) { export function isNetworkLoading(state) {
return state.metamask.network === 'loading'; return state.metamask.networkStatus !== NetworkStatus.Available;
} }
export function getNetworkIdentifier(state) { export function getNetworkIdentifier(state) {
@ -248,7 +246,7 @@ export function getAccountType(state) {
* @param {object} state - redux state object * @param {object} state - redux state object
*/ */
export function deprecatedGetCurrentNetworkId(state) { export function deprecatedGetCurrentNetworkId(state) {
return state.metamask.network; return state.metamask.networkId ?? 'loading';
} }
export const getMetaMaskAccounts = createSelector( export const getMetaMaskAccounts = createSelector(

View File

@ -29,14 +29,14 @@ export const incomingTxListSelector = (state) => {
} }
const { const {
network, networkId,
provider: { chainId }, provider: { chainId },
} = state.metamask; } = state.metamask;
const selectedAddress = getSelectedAddress(state); const selectedAddress = getSelectedAddress(state);
return Object.values(state.metamask.incomingTransactions).filter( return Object.values(state.metamask.incomingTransactions).filter(
(tx) => (tx) =>
tx.txParams.to === selectedAddress && tx.txParams.to === selectedAddress &&
transactionMatchesNetwork(tx, chainId, network), transactionMatchesNetwork(tx, chainId, networkId),
); );
}; };
export const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs; export const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs;

View File

@ -6,6 +6,7 @@ import { GasEstimateType, GasFeeEstimates } from '@metamask/gas-fee-controller';
import rootReducer from '../ducks'; import rootReducer from '../ducks';
import { LedgerTransportTypes } from '../../shared/constants/hardware-wallets'; import { LedgerTransportTypes } from '../../shared/constants/hardware-wallets';
import { TransactionMeta } from '../../shared/constants/transaction'; import { TransactionMeta } from '../../shared/constants/transaction';
import type { NetworkStatus } from '../../shared/constants/network';
/** /**
* This interface is temporary and is copied from the message-manager.js file * This interface is temporary and is copied from the message-manager.js file
@ -65,7 +66,8 @@ interface TemporaryBackgroundState {
unapprovedMsgs: MessagesIndexedById; unapprovedMsgs: MessagesIndexedById;
unapprovedPersonalMsgs: MessagesIndexedById; unapprovedPersonalMsgs: MessagesIndexedById;
unapprovedTypedMessages: MessagesIndexedById; unapprovedTypedMessages: MessagesIndexedById;
network: string; networkId: string | null;
networkStatus: NetworkStatus;
pendingApprovals: ApprovalControllerState['pendingApprovals']; pendingApprovals: ApprovalControllerState['pendingApprovals'];
knownMethodData?: { knownMethodData?: {
[fourBytePrefix: string]: Record<string, unknown>; [fourBytePrefix: string]: Record<string, unknown>;