UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

785 lines 36.4 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseEVMStateProvider = void 0; const crypto_rpc_1 = require("crypto-rpc"); const config_1 = __importDefault(require("../../../../config")); const decorators_1 = require("../../../../decorators/decorators"); const logger_1 = __importDefault(require("../../../../logger")); const cache_1 = require("../../../../models/cache"); const walletAddress_1 = require("../../../../models/walletAddress"); const internal_1 = require("../../../../providers/chain-state/internal/internal"); const storage_1 = require("../../../../services/storage"); const utils_1 = require("../../../../utils"); const stats_1 = require("../../../../utils/stats"); const streamWithEventPipe_1 = require("../../../../utils/streamWithEventPipe"); const apiStream_1 = require("../../external/streams/apiStream"); const erc20_1 = require("../abi/erc20"); const multisend_1 = require("../abi/multisend"); const block_1 = require("../models/block"); const transaction_1 = require("../models/transaction"); const erc20Transform_1 = require("./erc20Transform"); const internalTxTransform_1 = require("./internalTxTransform"); const populateEffectsTransform_1 = require("./populateEffectsTransform"); const populateReceiptTransform_1 = require("./populateReceiptTransform"); const provider_1 = require("./provider"); const transform_1 = require("./transform"); ; class BaseEVMStateProvider extends internal_1.InternalStateProvider { constructor(chain = 'ETH') { super(chain); this.chain = chain; this.config = config_1.default.chains[this.chain]; } async getWeb3(network, params) { for (const rpc of BaseEVMStateProvider.rpcs[this.chain]?.[network] || []) { if (!(0, provider_1.isValidProviderType)(params?.type, rpc.dataType)) { continue; } try { if (Date.now() - (rpc.lastPingTime || 0) < 10000) { // Keep the rpc from being blasted with ping calls return rpc; } await Promise.race([ rpc.web3.eth.getBlockNumber(), new Promise((_, reject) => setTimeout(reject, 5000)) ]); rpc.lastPingTime = Date.now(); return rpc; // return the first applicable rpc that's responsive } catch (e) { // try reconnecting logger_1.default.info(`Reconnecting to ${this.chain}:${network}`); if (typeof rpc.web3.currentProvider?.disconnect === 'function') { rpc.web3.currentProvider?.disconnect?.(); rpc.web3.currentProvider?.connect?.(); if (rpc.web3.currentProvider?.connected) { return rpc; } } const idx = BaseEVMStateProvider.rpcs[this.chain][network].indexOf(rpc); BaseEVMStateProvider.rpcs[this.chain][network].splice(idx, 1); } } logger_1.default.info(`Making a new connection for ${this.chain}:${network}`); const dataType = params?.type; const providerConfig = (0, provider_1.getProvider)({ network, dataType, config: this.config }); // Default to using ETH CryptoRpc with all EVM chain configs const rpcConfig = { ...providerConfig, chain: 'ETH', currencyConfig: {} }; const rpc = new crypto_rpc_1.CryptoRpc(rpcConfig, {}).get('ETH'); const rpcObj = { rpc, web3: rpc.web3, dataType: rpcConfig.dataType || 'combined' }; if (!BaseEVMStateProvider.rpcs[this.chain]) { BaseEVMStateProvider.rpcs[this.chain] = {}; } if (!BaseEVMStateProvider.rpcs[this.chain][network]) { BaseEVMStateProvider.rpcs[this.chain][network] = []; } BaseEVMStateProvider.rpcs[this.chain][network].push(rpcObj); return rpcObj; } async erc20For(network, address) { const { web3 } = await this.getWeb3(network); const contract = new web3.eth.Contract(erc20_1.ERC20Abi, address); return contract; } async getMultisendContract(network, address) { const { web3 } = await this.getWeb3(network); const contract = new web3.eth.Contract(multisend_1.MultisendAbi, address); return contract; } async getERC20TokenInfo(network, tokenAddress) { const token = await this.erc20For(network, tokenAddress); const [name, decimals, symbol] = await Promise.all([ token.methods.name().call(), token.methods.decimals().call(), token.methods.symbol().call() ]); return { name, decimals, symbol }; } async getERC20TokenAllowance(network, tokenAddress, ownerAddress, spenderAddress) { const token = await this.erc20For(network, tokenAddress); return await token.methods.allowance(ownerAddress, spenderAddress).call(); } async getFee(params) { let { network, target = 4, txType } = params; const chain = this.chain; if (network === 'livenet') { network = 'mainnet'; } let cacheKey = `getFee-${chain}-${network}-${target}`; if (txType) { cacheKey += `-type${txType}`; } return cache_1.CacheStorage.getGlobalOrRefresh(cacheKey, async () => { let feerate; if (txType?.toString() === '2') { const { rpc } = await this.getWeb3(network, { type: 'historical' }); feerate = await rpc.estimateFee({ nBlocks: target, txType }); } else { const txs = await transaction_1.EVMTransactionStorage.collection .find({ chain, network, blockHeight: { $gt: 0 } }) .project({ gasPrice: 1, blockHeight: 1 }) .sort({ blockHeight: -1 }) .limit(20 * 200) .toArray(); const blockGasPrices = txs .map(tx => Number(tx.gasPrice)) .filter(gasPrice => gasPrice) .sort((a, b) => b - a); const whichQuartile = Math.min(target, 4) || 1; const quartileMedian = stats_1.StatsUtil.getNthQuartileMedian(blockGasPrices, whichQuartile); const roundedGwei = (quartileMedian / 1e9).toFixed(2); const gwei = Number(roundedGwei) || 0; feerate = gwei * 1e9; } return { feerate, blocks: target }; }, cache_1.CacheStorage.Times.Minute); } async getPriorityFee(params) { let { network, percentile } = params; const chain = this.chain; const priorityFeePercentile = percentile || 15; if (network === 'livenet') { network = 'mainnet'; } let cacheKey = `getFee-${chain}-${network}-priorityFee-${priorityFeePercentile}`; return cache_1.CacheStorage.getGlobalOrRefresh(cacheKey, async () => { const { rpc } = await this.getWeb3(network); let feerate = await rpc.estimateMaxPriorityFee({ percentile: priorityFeePercentile }); return { feerate }; }, cache_1.CacheStorage.Times.Minute); } async getBalanceForAddress(params) { const { chain, network, address, args } = params; const { web3 } = await this.getWeb3(network, { type: 'realtime' }); const tokenAddress = args?.tokenAddress; const addressLower = address.toLowerCase(); const hex = args?.hex === 'true' || args?.hex === '1'; const cacheKey = tokenAddress ? `getBalanceForAddress-${chain}-${network}-${addressLower}-${tokenAddress.toLowerCase()}` : `getBalanceForAddress-${chain}-${network}-${addressLower}`; const balance = await cache_1.CacheStorage.getGlobalOrRefresh(cacheKey, async () => { if (tokenAddress) { const token = await this.erc20For(network, tokenAddress); const balance = await token.methods.balanceOf(address).call(); const numberBalance = '0x' + BigInt(balance).toString(16); return { confirmed: numberBalance, unconfirmed: '0x0', balance: numberBalance }; } else { const balance = await web3.eth.getBalance(address); const numberBalance = '0x' + BigInt(balance).toString(16); return { confirmed: numberBalance, unconfirmed: '0x0', balance: numberBalance }; } }, cache_1.CacheStorage.Times.Minute); return { confirmed: hex ? balance.confirmed : Number(balance.confirmed), unconfirmed: hex ? balance.unconfirmed : Number(balance.unconfirmed), balance: hex ? balance.balance : Number(balance.balance) }; } async getLocalTip({ chain, network }) { return block_1.EVMBlockStorage.getLocalTip({ chain, network }); } async getReceipt(network, txid) { const { web3 } = await this.getWeb3(network, { type: 'historical' }); return web3.eth.getTransactionReceipt(txid); } async populateReceipt(tx) { if (!tx.receipt) { const receipt = await this.getReceipt(tx.network, tx.txid); if (receipt) { const fee = receipt.gasUsed * tx.gasPrice; await transaction_1.EVMTransactionStorage.collection.updateOne({ _id: tx._id }, { $set: { receipt, fee } }); tx.receipt = receipt; tx.fee = fee; } } return tx; } populateEffects(tx) { if (!tx.effects || (tx.effects && tx.effects.length == 0)) { tx.effects = transaction_1.EVMTransactionStorage.getEffects(tx); } return tx; } async getTransaction(params) { try { params.network = params.network.toLowerCase(); let { chain, network, txId } = params; if (typeof txId !== 'string') { throw new Error('Missing required param: txId'); } if (!chain) { throw new Error('Missing required param: chain'); } if (!network) { throw new Error('Missing required param: network'); } let { tipHeight, found } = await this._getTransaction(params); if (found) { let confirmations = 0; if (found.blockHeight && found.blockHeight >= 0) { confirmations = tipHeight - found.blockHeight + 1; } found = await this.populateReceipt(found); // Add effects to old db entries found = this.populateEffects(found); const convertedTx = transaction_1.EVMTransactionStorage._apiTransform(found, { object: true }); return { ...convertedTx, confirmations }; } } catch (err) { console.error(err); } return undefined; } async _getTransaction(params) { let { chain, network, txId } = params; let query = { chain, network, txid: txId }; const tip = await this.getLocalTip(params); const tipHeight = tip ? tip.height : 0; const found = await transaction_1.EVMTransactionStorage.collection.findOne(query); return { tipHeight, found }; } async broadcastTransaction(params) { const { network, rawTx } = params; const { web3 } = await this.getWeb3(network, { type: 'realtime' }); const rawTxs = typeof rawTx === 'string' ? [rawTx] : rawTx; const txids = new Array(); for (const tx of rawTxs) { const txid = await new Promise((resolve, reject) => { web3.eth .sendSignedTransaction(tx) .on('transactionHash', resolve) .on('error', reject) .catch(e => { logger_1.default.error('%o', e); reject(e); }); }); txids.push(txid); } return txids.length === 1 ? txids[0] : txids; } async streamAddressTransactions(params) { return new Promise(async (resolve, reject) => { try { await this._buildAddressTransactionsStream(params); return resolve(); } catch (err) { return reject(err); } }); } async _buildAddressTransactionsStream(params) { const { req, res, args, chain, network, address } = params; const { limit, /*since,*/ tokenAddress } = args; if (!args.tokenAddress) { const query = { $or: [ { chain, network, from: address }, { chain, network, to: address }, { chain, network, 'internal.action.to': address }, // Retained for old db entries { chain, network, 'effects.to': address } ] }; // NOTE: commented out since and paging for now b/c they were causing extra long query times on insight. // The case where an address has >1000 txns is an edge case ATM and can be addressed later storage_1.Storage.apiStreamingFind(transaction_1.EVMTransactionStorage, query, { limit /*since, paging: '_id'*/ }, req, res); } else { try { const tokenTransfers = await this.getErc20Transfers(network, address, tokenAddress, args); res.json(tokenTransfers); } catch (err) { logger_1.default.error('Error streaming address transactions: %o', err.stack || err.message || err); throw err; } } } async streamTransactions(params) { const { chain, network, req, res, args } = params; let { blockHash, blockHeight } = args; if (!chain || !network) { throw new Error('Missing chain or network'); } let query = { chain, network: network.toLowerCase() }; if (blockHeight !== undefined) { query.blockHeight = Number(blockHeight); } if (blockHash !== undefined) { query.blockHash = blockHash; } const tip = await this.getLocalTip(params); const tipHeight = tip ? tip.height : 0; return storage_1.Storage.apiStreamingFind(transaction_1.EVMTransactionStorage, query, args, req, res, t => { let confirmations = 0; if (t.blockHeight !== undefined && t.blockHeight >= 0) { confirmations = tipHeight - t.blockHeight + 1; } // Add effects to old db entries if (!t.effects || (t.effects && t.effects.length == 0)) { t.effects = transaction_1.EVMTransactionStorage.getEffects(t); } const convertedTx = transaction_1.EVMTransactionStorage._apiTransform(t, { object: true }); return JSON.stringify({ ...convertedTx, confirmations }); }); } async getWalletBalance(params) { const { network, args, wallet } = params; const hex = args.hex === 'true' || args.hex === '1'; if (wallet._id === undefined) { throw new Error('Wallet balance can only be retrieved for wallets with the _id property'); } let addresses = await this.getWalletAddresses(wallet._id); addresses = !args.address ? addresses : addresses.filter(({ address }) => address.toLowerCase() === args.address.toLowerCase()); let addressBalances = await Promise.all(addresses.map(({ address }) => this.getBalanceForAddress({ chain: this.chain, network, address, args }))); let balance = addressBalances.reduce((prev, cur) => ({ unconfirmed: BigInt(prev.unconfirmed) + BigInt(cur.unconfirmed), confirmed: BigInt(prev.confirmed) + BigInt(cur.confirmed), balance: BigInt(prev.balance) + BigInt(cur.balance) }), { unconfirmed: 0n, confirmed: 0n, balance: 0n }); return { unconfirmed: hex ? '0x' + balance.unconfirmed.toString(16) : Number(balance.unconfirmed), confirmed: hex ? '0x' + balance.confirmed.toString(16) : Number(balance.confirmed), balance: hex ? '0x' + balance.balance.toString(16) : Number(balance.balance) }; } getWalletTransactionQuery(params) { const { chain, network, wallet, args } = params; let query = { chain, network, wallets: wallet._id, 'wallets.0': { $exists: true }, blockHeight: { $gt: -3 } // Exclude invalid transactions }; if (args) { if (args.startBlock || args.endBlock) { query.$or = []; if (args.includeMempool) { query.$or.push({ blockHeight: -1 /* SpentHeightIndicators.pending */ }); } let blockRangeQuery = {}; if (args.startBlock) { blockRangeQuery.$gte = Number(args.startBlock); } if (args.endBlock) { blockRangeQuery.$lte = Number(args.endBlock); } query.$or.push({ blockHeight: blockRangeQuery }); } else { if (args.startDate) { const startDate = new Date(args.startDate); if (startDate.getTime()) { query.blockTimeNormalized = { $gte: new Date(args.startDate) }; } } if (args.endDate) { const endDate = new Date(args.endDate); if (endDate.getTime()) { query.blockTimeNormalized = query.blockTimeNormalized || {}; query.blockTimeNormalized.$lt = new Date(args.endDate); } } } if (args.includeInvalidTxs) { delete query.blockHeight; } } return query; } async streamWalletTransactions(params) { return new Promise(async (resolve, reject) => { const { network, wallet, req, res, args } = params; const { web3 } = await this.getWeb3(network); let transactionStream = new streamWithEventPipe_1.TransformWithEventPipe({ objectMode: true, passThrough: true }); const walletAddresses = (await this.getWalletAddresses(wallet._id)).map(waddres => waddres.address); const ethTransactionTransform = new transform_1.EVMListTransactionsStream(walletAddresses); const populateReceipt = new populateReceiptTransform_1.PopulateReceiptTransform(); const populateEffects = new populateEffectsTransform_1.PopulateEffectsTransform(); const streamParams = { transactionStream, populateEffects, walletAddresses }; transactionStream = await this._buildWalletTransactionsStream(params, streamParams); if (!args.tokenAddress && wallet._id) { const internalTxTransform = new internalTxTransform_1.InternalTxRelatedFilterTransform(web3, wallet._id); transactionStream = transactionStream.eventPipe(internalTxTransform); } transactionStream = transactionStream .eventPipe(populateReceipt) .eventPipe(ethTransactionTransform); try { const result = await apiStream_1.ExternalApiStream.onStream(transactionStream, req, res, { jsonl: true }); if (!result?.success) { logger_1.default.error('Error mid-stream (streamWalletTransactions): %o', result.error?.log || result.error); } return resolve(); } catch (err) { return reject(err); } }); } async _buildWalletTransactionsStream(params, streamParams) { const query = this.getWalletTransactionQuery(params); let { transactionStream, populateEffects } = streamParams; transactionStream = transaction_1.EVMTransactionStorage.collection .find(query) .sort({ blockTimeNormalized: 1 }) .addCursorFlag('noCursorTimeout', true) .pipe(new streamWithEventPipe_1.TransformWithEventPipe({ objectMode: true, passThrough: true })); transactionStream = transactionStream.eventPipe(populateEffects); // For old db entires if (params.args.tokenAddress) { const erc20Transform = new erc20Transform_1.Erc20RelatedFilterTransform(params.args.tokenAddress); transactionStream = transactionStream.eventPipe(erc20Transform); } return transactionStream; } async getErc20Transfers(network, address, tokenAddress, args = {}) { const token = await this.erc20For(network, tokenAddress); let windowSize = 100; const { web3 } = await this.getWeb3(network); const tip = await web3.eth.getBlockNumber(); // If endBlock or startBlock is negative, it is a block offset from the tip if (args.endBlock < 0) { args.endBlock = tip + Number(args.endBlock); } if (args.startBlock < 0) { args.startBlock = tip + Number(args.startBlock); } args.endBlock = Math.min(args.endBlock ?? tip, tip); args.startBlock = Math.max(args.startBlock != null ? Number(args.startBlock) : args.endBlock - 10000, 0); if (isNaN(args.startBlock) || isNaN(args.endBlock)) { throw new Error('startBlock and endBlock must be numbers'); } else if (args.endBlock < args.startBlock) { throw new Error('startBlock cannot be greater than endBlock'); } else if (args.endBlock - args.startBlock > 10000) { throw new Error('Cannot scan more than 10000 blocks at a time. Please limit your search with startBlock and endBlock'); } windowSize = Math.min(windowSize, args.endBlock - args.startBlock); let endBlock = args.endBlock; const tokenTransfers = []; while (windowSize > 0) { const [sent, received] = await Promise.all([ token.getPastEvents('Transfer', { filter: { _from: address }, fromBlock: endBlock - windowSize, toBlock: endBlock }), token.getPastEvents('Transfer', { filter: { _to: address }, fromBlock: endBlock - windowSize, toBlock: endBlock }) ]); tokenTransfers.push(...this.convertTokenTransfers([...sent, ...received])); endBlock -= windowSize + 1; windowSize = Math.min(windowSize, endBlock - args.startBlock); } return tokenTransfers; } convertTokenTransfers(tokenTransfers) { return tokenTransfers.map(this.convertTokenTransfer); } convertTokenTransfer(transfer) { const { blockHash, blockNumber, transactionHash, returnValues, transactionIndex } = transfer; return { blockHash, blockNumber, transactionHash, transactionIndex, hash: transactionHash, from: returnValues['_from'], to: returnValues['_to'], value: returnValues['_value'] }; } async getAccountNonce(network, address) { const { web3 } = await this.getWeb3(network, { type: 'realtime' }); const count = await web3.eth.getTransactionCount(address); return count; /* *return EthTransactionStorage.collection.countDocuments({ * chain: 'ETH', * network, * from: address, * blockHeight: { $gt: -1 } *}); */ } async getWalletTokenTransactions(network, walletId, tokenAddress, args) { const addresses = await this.getWalletAddresses(walletId); const allTokenQueries = Array(); for (const walletAddress of addresses) { const transfers = this.getErc20Transfers(network, walletAddress.address, tokenAddress, args); allTokenQueries.push(transfers); } let batches = await Promise.all(allTokenQueries); let txs = batches.reduce((agg, batch) => agg.concat(batch)); return txs.sort((tx1, tx2) => tx1.blockNumber - tx2.blockNumber); } async estimateGas(params) { return new Promise(async (resolve, reject) => { try { let { network, value, from, data, /*gasPrice,*/ to } = params; const { web3 } = await this.getWeb3(network, { type: 'realtime' }); const dataDecoded = transaction_1.EVMTransactionStorage.abiDecode(data); if (dataDecoded && dataDecoded.type === 'INVOICE' && dataDecoded.name === 'pay') { value = dataDecoded.params[0].value; // gasPrice = dataDecoded.params[1].value; } else if (data && data.type === 'MULTISEND') { try { let method, gasLimit; const contract = await this.getMultisendContract(network, to); const addresses = web3.eth.abi.decodeParameter('address[]', data.addresses); const amounts = web3.eth.abi.decodeParameter('uint256[]', data.amounts); switch (data.method) { case 'sendErc20': method = contract.methods.sendErc20(data.tokenAddress, addresses, amounts); gasLimit = method ? await method.estimateGas({ from }) : undefined; break; case 'sendEth': method = contract.methods.sendEth(addresses, amounts); gasLimit = method ? await method.estimateGas({ from, value }) : undefined; break; default: break; } return resolve(Number(gasLimit)); } catch (err) { return reject(err); } } let _value; if (data) { // Gas estimation might fail with `insufficient funds` if value is higher than balance for a normal send. // We want this method to give a blind fee estimation, though, so we should not include the value // unless it's needed for estimating smart contract execution. _value = web3.utils.toHex(value); } const opts = { method: 'eth_estimateGas', params: [ { data, to: to && to.toLowerCase(), from: from && from.toLowerCase(), // gasPrice: web3.utils.toHex(gasPrice), // Setting this lower than the baseFee of the last block will cause an error. Better to just leave it out. value: _value } ], jsonrpc: '2.0', id: 'bitcore-' + Date.now() }; let provider = web3.currentProvider; provider.send(opts, (err, data) => { if (err) return reject(err); if (!data.result) return reject(data.error || data); return resolve(Number(data.result)); }); } catch (err) { return reject(err); } }); } async getBlocks(params) { const { tipHeight, blocks } = await this._getBlocks(params); const blockTransform = (b) => { let confirmations = 0; if (b.height && b.height >= 0) { confirmations = tipHeight - b.height + 1; } const convertedBlock = block_1.EVMBlockStorage._apiTransform(b, { object: true }); return { ...convertedBlock, confirmations }; }; return blocks.map(blockTransform); } async _getBlocks(params) { const { query, options } = this.getBlocksQuery(params); let cursor = block_1.EVMBlockStorage.collection.find(query, options).addCursorFlag('noCursorTimeout', true); if (options.sort) { cursor = cursor.sort(options.sort); } const blocks = await cursor.toArray(); const tip = await this.getLocalTip(params); const tipHeight = tip ? tip.height : 0; return { tipHeight, blocks }; } async updateWallet(params) { const { chain, network } = params; const addressBatches = (0, utils_1.partition)(params.addresses, 500); for (let addressBatch of addressBatches) { const walletAddressInserts = addressBatch.map(address => { return { insertOne: { document: { chain, network, wallet: params.wallet._id, address, processed: false } } }; }); try { await walletAddress_1.WalletAddressStorage.collection.bulkWrite(walletAddressInserts); } catch (err) { if (err.code !== 11000) { throw err; } } const addressBatchLC = addressBatch.map(address => address.toLowerCase()); await transaction_1.EVMTransactionStorage.collection.updateMany({ $or: [ { chain, network, from: { $in: addressBatch } }, { chain, network, to: { $in: addressBatch } }, { chain, network, 'internal.action.to': { $in: addressBatchLC } }, // Support old db entries { chain, network, 'calls.to': { $in: addressBatchLC } }, // Support old db entries { chain, network, 'calls.abiType.type': 'ERC20', 'calls.abiType.name': { $in: ['transfer', 'transferFrom'] }, 'calls.abiType.params.type': 'address', 'calls.abiType.params.value': { $in: addressBatchLC } }, { chain, network, 'effects.to': { $in: addressBatch } }, { chain, network, 'effects.from': { $in: addressBatch } }, ] }, { $addToSet: { wallets: params.wallet._id } }); await walletAddress_1.WalletAddressStorage.collection.updateMany({ chain, network, address: { $in: addressBatch }, wallet: params.wallet._id }, { $set: { processed: true } }); } } async getBlocksRange(params) { const { chain, network, chainId, sinceBlock, args = {} } = params; let { blockId } = params; let { startDate, endDate, date, limit = 10, sort = { height: -1 } } = args; const query = {}; if (!chain || !network) { throw new Error('Missing required chain and/or network param'); } // limit - 1 because startBlock is inclusive; ensure limit is >= 0 limit = Math.max(limit - 1, 0); let height = null; if (blockId && blockId.length < 64) { height = parseInt(blockId, 10); if (isNaN(height) || height.toString(10) != blockId) { throw new Error('invalid block id provided'); } blockId = undefined; } if (date) { startDate = new Date(date); endDate = new Date(date); endDate.setDate(endDate.getDate() + 1); } if (startDate || endDate) { if (startDate) { query.startBlock = await this._getBlockNumberByDate({ date: startDate, chainId }) || 0; } if (endDate) { query.endBlock = await this._getBlockNumberByDate({ date: endDate, chainId }) || 0; } } // Get range if (sinceBlock) { let height = Number(sinceBlock); if (isNaN(height) || height.toString(10) != sinceBlock) { throw new Error('invalid block id provided'); } const { web3 } = await this.getWeb3(network); const tipHeight = await web3.eth.getBlockNumber(); if (tipHeight < height) { return []; } query.endBlock = query.endBlock ?? tipHeight; query.startBlock = query.startBlock ?? query.endBlock - limit; } else if (blockId) { const { web3 } = await this.getWeb3(network); const blk = await web3.eth.getBlock(blockId); if (!blk || blk.number == null) { throw new Error(`Could not get block ${blockId}`); } height = blk.number; } if (height != null) { query.startBlock = height; query.endBlock = height + limit; } if (query.startBlock == null || query.endBlock == null) { // Calaculate range with options const { web3 } = await this.getWeb3(network); const tipHeight = await web3.eth.getBlockNumber(); query.endBlock = query.endBlock ?? tipHeight; query.startBlock = query.startBlock ?? query.endBlock - limit; } if (query.endBlock - query.startBlock > limit) { query.endBlock = query.startBlock + limit; } const r = (0, utils_1.range)(query.startBlock, query.endBlock + 1); // +1 since range is [start, end) if (sort?.height === -1 && query.startBlock < query.endBlock) { return r.reverse(); } return r; } async _getBlockNumberByDate(params) { const { date, network } = params; const block = await block_1.EVMBlockStorage.collection.findOne({ chain: this.chain, network, timeNormalized: { $gte: date } }, { sort: { timeNormalized: 1 } }); return block?.height; } async getChainId({ network }) { const { web3 } = await this.getWeb3(network); return web3.eth.getChainId(); } async getCoinsForTx() { return { inputs: [], outputs: [] }; } } exports.BaseEVMStateProvider = BaseEVMStateProvider; BaseEVMStateProvider.rpcs = {}; __decorate([ decorators_1.historical ], BaseEVMStateProvider.prototype, "getFee", null); __decorate([ decorators_1.historical, decorators_1.internal ], BaseEVMStateProvider.prototype, "streamTransactions", null); __decorate([ decorators_1.realtime ], BaseEVMStateProvider.prototype, "getWalletBalance", null); __decorate([ decorators_1.realtime ], BaseEVMStateProvider.prototype, "getAccountNonce", null); __decorate([ decorators_1.realtime ], BaseEVMStateProvider.prototype, "estimateGas", null); //# sourceMappingURL=csp.js.map