UNPKG

sparrow-controllers

Version:

Collection of platform-agnostic modules for creating secure data models for cryptocurrency wallets

202 lines 11.1 kB
"use strict"; 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 }); const ethereumjs_util_1 = require("ethereumjs-util"); const util_1 = require("../util"); const MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL = 1024; /** * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of * blocks that have occurred recently on a network. * * To learn more, see these resources: * * - <https://infura.io/docs/ethereum#operation/eth_feeHistory> * - <https://github.com/zsfelfoldi/feehistory/blob/main/docs/feeHistory.md> * - <https://github.com/ethereum/go-ethereum/blob/57a3fab8a75eeb9c2f4fab770b73b51b9fe672c5/eth/gasprice/feehistory.go#L180> * - <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md> * - <https://gas-api.metaswap.codefi.network/testFeeHistory> * * @param args - The arguments to this function. * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question. * @param args.endBlock - The desired end of the requested block range. Can be "latest" if you want * to start from the latest successful block or the number of a known past block. * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024, * multiple calls to `eth_feeHistory` will be made. * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by * the priority fee. It will then go through each transaction and add the total amount of gas paid * for that transaction to a bucket which maxes out at the total gas used for the whole block. As * the bucket fills, it will cross percentages which correspond to the percentiles specified here, * and the priority fees of the first transactions which cause it to reach those percentages will be * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key * gas used contribution levels, where earlier levels have smaller contributions and later levels * have higher contributions. * @param args.includeNextBlock - Whether to include an extra block that represents the next * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is * estimated). * @returns The list of blocks and their fee data, sorted from oldest to newest. */ function fetchBlockFeeHistory({ ethQuery, numberOfBlocks: totalNumberOfBlocks, endBlock: givenEndBlock = 'latest', percentiles: givenPercentiles = [], includeNextBlock = false, }) { return __awaiter(this, void 0, void 0, function* () { const percentiles = givenPercentiles.length > 0 ? Array.from(new Set(givenPercentiles)).sort((a, b) => a - b) : []; const finalEndBlockNumber = givenEndBlock === 'latest' ? (0, util_1.fromHex)(yield (0, util_1.query)(ethQuery, 'blockNumber')) : givenEndBlock; const requestChunkSpecifiers = determineRequestChunkSpecifiers(finalEndBlockNumber, totalNumberOfBlocks); const blockChunks = yield Promise.all(requestChunkSpecifiers.map(({ numberOfBlocks, endBlockNumber }, i) => { return i === requestChunkSpecifiers.length - 1 ? makeRequestForChunk({ ethQuery, numberOfBlocks, endBlockNumber, percentiles, includeNextBlock, }) : makeRequestForChunk({ ethQuery, numberOfBlocks, endBlockNumber, percentiles, includeNextBlock: false, }); })); return blockChunks.reduce((array, blocks) => [...array, ...blocks], []); }); } exports.default = fetchBlockFeeHistory; /** * Builds an ExistingFeeHistoryBlock. * * @param args - The args to this function. * @param args.number - The number of the block. * @param args.baseFeePerGas - The base fee per gas of the block. * @param args.blockIndex - The index of the block in the source chunk. * @param args.gasUsedRatios - The gas used ratios for the block. * @param args.priorityFeePercentileGroups - The priority fee percentile groups for the block. * @param args.percentiles - The percentiles used to fetch the source chunk. * @returns The ExistingFeeHistoryBlock. */ function buildExistingFeeHistoryBlock({ baseFeePerGas, number, blockIndex, gasUsedRatios, priorityFeePercentileGroups, percentiles, }) { const gasUsedRatio = gasUsedRatios[blockIndex]; const priorityFeesForEachPercentile = priorityFeePercentileGroups[blockIndex]; const priorityFeesByPercentile = percentiles.reduce((obj, percentile, percentileIndex) => { const priorityFee = priorityFeesForEachPercentile[percentileIndex]; return Object.assign(Object.assign({}, obj), { [percentile]: (0, util_1.fromHex)(priorityFee) }); }, {}); return { number, baseFeePerGas, gasUsedRatio, priorityFeesByPercentile, }; } /** * Builds a NextFeeHistoryBlock. * * @param args - The args to this function. * @param args.baseFeePerGas - The base fee per gas of the block. * @param args.number - The number of the block. * @returns The NextFeeHistoryBlock. */ function buildNextFeeHistoryBlock({ baseFeePerGas, number, }) { return { number, baseFeePerGas, gasUsedRatio: null, priorityFeesByPercentile: null, }; } /** * Uses eth_feeHistory to request historical data about a group of blocks (max size 1024). * * @param args - The arguments * @param args.ethQuery - An EthQuery instance. * @param args.numberOfBlocks - The number of blocks in the chunk. Must be at most 1024, as this is * the maximum that `eth_feeHistory` can return in one call. * @param args.endBlockNumber - The end of the requested block range. * @param args.percentiles - A set of numbers between 1 and 100 that will be used to pull priority * fees for each block. * @param args.includeNextBlock - Whether to include an extra block that represents the next * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is * estimated). * @returns A list of block data. */ function makeRequestForChunk({ ethQuery, numberOfBlocks, endBlockNumber, percentiles, includeNextBlock, }) { var _a; return __awaiter(this, void 0, void 0, function* () { const response = yield (0, util_1.query)(ethQuery, 'eth_feeHistory', [(0, util_1.toHex)(numberOfBlocks), (0, util_1.toHex)(endBlockNumber), percentiles]); const startBlockNumber = (0, util_1.fromHex)(response.oldestBlock); if (response.baseFeePerGas !== undefined && response.baseFeePerGas.length > 0 && response.gasUsedRatio.length > 0 && (response.reward === undefined || response.reward.length > 0)) { // Per // <https://github.com/ethereum/go-ethereum/blob/57a3fab8a75eeb9c2f4fab770b73b51b9fe672c5/eth/gasprice/feehistory.go#L191-L192>, // baseFeePerGas will always include an extra item which is the calculated base fee for the // next (future) block. We may or may not care about this; if we don't, chop it off. const baseFeesPerGasAsHex = includeNextBlock ? response.baseFeePerGas : response.baseFeePerGas.slice(0, numberOfBlocks); const gasUsedRatios = response.gasUsedRatio; const priorityFeePercentileGroups = (_a = response.reward) !== null && _a !== void 0 ? _a : []; // Chain is allowed to return fewer number of block results const numberOfExistingResults = gasUsedRatios.length; return baseFeesPerGasAsHex.map((baseFeePerGasAsHex, blockIndex) => { const baseFeePerGas = (0, util_1.fromHex)(baseFeePerGasAsHex); const number = startBlockNumber.addn(blockIndex); return blockIndex >= numberOfExistingResults ? buildNextFeeHistoryBlock({ baseFeePerGas, number }) : buildExistingFeeHistoryBlock({ baseFeePerGas, number, blockIndex, gasUsedRatios, priorityFeePercentileGroups, percentiles, }); }); } return []; }); } /** * Divides a block range (specified by a range size and the end of the range) into chunks based on * the maximum number of blocks that `eth_feeHistory` can return in a single call. * * If the requested totalNumberOfBlocks exceed endBlockNumber, totalNumberOfBlocks is * truncated to avoid requesting chunks with negative endBlockNumber. * * @param endBlockNumber - The final block in the complete desired block range after all * `eth_feeHistory` requests have been made. * @param totalNumberOfBlocks - The total number of desired blocks after all `eth_feeHistory` * requests have been made. * @returns A set of arguments that can be used to make requests to `eth_feeHistory` in order to * retrieve all of the requested blocks, sorted from oldest block to newest block. */ function determineRequestChunkSpecifiers(endBlockNumber, totalNumberOfBlocks) { if (endBlockNumber.lt(new ethereumjs_util_1.BN(totalNumberOfBlocks))) { totalNumberOfBlocks = endBlockNumber.toNumber(); } const specifiers = []; for (let chunkStartBlockNumber = endBlockNumber.subn(totalNumberOfBlocks); chunkStartBlockNumber.lt(endBlockNumber); chunkStartBlockNumber = chunkStartBlockNumber.addn(MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL)) { const distanceToEnd = endBlockNumber.sub(chunkStartBlockNumber).toNumber(); const numberOfBlocks = distanceToEnd < MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL ? distanceToEnd : MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL; const chunkEndBlockNumber = chunkStartBlockNumber.addn(numberOfBlocks); specifiers.push({ numberOfBlocks, endBlockNumber: chunkEndBlockNumber }); } return specifiers; } //# sourceMappingURL=fetchBlockFeeHistory.js.map