UNPKG

@metamask/network-controller

Version:

Provides an interface to the currently selected network via a MetaMask-compatible provider object

917 lines 89.1 kB
"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