@metamask/network-controller
Version:
Provides an interface to the currently selected network via a MetaMask-compatible provider object
917 lines • 89.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _NetworkController_instances, _NetworkController_ethQuery, _NetworkController_infuraProjectId, _NetworkController_previouslySelectedNetworkClientId, _NetworkController_providerProxy, _NetworkController_blockTrackerProxy, _NetworkController_autoManagedNetworkClientRegistry, _NetworkController_autoManagedNetworkClient, _NetworkController_log, _NetworkController_getRpcServiceOptions, _NetworkController_getBlockTrackerOptions, _NetworkController_networkConfigurationsByNetworkClientId, _NetworkController_isRpcFailoverEnabled, _NetworkController_updateRpcFailoverEnabled, _NetworkController_refreshNetwork, _NetworkController_determineNetworkMetadata, _NetworkController_lookupGivenNetwork, _NetworkController_lookupSelectedNetwork, _NetworkController_updateMetadataForNetwork, _NetworkController_getLatestBlock, _NetworkController_determineEIP1559Compatibility, _NetworkController_validateNetworkFields, _NetworkController_determineNetworkConfigurationToPersist, _NetworkController_registerNetworkClientsAsNeeded, _NetworkController_unregisterNetworkClientsAsNeeded, _NetworkController_updateNetworkConfigurations, _NetworkController_ensureAutoManagedNetworkClientRegistryPopulated, _NetworkController_createAutoManagedNetworkClientRegistry, _NetworkController_applyNetworkSelection;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkController = exports.selectAvailableNetworkClientIds = exports.getAvailableNetworkClientIds = exports.selectNetworkConfigurations = exports.getNetworkConfigurations = exports.getDefaultNetworkControllerState = exports.knownKeysOf = exports.RpcEndpointType = void 0;
const base_controller_1 = require("@metamask/base-controller");
const controller_utils_1 = require("@metamask/controller-utils");
const eth_query_1 = __importDefault(require("@metamask/eth-query"));
const rpc_errors_1 = require("@metamask/rpc-errors");
const swappable_obj_proxy_1 = require("@metamask/swappable-obj-proxy");
const utils_1 = require("@metamask/utils");
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
const immer_1 = require("immer");
const lodash_1 = require("lodash");
const reselect_1 = require("reselect");
const URI = __importStar(require("uri-js"));
const uuid_1 = require("uuid");
const constants_1 = require("./constants.cjs");
const create_auto_managed_network_client_1 = require("./create-auto-managed-network-client.cjs");
const logger_1 = require("./logger.cjs");
const types_1 = require("./types.cjs");
const debugLog = (0, logger_1.createModuleLogger)(logger_1.projectLogger, 'NetworkController');
const INFURA_URL_REGEX = /^https:\/\/(?<networkName>[^.]+)\.infura\.io\/v\d+\/(?<apiKey>.+)$/u;
/**
* The type of an RPC endpoint.
*
* @see {@link CustomRpcEndpoint}
* @see {@link InfuraRpcEndpoint}
*/
var RpcEndpointType;
(function (RpcEndpointType) {
RpcEndpointType["Custom"] = "custom";
RpcEndpointType["Infura"] = "infura";
})(RpcEndpointType || (exports.RpcEndpointType = RpcEndpointType = {}));
/**
* `Object.keys()` is intentionally generic: it returns the keys of an object,
* but it cannot make guarantees about the contents of that object, so the type
* of the keys is merely `string[]`. While this is technically accurate, it is
* also unnecessary if we have an object that we own and whose contents are
* known exactly.
*
* TODO: Move to @metamask/utils.
*
* @param object - The object.
* @returns The keys of an object, typed according to the type of the object
* itself.
*/
function knownKeysOf(
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
object) {
return Object.keys(object);
}
exports.knownKeysOf = knownKeysOf;
/**
* Type guard for determining whether the given value is an error object with a
* `code` property, such as an instance of Error.
*
* TODO: Move this to @metamask/utils.
*
* @param error - The object to check.
* @returns True if `error` has a `code`, false otherwise.
*/
function isErrorWithCode(error) {
return typeof error === 'object' && error !== null && 'code' in error;
}
const controllerName = 'NetworkController';
/**
* Constructs a value for the state property `networkConfigurationsByChainId`
* which will be used if it has not been provided to the constructor.
*
* @param [additionalDefaultNetworks] - An array of Hex Chain IDs representing the additional networks to be included as default.
* @returns The default value for `networkConfigurationsByChainId`.
*/
function getDefaultNetworkConfigurationsByChainId(additionalDefaultNetworks = []) {
const infuraNetworks = getDefaultInfuraNetworkConfigurationsByChainId();
const customNetworks = getDefaultCustomNetworkConfigurationsByChainId();
return additionalDefaultNetworks.reduce((obj, chainId) => {
if ((0, utils_1.hasProperty)(customNetworks, chainId)) {
obj[chainId] = customNetworks[chainId];
}
return obj;
},
// Always include the infura networks in the default networks
infuraNetworks);
}
/**
* Constructs a `networkConfigurationsByChainId` object for all default Infura networks.
*
* @returns The `networkConfigurationsByChainId` object of all Infura networks.
*/
function getDefaultInfuraNetworkConfigurationsByChainId() {
return Object.values(controller_utils_1.InfuraNetworkType).reduce((obj, infuraNetworkType) => {
const chainId = controller_utils_1.ChainId[infuraNetworkType];
// Skip deprecated network as default network.
if (constants_1.DEPRECATED_NETWORKS.has(chainId)) {
return obj;
}
const rpcEndpointUrl = `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`;
const networkConfiguration = {
blockExplorerUrls: [],
chainId,
defaultRpcEndpointIndex: 0,
name: controller_utils_1.NetworkNickname[infuraNetworkType],
nativeCurrency: controller_utils_1.NetworksTicker[infuraNetworkType],
rpcEndpoints: [
{
failoverUrls: [],
networkClientId: infuraNetworkType,
type: RpcEndpointType.Infura,
url: rpcEndpointUrl,
},
],
};
return { ...obj, [chainId]: networkConfiguration };
}, {});
}
/**
* Constructs a `networkConfigurationsByChainId` object for all default custom networks.
*
* @returns The `networkConfigurationsByChainId` object of all custom networks.
*/
function getDefaultCustomNetworkConfigurationsByChainId() {
// Create the `networkConfigurationsByChainId` objects explicitly,
// Because it is not always guaranteed that the custom networks are included in the
// default networks.
return {
[controller_utils_1.ChainId['megaeth-testnet']]: getCustomNetworkConfiguration(controller_utils_1.CustomNetworkType['megaeth-testnet']),
[controller_utils_1.ChainId['megaeth-testnet-v2']]: getCustomNetworkConfiguration(controller_utils_1.CustomNetworkType['megaeth-testnet-v2']),
[controller_utils_1.ChainId['monad-testnet']]: getCustomNetworkConfiguration(controller_utils_1.CustomNetworkType['monad-testnet']),
};
}
/**
* Constructs a `NetworkConfiguration` object by `CustomNetworkType`.
*
* @param customNetworkType - The type of the custom network.
* @returns The `NetworkConfiguration` object.
*/
function getCustomNetworkConfiguration(customNetworkType) {
const { ticker, rpcPrefs } = controller_utils_1.BUILT_IN_NETWORKS[customNetworkType];
const rpcEndpointUrl = controller_utils_1.BUILT_IN_CUSTOM_NETWORKS_RPC[customNetworkType];
return {
blockExplorerUrls: [rpcPrefs.blockExplorerUrl],
chainId: controller_utils_1.ChainId[customNetworkType],
defaultRpcEndpointIndex: 0,
defaultBlockExplorerUrlIndex: 0,
name: controller_utils_1.NetworkNickname[customNetworkType],
nativeCurrency: ticker,
rpcEndpoints: [
{
failoverUrls: [],
networkClientId: customNetworkType,
type: RpcEndpointType.Custom,
url: rpcEndpointUrl,
},
],
};
}
/**
* Constructs properties for the NetworkController state whose values will be
* used if not provided to the constructor.
*
* @param [additionalDefaultNetworks] - An array of Hex Chain IDs representing the additional networks to be included as default.
* @returns The default NetworkController state.
*/
function getDefaultNetworkControllerState(additionalDefaultNetworks) {
const networksMetadata = {};
const networkConfigurationsByChainId = getDefaultNetworkConfigurationsByChainId(additionalDefaultNetworks);
return {
selectedNetworkClientId: controller_utils_1.InfuraNetworkType.mainnet,
networksMetadata,
networkConfigurationsByChainId,
};
}
exports.getDefaultNetworkControllerState = getDefaultNetworkControllerState;
/**
* Redux selector for getting all network configurations from NetworkController
* state, keyed by chain ID.
*
* @param state - NetworkController state
* @returns All registered network configurations, keyed by chain ID.
*/
const selectNetworkConfigurationsByChainId = (state) => state.networkConfigurationsByChainId;
/**
* Get a list of all network configurations.
*
* @param state - NetworkController state
* @returns A list of all available network configurations
*/
function getNetworkConfigurations(state) {
return Object.values(state.networkConfigurationsByChainId);
}
exports.getNetworkConfigurations = getNetworkConfigurations;
/**
* Redux selector for getting a list of all network configurations from
* NetworkController state.
*
* @param state - NetworkController state
* @returns A list of all available network configurations
*/
exports.selectNetworkConfigurations = (0, reselect_1.createSelector)(selectNetworkConfigurationsByChainId, (networkConfigurationsByChainId) => Object.values(networkConfigurationsByChainId));
/**
* Get a list of all available network client IDs from a list of network
* configurations.
*
* @param networkConfigurations - The array of network configurations
* @returns A list of all available client IDs
*/
function getAvailableNetworkClientIds(networkConfigurations) {
return networkConfigurations.flatMap((networkConfiguration) => networkConfiguration.rpcEndpoints.map((rpcEndpoint) => rpcEndpoint.networkClientId));
}
exports.getAvailableNetworkClientIds = getAvailableNetworkClientIds;
/**
* Redux selector for getting a list of all available network client IDs
* from NetworkController state.
*
* @param state - NetworkController state
* @returns A list of all available network client IDs.
*/
exports.selectAvailableNetworkClientIds = (0, reselect_1.createSelector)(exports.selectNetworkConfigurations, getAvailableNetworkClientIds);
/**
* Determines whether the given URL is valid by attempting to parse it.
*
* @param url - The URL to test.
* @returns True if the URL is valid, false otherwise.
*/
function isValidUrl(url) {
const uri = URI.parse(url);
return (uri.error === undefined && (uri.scheme === 'http' || uri.scheme === 'https'));
}
/**
* Given an Infura API URL, extracts the subdomain that identifies the Infura
* network.
*
* @param rpcEndpointUrl - The URL to operate on.
* @returns The Infura network name that the URL references.
* @throws if the URL is not an Infura API URL, or if an Infura network is not
* present in the URL.
*/
function deriveInfuraNetworkNameFromRpcEndpointUrl(rpcEndpointUrl) {
const match = INFURA_URL_REGEX.exec(rpcEndpointUrl);
if (match?.groups) {
if ((0, controller_utils_1.isInfuraNetworkType)(match.groups.networkName)) {
return match.groups.networkName;
}
throw new Error(`Unknown Infura network '${match.groups.networkName}'`);
}
throw new Error('Could not derive Infura network from RPC endpoint URL');
}
/**
* Performs a series of checks that the given NetworkController state is
* internally consistent — that all parts of state that are supposed to match in
* fact do — so that working with the state later on doesn't cause unexpected
* errors.
*
* In the case of NetworkController, there are several parts of state that need
* to match. For instance, `defaultRpcEndpointIndex` needs to match an entry
* within `rpcEndpoints`, and `selectedNetworkClientId` needs to point to an RPC
* endpoint within a network configuration.
*
* @param state - The NetworkController state to verify.
* @throws if the state is invalid in some way.
*/
function validateInitialState(state) {
const networkConfigurationEntries = Object.entries(state.networkConfigurationsByChainId);
const networkClientIds = getAvailableNetworkClientIds(getNetworkConfigurations(state));
if (networkConfigurationEntries.length === 0) {
throw new Error('NetworkController state is invalid: `networkConfigurationsByChainId` cannot be empty');
}
for (const [chainId, networkConfiguration] of networkConfigurationEntries) {
if (chainId !== networkConfiguration.chainId) {
throw new Error(`NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' is filed under '${chainId}' which does not match its \`chainId\` of '${networkConfiguration.chainId}'`);
}
const isInvalidDefaultBlockExplorerUrlIndex = networkConfiguration.blockExplorerUrls.length > 0
? networkConfiguration.defaultBlockExplorerUrlIndex === undefined ||
networkConfiguration.blockExplorerUrls[networkConfiguration.defaultBlockExplorerUrlIndex] === undefined
: networkConfiguration.defaultBlockExplorerUrlIndex !== undefined;
if (isInvalidDefaultBlockExplorerUrlIndex) {
throw new Error(`NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultBlockExplorerUrlIndex\` that does not refer to an entry in \`blockExplorerUrls\``);
}
if (networkConfiguration.rpcEndpoints[networkConfiguration.defaultRpcEndpointIndex] === undefined) {
throw new Error(`NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultRpcEndpointIndex\` that does not refer to an entry in \`rpcEndpoints\``);
}
}
if ([...new Set(networkClientIds)].length < networkClientIds.length) {
throw new Error('NetworkController state has invalid `networkConfigurationsByChainId`: Every RPC endpoint across all network configurations must have a unique `networkClientId`');
}
}
/**
* Checks that the given initial NetworkController state is internally
* consistent similar to `validateInitialState`, but if an anomaly is detected,
* it does its best to correct the state and logs an error to Sentry.
*
* @param state - The NetworkController state to verify.
* @param messenger - The NetworkController messenger.
* @returns The corrected state.
*/
function correctInitialState(state, messenger) {
const networkConfigurationsSortedByChainId = getNetworkConfigurations(state).sort((a, b) => a.chainId.localeCompare(b.chainId));
const availableNetworkClientIds = getAvailableNetworkClientIds(networkConfigurationsSortedByChainId);
const invalidNetworkClientIdsWithMetadata = Object.keys(state.networksMetadata).filter((networkClientId) => !availableNetworkClientIds.includes(networkClientId));
return (0, immer_1.produce)(state, (newState) => {
if (!availableNetworkClientIds.includes(state.selectedNetworkClientId)) {
const firstNetworkConfiguration = networkConfigurationsSortedByChainId[0];
const newSelectedNetworkClientId = firstNetworkConfiguration.rpcEndpoints[firstNetworkConfiguration.defaultRpcEndpointIndex].networkClientId;
messenger.call('ErrorReportingService:captureException', new Error(`\`selectedNetworkClientId\` '${state.selectedNetworkClientId}' does not refer to an RPC endpoint within a network configuration; correcting to '${newSelectedNetworkClientId}'`));
newState.selectedNetworkClientId = newSelectedNetworkClientId;
}
if (invalidNetworkClientIdsWithMetadata.length > 0) {
for (const invalidNetworkClientId of invalidNetworkClientIdsWithMetadata) {
delete newState.networksMetadata[invalidNetworkClientId];
}
messenger.call('ErrorReportingService:captureException', new Error('`networksMetadata` had invalid network client IDs, which have been removed'));
}
});
}
/**
* Transforms a map of chain ID to network configuration to a map of network
* client ID to network configuration.
*
* @param networkConfigurationsByChainId - The network configurations, keyed by
* chain ID.
* @returns The network configurations, keyed by network client ID.
*/
function buildNetworkConfigurationsByNetworkClientId(networkConfigurationsByChainId) {
return new Map(Object.values(networkConfigurationsByChainId).flatMap((networkConfiguration) => {
return networkConfiguration.rpcEndpoints.map((rpcEndpoint) => {
return [rpcEndpoint.networkClientId, networkConfiguration];
});
}));
}
/**
* Controller that creates and manages an Ethereum network provider.
*/
class NetworkController extends base_controller_1.BaseController {
/**
* Constructs a NetworkController.
*
* @param options - The options; see {@link NetworkControllerOptions}.
*/
constructor(options) {
const { messenger, state, infuraProjectId, log, getRpcServiceOptions, getBlockTrackerOptions, additionalDefaultNetworks, isRpcFailoverEnabled = false, } = options;
const initialState = {
...getDefaultNetworkControllerState(additionalDefaultNetworks),
...state,
};
validateInitialState(initialState);
const correctedInitialState = correctInitialState(initialState, messenger);
if (!infuraProjectId || typeof infuraProjectId !== 'string') {
throw new Error('Invalid Infura project ID');
}
super({
name: controllerName,
metadata: {
selectedNetworkClientId: {
includeInStateLogs: true,
persist: true,
includeInDebugSnapshot: false,
usedInUi: true,
},
networksMetadata: {
includeInStateLogs: true,
persist: true,
includeInDebugSnapshot: false,
usedInUi: true,
},
networkConfigurationsByChainId: {
includeInStateLogs: true,
persist: true,
includeInDebugSnapshot: false,
usedInUi: true,
},
},
messenger,
state: correctedInitialState,
});
_NetworkController_instances.add(this);
_NetworkController_ethQuery.set(this, void 0);
_NetworkController_infuraProjectId.set(this, void 0);
_NetworkController_previouslySelectedNetworkClientId.set(this, void 0);
_NetworkController_providerProxy.set(this, void 0);
_NetworkController_blockTrackerProxy.set(this, void 0);
_NetworkController_autoManagedNetworkClientRegistry.set(this, void 0);
_NetworkController_autoManagedNetworkClient.set(this, void 0);
_NetworkController_log.set(this, void 0);
_NetworkController_getRpcServiceOptions.set(this, void 0);
_NetworkController_getBlockTrackerOptions.set(this, void 0);
_NetworkController_networkConfigurationsByNetworkClientId.set(this, void 0);
_NetworkController_isRpcFailoverEnabled.set(this, void 0);
__classPrivateFieldSet(this, _NetworkController_infuraProjectId, infuraProjectId, "f");
__classPrivateFieldSet(this, _NetworkController_log, log, "f");
__classPrivateFieldSet(this, _NetworkController_getRpcServiceOptions, getRpcServiceOptions, "f");
__classPrivateFieldSet(this, _NetworkController_getBlockTrackerOptions, getBlockTrackerOptions, "f");
__classPrivateFieldSet(this, _NetworkController_isRpcFailoverEnabled, isRpcFailoverEnabled, "f");
__classPrivateFieldSet(this, _NetworkController_previouslySelectedNetworkClientId, this.state.selectedNetworkClientId, "f");
__classPrivateFieldSet(this, _NetworkController_networkConfigurationsByNetworkClientId, buildNetworkConfigurationsByNetworkClientId(this.state.networkConfigurationsByChainId), "f");
this.messenger.registerActionHandler(`${this.name}:getEthQuery`, () => {
return __classPrivateFieldGet(this, _NetworkController_ethQuery, "f");
});
this.messenger.registerActionHandler(`${this.name}:getNetworkClientById`, this.getNetworkClientById.bind(this));
this.messenger.registerActionHandler(`${this.name}:getEIP1559Compatibility`, this.getEIP1559Compatibility.bind(this));
this.messenger.registerActionHandler(`${this.name}:setActiveNetwork`, this.setActiveNetwork.bind(this));
this.messenger.registerActionHandler(`${this.name}:setProviderType`, this.setProviderType.bind(this));
this.messenger.registerActionHandler(`${this.name}:findNetworkClientIdByChainId`, this.findNetworkClientIdByChainId.bind(this));
this.messenger.registerActionHandler(`${this.name}:getNetworkConfigurationByChainId`, this.getNetworkConfigurationByChainId.bind(this));
this.messenger.registerActionHandler(`${this.name}:getNetworkConfigurationByNetworkClientId`, this.getNetworkConfigurationByNetworkClientId.bind(this));
this.messenger.registerActionHandler(`${this.name}:getSelectedNetworkClient`, this.getSelectedNetworkClient.bind(this));
this.messenger.registerActionHandler(`${this.name}:getSelectedChainId`, this.getSelectedChainId.bind(this));
this.messenger.registerActionHandler(`${this.name}:addNetwork`, this.addNetwork.bind(this));
this.messenger.registerActionHandler(`${this.name}:removeNetwork`, this.removeNetwork.bind(this));
this.messenger.registerActionHandler(`${this.name}:updateNetwork`, this.updateNetwork.bind(this));
this.messenger.subscribe(`${this.name}:rpcEndpointChainUnavailable`, ({ networkClientId }) => {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_updateMetadataForNetwork).call(this, networkClientId, {
networkStatus: constants_1.NetworkStatus.Unavailable,
});
});
this.messenger.subscribe(`${this.name}:rpcEndpointChainDegraded`, ({ networkClientId }) => {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_updateMetadataForNetwork).call(this, networkClientId, {
networkStatus: constants_1.NetworkStatus.Degraded,
});
});
this.messenger.subscribe(`${this.name}:rpcEndpointChainAvailable`, ({ networkClientId }) => {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_updateMetadataForNetwork).call(this, networkClientId, {
networkStatus: constants_1.NetworkStatus.Available,
});
});
}
/**
* Enables the RPC failover functionality. That is, if any RPC endpoints are
* configured with failover URLs, then traffic will automatically be diverted
* to them if those RPC endpoints are unavailable.
*/
enableRpcFailover() {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_updateRpcFailoverEnabled).call(this, true);
}
/**
* Disables the RPC failover functionality. That is, even if any RPC endpoints
* are configured with failover URLs, then traffic will not automatically be
* diverted to them if those RPC endpoints are unavailable.
*/
disableRpcFailover() {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_updateRpcFailoverEnabled).call(this, false);
}
/**
* Accesses the provider and block tracker for the currently selected network.
*
* @returns The proxy and block tracker proxies.
* @deprecated This method has been replaced by `getSelectedNetworkClient` (which has a more easily used return type) and will be removed in a future release.
*/
getProviderAndBlockTracker() {
return {
provider: __classPrivateFieldGet(this, _NetworkController_providerProxy, "f"),
blockTracker: __classPrivateFieldGet(this, _NetworkController_blockTrackerProxy, "f"),
};
}
/**
* Accesses the provider and block tracker for the currently selected network.
*
* @returns an object with the provider and block tracker proxies for the currently selected network.
*/
getSelectedNetworkClient() {
if (__classPrivateFieldGet(this, _NetworkController_providerProxy, "f") && __classPrivateFieldGet(this, _NetworkController_blockTrackerProxy, "f")) {
return {
provider: __classPrivateFieldGet(this, _NetworkController_providerProxy, "f"),
blockTracker: __classPrivateFieldGet(this, _NetworkController_blockTrackerProxy, "f"),
};
}
return undefined;
}
/**
* Accesses the chain ID from the selected network client.
*
* @returns The chain ID of the selected network client in hex format or undefined if there is no network client.
*/
getSelectedChainId() {
const networkConfiguration = this.getNetworkConfigurationByNetworkClientId(this.state.selectedNetworkClientId);
return networkConfiguration?.chainId;
}
/**
* Internally, the Infura and custom network clients are categorized by type
* so that when accessing either kind of network client, TypeScript knows
* which type to assign to the network client. For some cases it's more useful
* to be able to access network clients by ID instead of by type and then ID,
* so this function makes that possible.
*
* @returns The network clients registered so far, keyed by ID.
*/
getNetworkClientRegistry() {
const autoManagedNetworkClientRegistry = __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_ensureAutoManagedNetworkClientRegistryPopulated).call(this);
return Object.assign({}, autoManagedNetworkClientRegistry[types_1.NetworkClientType.Infura], autoManagedNetworkClientRegistry[types_1.NetworkClientType.Custom]);
}
getNetworkClientById(networkClientId) {
if (!networkClientId) {
throw new Error('No network client ID was provided.');
}
const autoManagedNetworkClientRegistry = __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_ensureAutoManagedNetworkClientRegistryPopulated).call(this);
if ((0, controller_utils_1.isInfuraNetworkType)(networkClientId)) {
const infuraNetworkClient = autoManagedNetworkClientRegistry[types_1.NetworkClientType.Infura][networkClientId];
// This is impossible to reach
/* istanbul ignore if */
if (!infuraNetworkClient) {
throw new Error(`No Infura network client was found with the ID "${networkClientId}".`);
}
return infuraNetworkClient;
}
const customNetworkClient = autoManagedNetworkClientRegistry[types_1.NetworkClientType.Custom][networkClientId];
if (!customNetworkClient) {
throw new Error(`No custom network client was found with the ID "${networkClientId}".`);
}
return customNetworkClient;
}
initializeProvider({ lookupNetwork = true, } = {}) {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_applyNetworkSelection).call(this, this.state.selectedNetworkClientId);
if (lookupNetwork) {
return this.lookupNetwork();
}
return undefined;
}
/**
* Uses a request for the latest block to gather the following information on
* the given or selected network, persisting it to state:
*
* - The connectivity status: whether it is available, geo-blocked (Infura
* only), unavailable, or unknown
* - The capabilities status: whether it supports EIP-1559, whether it does
* not, or whether it is unknown
*
* @param networkClientId - The ID of the network client to inspect.
* If no ID is provided, uses the currently selected network.
*/
async lookupNetwork(networkClientId) {
if (networkClientId) {
await __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_lookupGivenNetwork).call(this, networkClientId);
}
else {
await __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_lookupSelectedNetwork).call(this);
}
}
/**
* Uses a request for the latest block to gather the following information on
* the given network, persisting it to state:
*
* - The connectivity status: whether the network is available, geo-blocked
* (Infura only), unavailable, or unknown
* - The feature compatibility status: whether the network supports EIP-1559,
* whether it does not, or whether it is unknown
*
* @param networkClientId - The ID of the network client to inspect.
* @deprecated Please use `lookupNetwork` and pass a network client ID
* instead. This method will be removed in a future major version.
*/
// We are planning on removing this so we aren't interested in testing this
// right now.
/* istanbul ignore next */
async lookupNetworkByClientId(networkClientId) {
await __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_lookupGivenNetwork).call(this, networkClientId);
}
/**
* Convenience method to update provider network type settings.
*
* @param type - Human readable network name.
* @deprecated This has been replaced by `setActiveNetwork`, and will be
* removed in a future release
*/
async setProviderType(type) {
if (type === controller_utils_1.NetworkType.rpc) {
throw new Error(`NetworkController - cannot call "setProviderType" with type "${controller_utils_1.NetworkType.rpc}". Use "setActiveNetwork"`);
}
if (!(0, controller_utils_1.isInfuraNetworkType)(type)) {
throw new Error(`Unknown Infura provider type "${String(type)}".`);
}
await this.setActiveNetwork(type);
}
/**
* Changes the selected network.
*
* @param networkClientId - The ID of a network client that will be used to
* make requests.
* @param options - Options for this method.
* @param options.updateState - Allows for updating state.
* @throws if no network client is associated with the given
* network client ID.
*/
async setActiveNetwork(networkClientId, options = {}) {
__classPrivateFieldSet(this, _NetworkController_previouslySelectedNetworkClientId, this.state.selectedNetworkClientId, "f");
await __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_refreshNetwork).call(this, networkClientId, options);
}
/**
* Determines whether the network supports EIP-1559 by checking whether the
* latest block has a `baseFeePerGas` property, then updates state
* appropriately.
*
* @param networkClientId - The networkClientId to fetch the correct provider against which to check 1559 compatibility.
* @returns A promise that resolves to true if the network supports EIP-1559
* , false otherwise, or `undefined` if unable to determine the compatibility.
*/
async getEIP1559Compatibility(networkClientId) {
if (networkClientId) {
return this.get1559CompatibilityWithNetworkClientId(networkClientId);
}
if (!__classPrivateFieldGet(this, _NetworkController_ethQuery, "f")) {
return false;
}
const { EIPS } = this.state.networksMetadata[this.state.selectedNetworkClientId];
if (EIPS[1559] !== undefined) {
return EIPS[1559];
}
const isEIP1559Compatible = await __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_determineEIP1559Compatibility).call(this, this.state.selectedNetworkClientId);
this.update((state) => {
if (isEIP1559Compatible !== undefined) {
state.networksMetadata[state.selectedNetworkClientId].EIPS[1559] =
isEIP1559Compatible;
}
});
return isEIP1559Compatible;
}
async get1559CompatibilityWithNetworkClientId(networkClientId) {
let metadata = this.state.networksMetadata[networkClientId];
if (metadata?.EIPS[1559] === undefined) {
await this.lookupNetwork(networkClientId);
metadata = this.state.networksMetadata[networkClientId];
}
const { EIPS } = metadata;
// may want to include some 'freshness' value - something to make sure we refetch this from time to time
return EIPS[1559];
}
/**
* Ensures that the provider and block tracker proxies are pointed to the
* currently selected network and refreshes the metadata for the
*/
async resetConnection() {
await __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_refreshNetwork).call(this, this.state.selectedNetworkClientId);
}
/**
* Returns the network configuration that has been filed under the given chain
* ID.
*
* @param chainId - The chain ID to use as a key.
* @returns The network configuration if one exists, or undefined.
*/
getNetworkConfigurationByChainId(chainId) {
return this.state.networkConfigurationsByChainId[chainId];
}
/**
* Returns the network configuration that contains an RPC endpoint with the
* given network client ID.
*
* @param networkClientId - The network client ID to use as a key.
* @returns The network configuration if one exists, or undefined.
*/
getNetworkConfigurationByNetworkClientId(networkClientId) {
return __classPrivateFieldGet(this, _NetworkController_networkConfigurationsByNetworkClientId, "f").get(networkClientId);
}
/**
* Creates and registers network clients for the collection of Infura and
* custom RPC endpoints that can be used to make requests for a particular
* chain, storing the given configuration object in state for later reference.
*
* @param fields - The object that describes the new network/chain and lists
* the RPC endpoints which front that chain.
* @returns The newly added network configuration.
* @throws if any part of `fields` would produce invalid state.
* @see {@link NetworkConfiguration}
*/
addNetwork(fields) {
const { rpcEndpoints: setOfRpcEndpointFields } = fields;
const autoManagedNetworkClientRegistry = __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_ensureAutoManagedNetworkClientRegistryPopulated).call(this);
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_validateNetworkFields).call(this, {
mode: 'add',
networkFields: fields,
autoManagedNetworkClientRegistry,
});
const networkClientOperations = setOfRpcEndpointFields.map((defaultOrCustomRpcEndpointFields) => {
const rpcEndpoint = defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom
? {
...defaultOrCustomRpcEndpointFields,
networkClientId: (0, uuid_1.v4)(),
}
: defaultOrCustomRpcEndpointFields;
return {
type: 'add',
rpcEndpoint,
};
});
const newNetworkConfiguration = __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_determineNetworkConfigurationToPersist).call(this, {
networkFields: fields,
networkClientOperations,
});
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_registerNetworkClientsAsNeeded).call(this, {
networkFields: fields,
networkClientOperations,
autoManagedNetworkClientRegistry,
});
this.update((state) => {
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_updateNetworkConfigurations).call(this, {
state,
mode: 'add',
networkFields: fields,
networkConfigurationToPersist: newNetworkConfiguration,
});
});
this.messenger.publish(`${controllerName}:networkAdded`, newNetworkConfiguration);
return newNetworkConfiguration;
}
/**
* Updates the configuration for a previously stored network filed under the
* given chain ID, creating + registering new network clients to represent RPC
* endpoints that have been added and destroying + unregistering existing
* network clients for RPC endpoints that have been removed.
*
* Note that if `chainId` is changed, then all network clients associated with
* that chain will be removed and re-added, even if none of the RPC endpoints
* have changed.
*
* @param chainId - The chain ID associated with an existing network.
* @param fields - The object that describes the updates to the network/chain,
* including the new set of RPC endpoints which should front that chain.
* @param options - Options to provide.
* @param options.replacementSelectedRpcEndpointIndex - Usually you cannot
* remove an RPC endpoint that is being represented by the currently selected
* network client. This option allows you to specify another RPC endpoint
* (either an existing one or a new one) that should be used to select a new
* network instead.
* @returns The updated network configuration.
* @throws if `chainId` does not refer to an existing network configuration,
* if any part of `fields` would produce invalid state, etc.
* @see {@link NetworkConfiguration}
*/
async updateNetwork(chainId, fields, { replacementSelectedRpcEndpointIndex, } = {}) {
const existingNetworkConfiguration = this.state.networkConfigurationsByChainId[chainId];
if (existingNetworkConfiguration === undefined) {
throw new Error(`Could not update network: Cannot find network configuration for chain '${chainId}'`);
}
const existingChainId = chainId;
const { chainId: newChainId, rpcEndpoints: setOfNewRpcEndpointFields } = fields;
const autoManagedNetworkClientRegistry = __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_ensureAutoManagedNetworkClientRegistryPopulated).call(this);
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_validateNetworkFields).call(this, {
mode: 'update',
networkFields: fields,
existingNetworkConfiguration,
autoManagedNetworkClientRegistry,
});
const networkClientOperations = [];
for (const newRpcEndpointFields of setOfNewRpcEndpointFields) {
const existingRpcEndpointForNoop = existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => {
return (rpcEndpoint.type === newRpcEndpointFields.type &&
rpcEndpoint.url === newRpcEndpointFields.url &&
(rpcEndpoint.networkClientId ===
newRpcEndpointFields.networkClientId ||
newRpcEndpointFields.networkClientId === undefined));
});
const existingRpcEndpointForReplaceWhenChainChanged = existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => {
return ((rpcEndpoint.type === RpcEndpointType.Infura &&
newRpcEndpointFields.type === RpcEndpointType.Infura) ||
(rpcEndpoint.type === newRpcEndpointFields.type &&
rpcEndpoint.networkClientId ===
newRpcEndpointFields.networkClientId &&
rpcEndpoint.url === newRpcEndpointFields.url));
});
const existingRpcEndpointForReplaceWhenChainNotChanged = existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => {
return (rpcEndpoint.type === newRpcEndpointFields.type &&
(rpcEndpoint.url === newRpcEndpointFields.url ||
rpcEndpoint.networkClientId ===
newRpcEndpointFields.networkClientId));
});
if (newChainId !== existingChainId &&
existingRpcEndpointForReplaceWhenChainChanged !== undefined) {
const newRpcEndpoint = newRpcEndpointFields.type === RpcEndpointType.Infura
? newRpcEndpointFields
: { ...newRpcEndpointFields, networkClientId: (0, uuid_1.v4)() };
networkClientOperations.push({
type: 'replace',
oldRpcEndpoint: existingRpcEndpointForReplaceWhenChainChanged,
newRpcEndpoint,
});
}
else if (existingRpcEndpointForNoop !== undefined) {
let newRpcEndpoint;
if (existingRpcEndpointForNoop.type === RpcEndpointType.Infura) {
newRpcEndpoint = existingRpcEndpointForNoop;
}
else {
// `networkClientId` shouldn't be missing at this point; if it is,
// that's a mistake, so fill it back in
newRpcEndpoint = Object.assign({}, newRpcEndpointFields, {
networkClientId: existingRpcEndpointForNoop.networkClientId,
});
}
networkClientOperations.push({
type: 'noop',
rpcEndpoint: newRpcEndpoint,
});
}
else if (existingRpcEndpointForReplaceWhenChainNotChanged === undefined) {
const newRpcEndpoint = newRpcEndpointFields.type === RpcEndpointType.Infura
? newRpcEndpointFields
: { ...newRpcEndpointFields, networkClientId: (0, uuid_1.v4)() };
const networkClientOperation = {
type: 'add',
rpcEndpoint: newRpcEndpoint,
};
networkClientOperations.push(networkClientOperation);
}
else {
let newRpcEndpoint;
/* istanbul ignore if */
if (newRpcEndpointFields.type === RpcEndpointType.Infura) {
// This case can't actually happen. If we're here, it means that some
// part of the RPC endpoint changed. But there is no part of an Infura
// RPC endpoint that can be changed (as it would immediately make that
// RPC endpoint self-inconsistent). This is just here to appease
// TypeScript.
newRpcEndpoint = newRpcEndpointFields;
}
else {
newRpcEndpoint = {
...newRpcEndpointFields,
networkClientId: (0, uuid_1.v4)(),
};
}
networkClientOperations.push({
type: 'replace',
oldRpcEndpoint: existingRpcEndpointForReplaceWhenChainNotChanged,
newRpcEndpoint,
});
}
}
for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) {
if (!networkClientOperations.some((networkClientOperation) => {
const otherRpcEndpoint = networkClientOperation.type === 'replace'
? networkClientOperation.oldRpcEndpoint
: networkClientOperation.rpcEndpoint;
return (otherRpcEndpoint.type === existingRpcEndpoint.type &&
otherRpcEndpoint.networkClientId ===
existingRpcEndpoint.networkClientId &&
otherRpcEndpoint.url === existingRpcEndpoint.url);
})) {
const networkClientOperation = {
type: 'remove',
rpcEndpoint: existingRpcEndpoint,
};
networkClientOperations.push(networkClientOperation);
}
}
const updatedNetworkConfiguration = __classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_determineNetworkConfigurationToPersist).call(this, {
networkFields: fields,
networkClientOperations,
});
if (replacementSelectedRpcEndpointIndex === undefined &&
networkClientOperations.some((networkClientOperation) => {
return (networkClientOperation.type === 'remove' &&
networkClientOperation.rpcEndpoint.networkClientId ===
this.state.selectedNetworkClientId);
}) &&
!networkClientOperations.some((networkClientOperation) => {
return (networkClientOperation.type === 'replace' &&
networkClientOperation.oldRpcEndpoint.networkClientId ===
this.state.selectedNetworkClientId);
})) {
throw new Error(`Could not update network: Cannot update RPC endpoints in such a way that the selected network '${this.state.selectedNetworkClientId}' would be removed without a replacement. Choose a different RPC endpoint as the selected network via the \`replacementSelectedRpcEndpointIndex\` option.`);
}
__classPrivateFieldGet(this, _NetworkController_instances, "m", _NetworkController_registerNetworkClientsAsNeeded).call(this, {
networkFields: fields,
networkClientOperations,
autoManagedNetworkClientRegistry,
});
const replacementSelectedRpcEndpointWithIndex = networkClientOperations
.map((networkClientOperation, index) => [networkClientOperation, index])
.find(([networkClientOperation, _index]) => {
return (networkClientOperation.type === 'replace' &&
networkClientOperation.oldRpcEndpoint.networkClientId ===
this.state.selectedNetworkClientId);
});
const correctedReplacementSelectedRpcEndpointIndex = replacementSelectedRpcEndpointIndex ??
replacementSelectedRpcEndpointWithIndex?.[1];
let rpcEndpointToSelect;
if (correctedReplacementSelectedRpcEndpointIndex !== undefined) {
rpcEndpointToSelect =
updatedNetworkConfiguration.rpcEndpoints[correctedReplacementSelectedRpcEndpointIndex];
if (rpcEndpointToSelect === undefined) {
throw new Error(`Could no