UNPKG

@metamask/network-controller

Version:

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

323 lines 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNetworkClient = void 0; const controller_utils_1 = require("@metamask/controller-utils"); const eth_block_tracker_1 = require("@metamask/eth-block-tracker"); const eth_json_rpc_infura_1 = require("@metamask/eth-json-rpc-infura"); const eth_json_rpc_middleware_1 = require("@metamask/eth-json-rpc-middleware"); const eth_json_rpc_provider_1 = require("@metamask/eth-json-rpc-provider"); const eth_json_rpc_provider_2 = require("@metamask/eth-json-rpc-provider"); const json_rpc_engine_1 = require("@metamask/json-rpc-engine"); const v2_1 = require("@metamask/json-rpc-engine/v2"); const rpc_service_chain_1 = require("./rpc-service/rpc-service-chain.cjs"); const types_1 = require("./types.cjs"); const SECOND = 1000; /** * Create a JSON RPC network client for a specific network. * * @param args - The arguments. * @param args.id - The ID that will be assigned to the new network client in * the registry. * @param args.configuration - The network configuration. * @param args.getRpcServiceOptions - Factory for constructing RPC service * options. See {@link NetworkControllerOptions.getRpcServiceOptions}. * @param args.getBlockTrackerOptions - Factory for constructing block tracker * options. See {@link NetworkControllerOptions.getBlockTrackerOptions}. * @param args.messenger - The network controller messenger. * @param args.isRpcFailoverEnabled - Whether or not requests sent to the * primary RPC endpoint for this network should be automatically diverted to * provided failover endpoints if the primary is unavailable. This effectively * causes the `failoverRpcUrls` property of the network client configuration * to be honored or ignored. * @param args.logger - A `loglevel` logger. * @returns The network client. */ function createNetworkClient({ id, configuration, getRpcServiceOptions, getBlockTrackerOptions, messenger, isRpcFailoverEnabled, logger, }) { const primaryEndpointUrl = configuration.type === types_1.NetworkClientType.Infura ? `https://${configuration.network}.infura.io/v3/${configuration.infuraProjectId}` : configuration.rpcUrl; const rpcServiceChain = createRpcServiceChain({ id, primaryEndpointUrl, configuration, getRpcServiceOptions, messenger, isRpcFailoverEnabled, logger, }); let rpcApiMiddleware; if (configuration.type === types_1.NetworkClientType.Infura) { rpcApiMiddleware = (0, json_rpc_engine_1.asV2Middleware)((0, eth_json_rpc_infura_1.createInfuraMiddleware)({ rpcService: rpcServiceChain, options: { source: 'metamask', }, })); } else { rpcApiMiddleware = (0, eth_json_rpc_middleware_1.createFetchMiddleware)({ rpcService: rpcServiceChain }); } const rpcProvider = (0, eth_json_rpc_provider_2.providerFromMiddlewareV2)(rpcApiMiddleware); const blockTracker = createBlockTracker({ networkClientType: configuration.type, endpointUrl: primaryEndpointUrl, getOptions: getBlockTrackerOptions, provider: rpcProvider, }); const networkMiddleware = configuration.type === types_1.NetworkClientType.Infura ? createInfuraNetworkMiddleware({ blockTracker, network: configuration.network, rpcProvider, rpcApiMiddleware, }) : createCustomNetworkMiddleware({ blockTracker, chainId: configuration.chainId, rpcApiMiddleware, }); const provider = new eth_json_rpc_provider_1.InternalProvider({ engine: v2_1.JsonRpcEngineV2.create({ middleware: [networkMiddleware], }), }); const destroy = () => { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/no-floating-promises blockTracker.destroy(); }; return { configuration, provider, blockTracker, destroy }; } exports.createNetworkClient = createNetworkClient; /** * Creates an RPC service chain, which represents the primary endpoint URL for * the network as well as its failover URLs. * * @param args - The arguments. * @param args.id - The ID that will be assigned to the new network client in * the registry. * @param args.primaryEndpointUrl - The primary endpoint URL. * @param args.configuration - The network configuration. * @param args.getRpcServiceOptions - Factory for constructing RPC service * options. See {@link NetworkControllerOptions.getRpcServiceOptions}. * @param args.messenger - The network controller messenger. * @param args.isRpcFailoverEnabled - Whether or not requests sent to the * primary RPC endpoint for this network should be automatically diverted to * provided failover endpoints if the primary is unavailable. This effectively * causes the `failoverRpcUrls` property of the network client configuration * to be honored or ignored. * @param args.logger - A `loglevel` logger. * @returns The RPC service chain. */ function createRpcServiceChain({ id, primaryEndpointUrl, configuration, getRpcServiceOptions, messenger, isRpcFailoverEnabled, logger, }) { const availableEndpointUrls = isRpcFailoverEnabled ? [primaryEndpointUrl, ...(configuration.failoverRpcUrls ?? [])] : [primaryEndpointUrl]; const rpcServiceConfigurations = availableEndpointUrls.map((endpointUrl) => ({ ...getRpcServiceOptions(endpointUrl), endpointUrl, logger, })); /** * Extracts the error from Cockatiel's `FailureReason` type received in * circuit breaker event handlers. * * The `FailureReason` object can have two possible shapes: * - `{ error: Error }` - When the RPC service throws an error (the common * case for RPC failures). * - `{ value: T }` - When the RPC service returns a value that the retry * filter policy considers a failure. * * @param value - The event data object from the circuit breaker event * listener (after destructuring known properties like `endpointUrl`). This * represents Cockatiel's `FailureReason` type. * @returns The error or failure value, or `undefined` if neither property * exists (which shouldn't happen in practice unless the circuit breaker is * manually isolated). */ const getError = (value) => { if ('error' in value) { return value.error; } else if ('value' in value) { return value.value; } return undefined; }; const rpcServiceChain = new rpc_service_chain_1.RpcServiceChain([ rpcServiceConfigurations[0], ...rpcServiceConfigurations.slice(1), ]); rpcServiceChain.onBreak((data) => { const error = getError(data); if (error === undefined) { // This error shouldn't happen in practice because we never call `.isolate` // on the circuit breaker policy, but we need to appease TypeScript. throw new Error('Could not make request to endpoint.'); } messenger.publish('NetworkController:rpcEndpointChainUnavailable', { chainId: configuration.chainId, networkClientId: id, error, }); }); rpcServiceChain.onServiceBreak(({ endpointUrl, primaryEndpointUrl: primaryEndpointUrlFromEvent, ...rest }) => { const error = getError(rest); if (error === undefined) { // This error shouldn't happen in practice because we never call `.isolate` // on the circuit breaker policy, but we need to appease TypeScript. throw new Error('Could not make request to endpoint.'); } messenger.publish('NetworkController:rpcEndpointUnavailable', { chainId: configuration.chainId, networkClientId: id, primaryEndpointUrl: primaryEndpointUrlFromEvent, endpointUrl, error, }); }); rpcServiceChain.onDegraded((data) => { const error = getError(data); messenger.publish('NetworkController:rpcEndpointChainDegraded', { chainId: configuration.chainId, networkClientId: id, error, }); }); rpcServiceChain.onServiceDegraded(({ endpointUrl, primaryEndpointUrl: primaryEndpointUrlFromEvent, ...rest }) => { const error = getError(rest); messenger.publish('NetworkController:rpcEndpointDegraded', { chainId: configuration.chainId, networkClientId: id, primaryEndpointUrl: primaryEndpointUrlFromEvent, endpointUrl, error, }); }); rpcServiceChain.onAvailable(() => { messenger.publish('NetworkController:rpcEndpointChainAvailable', { chainId: configuration.chainId, networkClientId: id, }); }); rpcServiceChain.onServiceRetry(({ attempt, endpointUrl, primaryEndpointUrl: primaryEndpointUrlFromEvent, }) => { messenger.publish('NetworkController:rpcEndpointRetried', { chainId: configuration.chainId, networkClientId: id, primaryEndpointUrl: primaryEndpointUrlFromEvent, endpointUrl, attempt, }); }); return rpcServiceChain; } /** * Create the block tracker for the network. * * @param args - The arguments. * @param args.networkClientType - The type of the network client ("infura" or * "custom"). * @param args.endpointUrl - The URL of the endpoint. * @param args.getOptions - Factory for the block tracker options. * @param args.provider - The EIP-1193 provider for the network's JSON-RPC * middleware stack. * @returns The created block tracker. */ function createBlockTracker({ networkClientType, endpointUrl, getOptions, provider, }) { const testOptions = // Needed for testing. // eslint-disable-next-line no-restricted-globals process.env.IN_TEST && networkClientType === types_1.NetworkClientType.Custom ? { pollingInterval: SECOND } : {}; return new eth_block_tracker_1.PollingBlockTracker({ ...testOptions, ...getOptions(endpointUrl), provider, }); } /** * Create middleware for infura. * * @param args - The arguments. * @param args.blockTracker - The block tracker to use. * @param args.network - The Infura network to use. * @param args.rpcProvider - The RPC provider to use. * @param args.rpcApiMiddleware - Additional middleware. * @returns The collection of middleware that makes up the Infura client. */ function createInfuraNetworkMiddleware({ blockTracker, network, rpcProvider, rpcApiMiddleware, }) { return v2_1.JsonRpcEngineV2.create({ middleware: [ createNetworkAndChainIdMiddleware({ network }), (0, eth_json_rpc_middleware_1.createBlockCacheMiddleware)({ blockTracker }), (0, eth_json_rpc_middleware_1.createInflightCacheMiddleware)(), (0, eth_json_rpc_middleware_1.createBlockRefMiddleware)({ blockTracker, provider: rpcProvider }), (0, eth_json_rpc_middleware_1.createRetryOnEmptyMiddleware)({ blockTracker, provider: rpcProvider }), (0, eth_json_rpc_middleware_1.createBlockTrackerInspectorMiddleware)({ blockTracker }), rpcApiMiddleware, ], }).asMiddleware(); } /** * Creates static method middleware. * * @param args - The Arguments. * @param args.network - The Infura network to use. * @returns The middleware that implements the eth_chainId method. */ function createNetworkAndChainIdMiddleware({ network, }) { return (0, v2_1.createScaffoldMiddleware)({ eth_chainId: controller_utils_1.ChainId[network], }); } const createChainIdMiddleware = (chainId) => { return ({ request, next }) => { if (request.method === 'eth_chainId') { return chainId; } return next(); }; }; /** * Creates custom middleware. * * @param args - The arguments. * @param args.blockTracker - The block tracker to use. * @param args.chainId - The chain id to use. * @param args.rpcApiMiddleware - Additional middleware. * @returns The collection of middleware that makes up the Infura client. */ function createCustomNetworkMiddleware({ blockTracker, chainId, rpcApiMiddleware, }) { // Needed for testing. // eslint-disable-next-line no-restricted-globals const testMiddlewares = process.env.IN_TEST ? [createEstimateGasDelayTestMiddleware()] : []; return v2_1.JsonRpcEngineV2.create({ middleware: [ ...testMiddlewares, createChainIdMiddleware(chainId), (0, eth_json_rpc_middleware_1.createBlockRefRewriteMiddleware)({ blockTracker }), (0, eth_json_rpc_middleware_1.createBlockCacheMiddleware)({ blockTracker }), (0, eth_json_rpc_middleware_1.createInflightCacheMiddleware)(), (0, eth_json_rpc_middleware_1.createBlockTrackerInspectorMiddleware)({ blockTracker }), rpcApiMiddleware, ], }).asMiddleware(); } /** * For use in tests only. * Adds a delay to `eth_estimateGas` calls. * * @returns The middleware for delaying gas estimation calls by 2 seconds when in test. */ function createEstimateGasDelayTestMiddleware() { return async ({ request, next }) => { if (request.method === 'eth_estimateGas') { await new Promise((resolve) => setTimeout(resolve, SECOND * 2)); } return next(); }; } //# sourceMappingURL=create-network-client.cjs.map