mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-21 17:37:01 +01:00
Convert NetworkController to TS (#18358)
Converting this controller to TypeScript furthers the goal of getting this whole codebase converted, of course, but it also helps in comparing the differences between this version of the NetworkController and the version in the `core` repo more easily, which will ultimately help us in coalescing the two implementations. Co-authored-by: Mark Stacey <markjstacey@gmail.com> Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com>
This commit is contained in:
parent
16bfa1f728
commit
3577d3545f
@ -13,7 +13,7 @@ import { convertHexToDecimal } from '@metamask/controller-utils';
|
||||
import { NETWORK_TYPES } from '../../../shared/constants/network';
|
||||
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
||||
import DetectTokensController from './detect-tokens';
|
||||
import NetworkController, { NetworkControllerEventTypes } from './network';
|
||||
import { NetworkController, NetworkControllerEventType } from './network';
|
||||
import PreferencesController from './preferences';
|
||||
|
||||
describe('DetectTokensController', function () {
|
||||
@ -248,7 +248,7 @@ describe('DetectTokensController', function () {
|
||||
),
|
||||
onNetworkStateChange: (cb) =>
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
() => {
|
||||
const networkState = network.store.getState();
|
||||
const modifiedNetworkState = {
|
||||
|
@ -1 +0,0 @@
|
||||
export { default, NetworkControllerEventTypes } from './network-controller';
|
1
app/scripts/controllers/network/index.ts
Normal file
1
app/scripts/controllers/network/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './network-controller';
|
@ -1,679 +0,0 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import EventEmitter from 'events';
|
||||
import { ComposedStore, ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import {
|
||||
createSwappableProxy,
|
||||
createEventEmitterProxy,
|
||||
} from '@metamask/swappable-obj-proxy';
|
||||
import EthQuery from 'eth-query';
|
||||
// ControllerMessenger is referred to in the JSDocs
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { v4 as random } from 'uuid';
|
||||
import { hasProperty, isPlainObject } from '@metamask/utils';
|
||||
import { errorCodes } from 'eth-rpc-errors';
|
||||
import {
|
||||
INFURA_PROVIDER_TYPES,
|
||||
BUILT_IN_NETWORKS,
|
||||
INFURA_BLOCKED_KEY,
|
||||
TEST_NETWORK_TICKER_MAP,
|
||||
CHAIN_IDS,
|
||||
NETWORK_TYPES,
|
||||
NetworkStatus,
|
||||
} from '../../../../shared/constants/network';
|
||||
import {
|
||||
isPrefixedFormattedHexString,
|
||||
isSafeChainId,
|
||||
} from '../../../../shared/modules/network.utils';
|
||||
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
||||
import { createNetworkClient } from './create-network-client';
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
function buildDefaultProviderConfigState() {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: NETWORK_TYPES.MAINNET,
|
||||
chainId: CHAIN_IDS.MAINNET,
|
||||
ticker: 'ETH',
|
||||
};
|
||||
}
|
||||
|
||||
function buildDefaultNetworkIdState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildDefaultNetworkStatusState() {
|
||||
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.
|
||||
*/
|
||||
export const NetworkControllerEventTypes = {
|
||||
/**
|
||||
* Fired after the current network is changed.
|
||||
*/
|
||||
NetworkDidChange: `${name}:networkDidChange`,
|
||||
/**
|
||||
* Fired when there is a request to change the current network, but no state
|
||||
* changes have occurred yet.
|
||||
*/
|
||||
NetworkWillChange: `${name}:networkWillChange`,
|
||||
/**
|
||||
* Fired after the network is changed to an Infura network, but when Infura
|
||||
* returns an error denying support for the user's location.
|
||||
*/
|
||||
InfuraIsBlocked: `${name}:infuraIsBlocked`,
|
||||
/**
|
||||
* Fired after the network is changed to an Infura network and Infura does not
|
||||
* return an error denying support for the user's location, or after the
|
||||
* network is changed to a custom network.
|
||||
*/
|
||||
InfuraIsUnblocked: `${name}:infuraIsUnblocked`,
|
||||
};
|
||||
|
||||
export default class NetworkController extends EventEmitter {
|
||||
/**
|
||||
* Construct a NetworkController.
|
||||
*
|
||||
* @param {object} options - Options for this controller.
|
||||
* @param {ControllerMessenger} options.messenger - The controller messenger.
|
||||
* @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({
|
||||
messenger,
|
||||
state = {},
|
||||
infuraProjectId,
|
||||
trackMetaMetricsEvent,
|
||||
} = {}) {
|
||||
super();
|
||||
|
||||
this.messenger = messenger;
|
||||
|
||||
// create stores
|
||||
this.providerStore = new ObservableStore(
|
||||
state.provider || buildDefaultProviderConfigState(),
|
||||
);
|
||||
this.previousProviderStore = new ObservableStore(
|
||||
this.providerStore.getState(),
|
||||
);
|
||||
this.networkIdStore = new ObservableStore(buildDefaultNetworkIdState());
|
||||
this.networkStatusStore = new ObservableStore(
|
||||
buildDefaultNetworkStatusState(),
|
||||
);
|
||||
// We need to keep track of a few details about the current network.
|
||||
// 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(
|
||||
state.networkDetails || buildDefaultNetworkDetailsState(),
|
||||
);
|
||||
|
||||
this.networkConfigurationsStore = new ObservableStore(
|
||||
state.networkConfigurations || buildDefaultNetworkConfigurationsState(),
|
||||
);
|
||||
|
||||
this.store = new ComposedStore({
|
||||
provider: this.providerStore,
|
||||
previousProviderStore: this.previousProviderStore,
|
||||
networkId: this.networkIdStore,
|
||||
networkStatus: this.networkStatusStore,
|
||||
networkDetails: this.networkDetails,
|
||||
networkConfigurations: this.networkConfigurationsStore,
|
||||
});
|
||||
|
||||
// provider and block tracker
|
||||
this._provider = null;
|
||||
this._blockTracker = null;
|
||||
|
||||
// provider and block tracker proxies - because the network changes
|
||||
this._providerProxy = null;
|
||||
this._blockTrackerProxy = null;
|
||||
|
||||
if (!infuraProjectId || typeof infuraProjectId !== 'string') {
|
||||
throw new Error('Invalid Infura project ID');
|
||||
}
|
||||
this._infuraProjectId = infuraProjectId;
|
||||
|
||||
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the network controller, stopping any ongoing polling.
|
||||
*
|
||||
* In-progress requests will not be aborted.
|
||||
*/
|
||||
async destroy() {
|
||||
await this._blockTracker?.destroy();
|
||||
}
|
||||
|
||||
async initializeProvider() {
|
||||
const { type, rpcUrl, chainId } = this.providerStore.getState();
|
||||
this._configureProvider({ type, rpcUrl, chainId });
|
||||
await this.lookupNetwork();
|
||||
}
|
||||
|
||||
// return the proxies so the references will always be good
|
||||
getProviderAndBlockTracker() {
|
||||
const provider = this._providerProxy;
|
||||
const blockTracker = this._blockTrackerProxy;
|
||||
return { provider, blockTracker };
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the network supports EIP-1559 by checking whether the
|
||||
* latest block has a `baseFeePerGas` property, then updates state
|
||||
* appropriately.
|
||||
*
|
||||
* @returns {Promise<boolean>} A promise that resolves to true if the network
|
||||
* supports EIP-1559 and false otherwise.
|
||||
*/
|
||||
async getEIP1559Compatibility() {
|
||||
const { EIPS } = this.networkDetails.getState();
|
||||
// NOTE: This isn't necessary anymore because the block cache middleware
|
||||
// already prevents duplicate requests from taking place
|
||||
if (EIPS[1559] !== undefined) {
|
||||
return EIPS[1559];
|
||||
}
|
||||
const supportsEIP1559 = await this._determineEIP1559Compatibility();
|
||||
this.networkDetails.updateState({
|
||||
EIPS: {
|
||||
...this.networkDetails.getState().EIPS,
|
||||
1559: 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() {
|
||||
const { chainId, type } = this.providerStore.getState();
|
||||
let networkChanged = false;
|
||||
let networkId;
|
||||
let supportsEIP1559;
|
||||
let networkStatus;
|
||||
|
||||
if (!this._provider) {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing provider',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chainId) {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing chainId',
|
||||
);
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkStatus();
|
||||
this._resetNetworkDetails();
|
||||
return;
|
||||
}
|
||||
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type);
|
||||
|
||||
const listener = () => {
|
||||
networkChanged = true;
|
||||
this.messenger.unsubscribe(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkChanged) {
|
||||
// If the network has changed, then `lookupNetwork` either has been or is
|
||||
// in the process of being called, so we don't need to go further.
|
||||
return;
|
||||
}
|
||||
this.messenger.unsubscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
|
||||
this.networkStatusStore.putState(networkStatus);
|
||||
|
||||
if (networkStatus === NetworkStatus.Available) {
|
||||
this.networkIdStore.putState(networkId);
|
||||
this.networkDetails.updateState({
|
||||
EIPS: {
|
||||
...this.networkDetails.getState().EIPS,
|
||||
1559: supportsEIP1559,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkDetails();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
...targetNetwork,
|
||||
});
|
||||
|
||||
return targetNetwork.rpcUrl;
|
||||
}
|
||||
|
||||
setProviderType(type) {
|
||||
assert.notStrictEqual(
|
||||
type,
|
||||
NETWORK_TYPES.RPC,
|
||||
`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, blockExplorerUrl } = BUILT_IN_NETWORKS[type];
|
||||
this._setProviderConfig({
|
||||
type,
|
||||
rpcUrl: '',
|
||||
chainId,
|
||||
ticker: ticker ?? 'ETH',
|
||||
nickname: '',
|
||||
rpcPrefs: { blockExplorerUrl },
|
||||
});
|
||||
}
|
||||
|
||||
resetConnection() {
|
||||
this._setProviderConfig(this.providerStore.getState());
|
||||
}
|
||||
|
||||
rollbackToPreviousProvider() {
|
||||
const config = this.previousProviderStore.getState();
|
||||
this.providerStore.putState(config);
|
||||
this._switchNetwork(config);
|
||||
}
|
||||
|
||||
//
|
||||
// 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
|
||||
*
|
||||
* @returns {string} The network ID for the current network.
|
||||
*/
|
||||
async _getNetworkId() {
|
||||
const { provider } = this.getProviderAndBlockTracker();
|
||||
const ethQuery = new EthQuery(provider);
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
ethQuery.sendAsync({ method: 'net_version' }, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored network ID.
|
||||
*/
|
||||
_resetNetworkId() {
|
||||
this.networkIdStore.putState(buildDefaultNetworkIdState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets network status to the default ("unknown").
|
||||
*/
|
||||
_resetNetworkStatus() {
|
||||
this.networkStatusStore.putState(buildDefaultNetworkStatusState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears details previously stored for the network.
|
||||
*/
|
||||
_resetNetworkDetails() {
|
||||
this.networkDetails.putState(buildDefaultNetworkDetailsState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider config and switches the network.
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
_setProviderConfig(config) {
|
||||
this.previousProviderStore.putState(this.providerStore.getState());
|
||||
this.providerStore.putState(config);
|
||||
this._switchNetwork(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the latest block from the currently selected network; if the
|
||||
* block has a `baseFeePerGas` property, then we know that the network
|
||||
* supports EIP-1559; otherwise it doesn't.
|
||||
*
|
||||
* @returns {Promise<boolean>} A promise that resolves to true if the network
|
||||
* supports EIP-1559 and false otherwise.
|
||||
*/
|
||||
async _determineEIP1559Compatibility() {
|
||||
const latestBlock = await this._getLatestBlock();
|
||||
return latestBlock && latestBlock.baseFeePerGas !== undefined;
|
||||
}
|
||||
|
||||
_switchNetwork(opts) {
|
||||
this.messenger.publish(NetworkControllerEventTypes.NetworkWillChange);
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkStatus();
|
||||
this._resetNetworkDetails();
|
||||
this._configureProvider(opts);
|
||||
this.messenger.publish(NetworkControllerEventTypes.NetworkDidChange);
|
||||
this.lookupNetwork();
|
||||
}
|
||||
|
||||
_configureProvider({ type, rpcUrl, chainId }) {
|
||||
// infura type-based endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type);
|
||||
if (isInfura) {
|
||||
this._configureInfuraProvider({
|
||||
type,
|
||||
infuraProjectId: this._infuraProjectId,
|
||||
});
|
||||
// url-based rpc endpoints
|
||||
} else if (type === NETWORK_TYPES.RPC) {
|
||||
this._configureStandardProvider(rpcUrl, chainId);
|
||||
} else {
|
||||
throw new Error(
|
||||
`NetworkController - _configureProvider - unknown type "${type}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_configureInfuraProvider({ type, infuraProjectId }) {
|
||||
log.info('NetworkController - configureInfuraProvider', type);
|
||||
const { provider, blockTracker } = createNetworkClient({
|
||||
network: type,
|
||||
infuraProjectId,
|
||||
type: 'infura',
|
||||
});
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
_configureStandardProvider(rpcUrl, chainId) {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl);
|
||||
const { provider, blockTracker } = createNetworkClient({
|
||||
chainId,
|
||||
rpcUrl,
|
||||
type: 'custom',
|
||||
});
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
_setProviderAndBlockTracker({ provider, blockTracker }) {
|
||||
// update or initialize proxies
|
||||
if (this._providerProxy) {
|
||||
this._providerProxy.setTarget(provider);
|
||||
} else {
|
||||
this._providerProxy = createSwappableProxy(provider);
|
||||
}
|
||||
if (this._blockTrackerProxy) {
|
||||
this._blockTrackerProxy.setTarget(blockTracker);
|
||||
} else {
|
||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||
eventFilter: 'skipInternal',
|
||||
});
|
||||
}
|
||||
// set new provider and blockTracker
|
||||
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.putState({
|
||||
...networkConfigurations,
|
||||
[newNetworkConfigurationId]: {
|
||||
...newNetworkConfiguration,
|
||||
id: newNetworkConfigurationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!oldNetworkConfigurationId) {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Custom Network Added',
|
||||
category: MetaMetricsEventCategory.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);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import sinon from 'sinon';
|
||||
import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network';
|
||||
import { MetaMetricsNetworkEventSource } from '../../../../shared/constants/metametrics';
|
||||
import NetworkController from './network-controller';
|
||||
import { NetworkController } from './network-controller';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
@ -1100,7 +1100,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
|
||||
describe('when the request for the latest block responds with null', () => {
|
||||
it('stores null as whether the network supports EIP-1559', async () => {
|
||||
it('persists false to state as whether the network supports EIP-1559', async () => {
|
||||
await withController(
|
||||
{
|
||||
state: {
|
||||
@ -1118,13 +1118,13 @@ describe('NetworkController', () => {
|
||||
await controller.getEIP1559Compatibility();
|
||||
|
||||
expect(controller.store.getState().networkDetails.EIPS[1559]).toBe(
|
||||
null,
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null', async () => {
|
||||
it('returns false', async () => {
|
||||
await withController(async ({ controller, network }) => {
|
||||
network.mockEssentialRpcCalls({
|
||||
latestBlock: null,
|
||||
@ -1133,7 +1133,7 @@ describe('NetworkController', () => {
|
||||
|
||||
const supportsEIP1559 = await controller.getEIP1559Compatibility();
|
||||
|
||||
expect(supportsEIP1559).toBe(null);
|
||||
expect(supportsEIP1559).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
1171
app/scripts/controllers/network/network-controller.ts
Normal file
1171
app/scripts/controllers/network/network-controller.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { TokenListController } from '@metamask/assets-controllers';
|
||||
import { CHAIN_IDS } from '../../../shared/constants/network';
|
||||
import PreferencesController from './preferences';
|
||||
import NetworkController from './network';
|
||||
import { NetworkController } from './network';
|
||||
|
||||
describe('preferences controller', function () {
|
||||
let preferencesController;
|
||||
|
@ -143,8 +143,9 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware';
|
||||
import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
|
||||
import { setupMultiplex } from './lib/stream-utils';
|
||||
import EnsController from './controllers/ens';
|
||||
import NetworkController, {
|
||||
NetworkControllerEventTypes,
|
||||
import {
|
||||
NetworkController,
|
||||
NetworkControllerEventType,
|
||||
} from './controllers/network';
|
||||
import PreferencesController from './controllers/preferences';
|
||||
import AppStateController from './controllers/app-state';
|
||||
@ -263,7 +264,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
const networkControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'NetworkController',
|
||||
allowedEvents: Object.values(NetworkControllerEventTypes),
|
||||
allowedEvents: Object.values(NetworkControllerEventType),
|
||||
});
|
||||
this.networkController = new NetworkController({
|
||||
messenger: networkControllerMessenger,
|
||||
@ -310,11 +311,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
initLangCode: opts.initLangCode,
|
||||
onInfuraIsBlocked: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.InfuraIsBlocked,
|
||||
NetworkControllerEventType.InfuraIsBlocked,
|
||||
),
|
||||
onInfuraIsUnblocked: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.InfuraIsUnblocked,
|
||||
NetworkControllerEventType.InfuraIsUnblocked,
|
||||
),
|
||||
tokenListController: this.tokenListController,
|
||||
provider: this.provider,
|
||||
@ -452,7 +453,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
preferencesStore: this.preferencesController.store,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getNetworkIdentifier: () => {
|
||||
const { type, rpcUrl } =
|
||||
@ -491,7 +492,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
// onNetworkDidChange
|
||||
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getCurrentNetworkEIP1559Compatibility:
|
||||
this.networkController.getEIP1559Compatibility.bind(
|
||||
@ -609,7 +610,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
});
|
||||
|
||||
@ -621,7 +622,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
blockTracker: this.blockTracker,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
@ -1106,7 +1107,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
async () => {
|
||||
const { ticker } = this.networkController.store.getState().provider;
|
||||
try {
|
||||
@ -1152,7 +1153,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
networkController: this.networkController,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
provider: this.provider,
|
||||
getProviderConfig: () => this.networkController.store.getState().provider,
|
||||
@ -1195,7 +1196,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
() => {
|
||||
this.accountTracker._updateAccounts();
|
||||
},
|
||||
@ -1203,7 +1204,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
// clear unapproved transactions and messages when the network will change
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkWillChange,
|
||||
NetworkControllerEventType.NetworkWillChange,
|
||||
() => {
|
||||
this.txController.txStateManager.clearUnapprovedTxs();
|
||||
this.encryptionPublicKeyManager.clearUnapproved();
|
||||
|
@ -249,7 +249,7 @@
|
||||
"@metamask/message-manager": "^2.1.0",
|
||||
"@metamask/metamask-eth-abis": "^3.0.0",
|
||||
"@metamask/notification-controller": "^1.0.0",
|
||||
"@metamask/obs-store": "^8.0.0",
|
||||
"@metamask/obs-store": "^8.1.0",
|
||||
"@metamask/permission-controller": "^3.1.0",
|
||||
"@metamask/phishing-controller": "^2.0.0",
|
||||
"@metamask/post-message-stream": "^6.0.0",
|
||||
|
@ -238,7 +238,7 @@ export const INFURA_PROVIDER_TYPES = [
|
||||
NETWORK_TYPES.MAINNET,
|
||||
NETWORK_TYPES.GOERLI,
|
||||
NETWORK_TYPES.SEPOLIA,
|
||||
];
|
||||
] as const;
|
||||
|
||||
export const TEST_CHAINS = [
|
||||
CHAIN_IDS.GOERLI,
|
||||
|
50
types/eth-query.d.ts
vendored
Normal file
50
types/eth-query.d.ts
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
declare module 'eth-query' {
|
||||
// What it says on the tin. We omit `null` because confusingly, this is used
|
||||
// for a successful response to indicate a lack of an error.
|
||||
type EverythingButNull =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| object
|
||||
| symbol
|
||||
| undefined;
|
||||
|
||||
type ProviderSendAsyncResponse<Result> = {
|
||||
error?: { message: string };
|
||||
result?: Result;
|
||||
};
|
||||
|
||||
type ProviderSendAsyncCallback<Result> = (
|
||||
error: unknown,
|
||||
response: ProviderSendAsyncResponse<Result>,
|
||||
) => void;
|
||||
|
||||
type Provider = {
|
||||
sendAsync<Params, Result>(
|
||||
payload: SendAsyncPayload<Params>,
|
||||
callback: ProviderSendAsyncCallback<Result>,
|
||||
): void;
|
||||
};
|
||||
|
||||
type SendAsyncPayload<Params> = {
|
||||
id: number;
|
||||
jsonrpc: '2.0';
|
||||
method: string;
|
||||
params: Params;
|
||||
};
|
||||
|
||||
type SendAsyncCallback<Result> = (
|
||||
...args:
|
||||
| [error: EverythingButNull, result: undefined]
|
||||
| [error: null, result: Result]
|
||||
) => void;
|
||||
|
||||
export default class EthQuery {
|
||||
constructor(provider: Provider);
|
||||
|
||||
sendAsync<Params, Result>(
|
||||
opts: Partial<SendAsyncPayload<Params>>,
|
||||
callback: SendAsyncCallback<Result>,
|
||||
): void;
|
||||
}
|
||||
}
|
10
yarn.lock
10
yarn.lock
@ -4152,13 +4152,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/obs-store@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "@metamask/obs-store@npm:8.0.0"
|
||||
"@metamask/obs-store@npm:^8.1.0":
|
||||
version: 8.1.0
|
||||
resolution: "@metamask/obs-store@npm:8.1.0"
|
||||
dependencies:
|
||||
"@metamask/safe-event-emitter": ^2.0.0
|
||||
through2: ^2.0.3
|
||||
checksum: 232362e65a3563f0bd3299cec48f5adb37e68d4f066b7de90f2b044480d3b16c2d918c12d672c825e1d9b55344ae818fb8494d91129e4613555097653b9bb887
|
||||
checksum: 92356067fa3517526d656f2f0bdfbc4d39f65e27fb30d84240cfc9c1aa9cd5d743498952df18ed8efbb8887b6cc1bc1fab37bde3fb0fc059539e0dfcc67ff86f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24308,7 +24308,7 @@ __metadata:
|
||||
"@metamask/message-manager": ^2.1.0
|
||||
"@metamask/metamask-eth-abis": ^3.0.0
|
||||
"@metamask/notification-controller": ^1.0.0
|
||||
"@metamask/obs-store": ^8.0.0
|
||||
"@metamask/obs-store": ^8.1.0
|
||||
"@metamask/permission-controller": ^3.1.0
|
||||
"@metamask/phishing-controller": ^2.0.0
|
||||
"@metamask/phishing-warning": ^2.1.0
|
||||
|
Loading…
Reference in New Issue
Block a user