@metamask/network-controller
Version:
Provides an interface to the currently selected network via a MetaMask-compatible provider object
213 lines • 8.66 kB
JavaScript
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.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.
* @returns The network client.
*/
export function createNetworkClient({ configuration, getRpcServiceOptions, getBlockTrackerOptions, messenger, isRpcFailoverEnabled, }) {
const primaryEndpointUrl = configuration.type === NetworkClientType.Infura
? `https://${configuration.network}.infura.io/v3/${configuration.infuraProjectId}`
: configuration.rpcUrl;
const availableEndpointUrls = isRpcFailoverEnabled
? [primaryEndpointUrl, ...(configuration.failoverRpcUrls ?? [])]
: [primaryEndpointUrl];
const rpcServiceChain = new RpcServiceChain(availableEndpointUrls.map((endpointUrl) => ({
...getRpcServiceOptions(endpointUrl),
endpointUrl,
})));
rpcServiceChain.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,
});
});
rpcServiceChain.onDegraded(({ endpointUrl, ...rest }) => {
let error;
if ('error' in rest) {
error = rest.error;
}
else if ('value' in rest) {
error = rest.value;
}
messenger.publish('NetworkController:rpcEndpointDegraded', {
chainId: configuration.chainId,
endpointUrl,
error,
});
});
rpcServiceChain.onRetry(({ endpointUrl, attempt }) => {
messenger.publish('NetworkController:rpcEndpointRequestRetried', {
endpointUrl,
attempt,
});
});
const rpcApiMiddleware = configuration.type === NetworkClientType.Infura
? createInfuraMiddleware({
rpcService: rpcServiceChain,
options: {
source: 'metamask',
},
})
: createFetchMiddleware({ rpcService: rpcServiceChain });
const rpcProvider = providerFromMiddleware(rpcApiMiddleware);
const blockTracker = createBlockTracker({
networkClientType: configuration.type,
endpointUrl: primaryEndpointUrl,
getOptions: getBlockTrackerOptions,
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 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 = process.env.IN_TEST && networkClientType === NetworkClientType.Custom
? { pollingInterval: SECOND }
: {};
return new 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 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