UNPKG

@lifi/rpc-wrapper

Version:
413 lines (412 loc) 18.7 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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RPCProvider = void 0; const ethers_1 = require("ethers"); const providers = __importStar(require("@ethersproject/providers")); const abi_1 = require("./abi"); const errors_1 = require("./errors"); const utils_1 = require("./utils"); const RPCProviderCache_1 = require("./RPCProviderCache"); class RPCProvider extends providers.FallbackProvider { /** * * @param rpcConfigs - prioritized provider configs * @param logger - optional logger * @param cacheTTL - provider cache time to live (ttl) in seconds * @param pollingInterval - interval in seconds between each call to getBlockNumber */ constructor(rpcConfigs, logger, cacheTTL, pollingInterval, rpcTimeoutForExecute = 10000) { // need to initialize a static json rpc provider to satisfy the fallbackprovider // this ensures that methods implemented by Fallbackprovider but not RPCprovider // are exposed to the user const provider = new providers.StaticJsonRpcProvider(rpcConfigs[0].url); super([{ provider }]); this.rpcConfigs = rpcConfigs; this.logger = logger; this.retryNumber = 2; this.ctx = { method: '', args: {} }; this.setMethodCtx = (method, args = {}) => { this.ctx = { method, args }; }; this.rpcTimeoutForExecute = rpcTimeoutForExecute; if (pollingInterval) this._pollingInterval = pollingInterval; this._highestBlockNumber = 0; provider._pollingInterval = this.pollingInterval; // initialize providerCache this.providerCache = new RPCProviderCache_1.RPCProviderCache(cacheTTL, pollingInterval); this._nextId = 42; } get _cache() { if (this._eventLoopCache == null) { this._eventLoopCache = {}; } return this._eventLoopCache; } /** * Gets the current transaction count. * * @param addressOrName - The hexadecimal string address or name * @param blockTag (default: "latest") - The block tag to get the transaction count for. Use "latest" mined-only transactions. * Use "pending" for transactions that have not been mined yet, but will (supposedly) be mined in the pending * block (essentially, transactions included in the mempool, but this behavior is not consistent). * * @returns Number of transactions sent AKA the current nonce. */ getTransactionCount(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getTransactionCount', { addressOrName: yield addressOrName, blockTag: JSON.stringify(yield blockTag), }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { const transactionCount = yield provider.getTransactionCount(addressOrName, blockTag); // TODO cache transaction count return transactionCount; }))); }); } /** * The RPC method execute wrapper is used for wrapping and parsing errors, as well as ensuring that * providers are ready before any call is made. Also used for executing multiple retries for RPC * requests to providers. This is to circumvent any issues related to unreliable internet/network * issues, whether locally, or externally (for the provider's network). * * @param method - The method callback to execute and wrap in retries. * @returns The object of the specified generic type. * @throws CustomError if the method fails to execute. */ execute(method) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { const errors = []; for (let i = 0; i < this.retryNumber; i++) { const rpcConfig = this.rpcConfigs[i % this.rpcConfigs.length]; const provider = this.providerCache.getProvider(rpcConfig.url); try { (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`[RpcProvider] ${this.ctx.method}.debug. Trying provider: ${rpcConfig.url} on chain ${rpcConfig.chainId}...`); const tick = process.hrtime(); const res = yield Promise.race([ method(provider), new Promise((_, reject) => setTimeout(() => { reject(new errors_1.StallTimeout()); }, this.rpcTimeoutForExecute)), ]); const tock = (0, utils_1.perfProfileHrtime)(tick); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`[RpcProvider] ${this.ctx.method}.debug. Successfully response from: ${rpcConfig.url} on chain ${rpcConfig.chainId}! Took ${tock}`); return res; } catch (e) { // It will return instantly if the error is already a CustomError. const error = (0, errors_1.parseError)(e, Object.assign({ RPCUrl: rpcConfig.url }, this.ctx)); (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug(`[RpcProvider] ${this.ctx.method}.debug. Failed to get data from ${rpcConfig.url} on chain ${rpcConfig.chainId}. Cause: ${error} `); // Extra logging for timeouts if (error.type === errors_1.StallTimeout.type) { (_d = this.logger) === null || _d === void 0 ? void 0 : _d.warn(`[RpcProvider] ${this.ctx.method}.warn. Timeout for ${rpcConfig.url} on chain ${rpcConfig.chainId}!`, this.ctx.args); } if (error.type === errors_1.ServerError.type || error.type === errors_1.RpcError.type) { // If the method threw a RpcError, or ServerError, that indicates a problem with the provider and not // the call - so we'll retry the call with a different provider (if available). errors.push(error); } else { // e.g. a TransactionReverted, TransactionReplaced, etc. throw error; } } } (_e = this.logger) === null || _e === void 0 ? void 0 : _e.error(`[RpcProvider] ${this.ctx.method}.error. Cause: ${errors_1.RpcError.reasons.FailedToSend}. Errors: ${errors}`); throw new errors_1.RpcError(errors_1.RpcError.reasons.FailedToSend, { errors }); }); } stopwatch(fn) { var _a; return __awaiter(this, void 0, void 0, function* () { const tick = process.hrtime(); const res = yield fn(); const tock = (0, utils_1.perfProfileHrtime)(tick); const logType = `${this.ctx.method}.debug. Time: `; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`$[RpcProvider] ${logType.padEnd(25)} ${tock}`); return res; }); } /** * Get the current balance for the specified address. * * @param addressOrName - The hexadecimal string address whose balance we are getting. * @param blockTag - string | number of the block. * @param assetId - The ID (address) of the asset whose balance we are getting. * @param abi (default = ERC20) - The ABI of the token contract to use, if non-native token. * * @returns A BigNumber representing the current value held by the wallet at the * specified address. */ getBalance(addressOrName, blockTag, assetId = ethers_1.constants.AddressZero, abi) { var _a; return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getBalance', { addressOrName: yield addressOrName, blockTag: JSON.stringify(yield blockTag), assetId, abi: (_a = abi === null || abi === void 0 ? void 0 : abi.join(',')) !== null && _a !== void 0 ? _a : '', }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { if (assetId === ethers_1.constants.AddressZero) { return yield provider.getBalance(addressOrName, blockTag); } else { const contract = new ethers_1.Contract(assetId, abi !== null && abi !== void 0 ? abi : abi_1.ERC20Abi, provider); return yield contract.balanceOf(addressOrName); } }))); }); } /** * Returns the Array of Log matching the filter. * * @param filter - Filter | FilterByBlockHash object * * @returns Array of logs */ getLogs(filter) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getLogs', { filter: JSON.stringify(filter), }); this.setMethodCtx('getLogs'); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getLogs(filter); }))); }); } /** * * Returns the block number (or height) of the most recently mined block. * * @returns number */ getBlockNumber() { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getBlockNumber'); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getBlockNumber(); }))); }); } /** * * Get the block from the network, where the result.transactions is a list of transaction hashes. * * @param blockHashOrBlockTag - block hash or block tag * * @returns Block */ getBlock(blockHashOrBlockTag) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getBlock', { blockHashOrBlockTag: JSON.stringify(yield blockHashOrBlockTag), }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getBlock(blockHashOrBlockTag); }))); }); } /** * * Get gas price. * * @returns BigNumber */ getGasPrice() { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getGasPrice'); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getGasPrice(); }))); }); } /** * * Returns an estimate of the amount of gas that would be required * to submit transaction to the network. * * @param transaction - ethers TransactionRequest type * * @returns BigNumber */ estimateGas(transaction) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('estimateGas', { transaction: JSON.stringify(transaction), }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.estimateGas(transaction); }))); }); } /** * * Get the block from the network, where the result.transactions is an Array * of TransactionResponse objects. * * @param blockHashOrBlockTag - block hash or block tag * * @returns BlockWithTransactions */ getBlockWithTransactions(blockHashOrBlockTag) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getBlockWithTransactions', { blockHashOrBlockTag: JSON.stringify(yield blockHashOrBlockTag), }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getBlockWithTransactions(blockHashOrBlockTag); }))); }); } waitForTransaction(transactionHash, confirmations, timeout) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('waitForTransaction', { transactionHash, confirmations: (_a = confirmations === null || confirmations === void 0 ? void 0 : confirmations.toString()) !== null && _a !== void 0 ? _a : '1', timeout: (_b = timeout === null || timeout === void 0 ? void 0 : timeout.toString()) !== null && _b !== void 0 ? _b : '0', }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.waitForTransaction(transactionHash, confirmations, timeout); }))); }); } /** * * Returns the transaction receipt for hash or null if the transaction has not been mined. * * @param transactionHash - transaction hash * * @returns TransactionReceipt */ getTransactionReceipt(transactionHash) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getTransactionReceipt', { transactionHash: yield transactionHash, }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getTransactionReceipt(transactionHash); }))); }); } /** * * Returns the transaction response for hash or null if the transaction has not been mined. * * @param transactionHash - transaction hash * * @returns TransactionReceipt */ getTransaction(transactionHash) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getTransaction', { transactionHash: yield transactionHash, }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getTransaction(transactionHash); }))); }); } detectNetwork() { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('detectNetwork'); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.detectNetwork(); }))); }); } /** * * Returns the contract code of address as of the blockTag block height. * If there is no contract currently deployed, the result is 0x. * * @param addressOrName - The hexadecimal string address who Code we are getting or name * @param blockTag - string or number of the block * * @returns string */ getCode(addressOrName, blockTag) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('getCode', { addressOrName: yield addressOrName, blockTag: JSON.stringify(yield blockTag), }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.getCode(addressOrName, blockTag); }))); }); } /** * The raw send method allows you to call any method on the RPC provider * * @param method - The name of the method * @param params - The parameters that the method receives * @returns */ send(method, params) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('send', { params: JSON.stringify(params), method, }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.send(method, params); }))); }); } /** * sendTransaction allows you to send a signed transaction to the network * defined in the constructor. * * @param signedTransaction - The signed transaction * @returns */ sendTransaction(signedTransaction) { return __awaiter(this, void 0, void 0, function* () { this.setMethodCtx('sendTransaction', { signedTransaction: yield signedTransaction, }); return this.stopwatch(() => this.execute((provider) => __awaiter(this, void 0, void 0, function* () { return yield provider.sendTransaction(signedTransaction); }))); }); } } exports.RPCProvider = RPCProvider;