UNPKG

@metamask/network-controller

Version:

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

179 lines 7.16 kB
import { ChainId } from "@metamask/controller-utils"; import { PollingBlockTracker } from "@metamask/eth-block-tracker"; import { createInfuraMiddleware } from "@metamask/eth-json-rpc-infura"; import { createBlockCacheMiddleware, createBlockRefMiddleware, createBlockRefRewriteMiddleware, createBlockTrackerInspectorMiddleware, createInflightCacheMiddleware, createFetchMiddleware, createRetryOnEmptyMiddleware } from "@metamask/eth-json-rpc-middleware"; import { providerFromEngine, providerFromMiddleware } from "@metamask/eth-json-rpc-provider"; import { createAsyncMiddleware, createScaffoldMiddleware, JsonRpcEngine, mergeMiddleware } from "@metamask/json-rpc-engine"; import { RpcServiceChain } from "./rpc-service/rpc-service-chain.mjs"; import { NetworkClientType } from "./types.mjs"; const SECOND = 1000; /** * Create a JSON RPC network client for a specific network. * * @param args - The arguments. * @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. * See {@link NetworkControllerOptions.getRpcServiceOptions}. * @returns The network client. */ export function createNetworkClient({ configuration, getRpcServiceOptions, messenger, }) { const primaryEndpointUrl = configuration.type === NetworkClientType.Infura ? `https://${configuration.network}.infura.io/v3/${configuration.infuraProjectId}` : configuration.rpcUrl; const availableEndpointUrls = [ primaryEndpointUrl, ...(configuration.failoverRpcUrls ?? []), ]; const rpcService = new RpcServiceChain(availableEndpointUrls.map((endpointUrl) => ({ ...getRpcServiceOptions(endpointUrl), endpointUrl, }))); rpcService.onBreak(({ endpointUrl, failoverEndpointUrl, ...rest }) => { let error; if ('error' in rest) { error = rest.error; } else if ('value' in rest) { error = rest.value; } messenger.publish('NetworkController:rpcEndpointUnavailable', { chainId: configuration.chainId, endpointUrl, failoverEndpointUrl, error, }); }); rpcService.onDegraded(({ endpointUrl }) => { messenger.publish('NetworkController:rpcEndpointDegraded', { chainId: configuration.chainId, endpointUrl, }); }); rpcService.onRetry(({ endpointUrl, attempt }) => { messenger.publish('NetworkController:rpcEndpointRequestRetried', { endpointUrl, attempt, }); }); const rpcApiMiddleware = configuration.type === NetworkClientType.Infura ? createInfuraMiddleware({ rpcService, options: { source: 'metamask', }, }) : createFetchMiddleware({ rpcService }); const rpcProvider = providerFromMiddleware(rpcApiMiddleware); const blockTrackerOpts = process.env.IN_TEST && configuration.type === NetworkClientType.Custom ? { pollingInterval: SECOND } : {}; const blockTracker = new PollingBlockTracker({ ...blockTrackerOpts, provider: rpcProvider, }); const networkMiddleware = configuration.type === NetworkClientType.Infura ? createInfuraNetworkMiddleware({ blockTracker, network: configuration.network, rpcProvider, rpcApiMiddleware, }) : createCustomNetworkMiddleware({ blockTracker, chainId: configuration.chainId, rpcApiMiddleware, }); const engine = new JsonRpcEngine(); engine.push(networkMiddleware); const provider = providerFromEngine(engine); 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 }; } /** * 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 mergeMiddleware([ createNetworkAndChainIdMiddleware({ network }), createBlockCacheMiddleware({ blockTracker }), createInflightCacheMiddleware(), createBlockRefMiddleware({ blockTracker, provider: rpcProvider }), createRetryOnEmptyMiddleware({ blockTracker, provider: rpcProvider }), createBlockTrackerInspectorMiddleware({ blockTracker }), rpcApiMiddleware, ]); } /** * 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 createScaffoldMiddleware({ // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/naming-convention eth_chainId: ChainId[network], }); } const createChainIdMiddleware = (chainId) => { return (req, res, next, end) => { if (req.method === 'eth_chainId') { res.result = chainId; return end(); } 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, }) { const testMiddlewares = process.env.IN_TEST ? [createEstimateGasDelayTestMiddleware()] : []; return mergeMiddleware([ ...testMiddlewares, createChainIdMiddleware(chainId), createBlockRefRewriteMiddleware({ blockTracker }), createBlockCacheMiddleware({ blockTracker }), createInflightCacheMiddleware(), createBlockTrackerInspectorMiddleware({ blockTracker }), rpcApiMiddleware, ]); } /** * 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 createAsyncMiddleware(async (req, _, next) => { if (req.method === 'eth_estimateGas') { await new Promise((resolve) => setTimeout(resolve, SECOND * 2)); } return next(); }); } //# sourceMappingURL=create-network-client.mjs.map