UNPKG

eip1559-fee-suggestions-ethers

Version:

JavaScript library that suggest fees on Ethereum after EIP-1559 using historical data using ethers.js

245 lines (244 loc) 12.7 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()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.suggestFees = exports.suggestMaxPriorityFee = exports.suggestMaxBaseFee = void 0; var moving_averages_1 = require("moving-averages"); var utils_1 = require("./utils"); // samplingCurve is a helper function for the base fee percentile range calculation. var samplingCurve = function (sumWeight, sampleMin, sampleMax) { if (sumWeight <= sampleMin) { return 0; } if (sumWeight >= sampleMax) { return 1; } return ((1 - Math.cos(((sumWeight - sampleMin) * 2 * Math.PI) / (sampleMax - sampleMin))) / 2); }; var linearRegression = function (y, x) { var n = y.length; var sumX = 0; var sumY = 0; var sumXY = 0; var sumXX = 0; for (var i = 0; i < y.length; i++) { var cY = Number(y[i]); var cX = Number(x[i]); sumX += cX; sumY += cY; sumXY += cX * cY; sumXX += cX * cX; } var slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); return slope; }; var suggestBaseFee = function (baseFee, order, timeFactor, sampleMin, sampleMax) { if (timeFactor < 1e-6) { return baseFee[baseFee.length - 1]; } var pendingWeight = (1 - Math.exp(-1 / timeFactor)) / (1 - Math.exp(-baseFee.length / timeFactor)); var sumWeight = 0; var result = 0; var samplingCurveLast = 0; for (var _i = 0, order_1 = order; _i < order_1.length; _i++) { var or = order_1[_i]; sumWeight += pendingWeight * Math.exp((or - baseFee.length + 1) / timeFactor); var samplingCurveValue = samplingCurve(sumWeight, sampleMin, sampleMax); result += (samplingCurveValue - samplingCurveLast) * baseFee[or]; if (samplingCurveValue >= 1) { return result; } samplingCurveLast = samplingCurveValue; } return result; }; var suggestMaxBaseFee = function (provider, fromBlock, blockCountHistory, sampleMin, sampleMax, maxTimeFactor) { if (fromBlock === void 0) { fromBlock = 'latest'; } if (blockCountHistory === void 0) { blockCountHistory = 100; } if (sampleMin === void 0) { sampleMin = 0.1; } if (sampleMax === void 0) { sampleMax = 0.3; } if (maxTimeFactor === void 0) { maxTimeFactor = 15; } return __awaiter(void 0, void 0, void 0, function () { var feeHistory, baseFees, order, i, blocksArray, trend, i, result, maxBaseFee, timeFactor, bf, suggestedMaxBaseFee; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, provider.send('eth_feeHistory', [ blockCountHistory, fromBlock, [], ])]; case 1: feeHistory = _a.sent(); baseFees = []; order = []; for (i = 0; i < feeHistory.baseFeePerGas.length; i++) { baseFees.push(utils_1.weiToGweiNumber(feeHistory.baseFeePerGas[i])); order.push(i); } blocksArray = Array.from(Array(blockCountHistory + 1).keys()); trend = linearRegression(baseFees, blocksArray); // If a block is full then the baseFee of the next block is copied. The reason is that in full blocks the minimal tip might not be enough to get included. // The last (pending) block is also assumed to end up being full in order to give some upwards bias for urgent suggestions. baseFees[baseFees.length - 1] *= 9 / 8; for (i = feeHistory.gasUsedRatio.length - 1; i >= 0; i--) { if (feeHistory.gasUsedRatio[i] > 0.9) { baseFees[i] = baseFees[i + 1]; } } order.sort(function (a, b) { var aa = baseFees[a]; var bb = baseFees[b]; if (aa < bb) { return -1; } if (aa > bb) { return 1; } return 0; }); result = []; maxBaseFee = 0; for (timeFactor = maxTimeFactor; timeFactor >= 0; timeFactor--) { bf = suggestBaseFee(baseFees, order, timeFactor, sampleMin, sampleMax); if (bf > maxBaseFee) { maxBaseFee = bf; } else { bf = maxBaseFee; } result[timeFactor] = bf; } suggestedMaxBaseFee = Math.max.apply(Math, result); return [2 /*return*/, { baseFeeSuggestion: utils_1.gweiToWei(suggestedMaxBaseFee), baseFeeTrend: trend, }]; } }); }); }; exports.suggestMaxBaseFee = suggestMaxBaseFee; var suggestMaxPriorityFee = function (provider, fromBlock) { if (fromBlock === void 0) { fromBlock = 'latest'; } return __awaiter(void 0, void 0, void 0, function () { var feeHistory, blocksRewards, blocksRewardsPerc10, blocksRewardsPerc20, blocksRewardsPerc25, blocksRewardsPerc30, blocksRewardsPerc40, blocksRewardsPerc50, emaPerc10, emaPerc20, emaPerc25, emaPerc30, emaPerc40, emaPerc50; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, provider.send('eth_feeHistory', [ 10, fromBlock, [10, 20, 25, 30, 40, 50], ])]; case 1: feeHistory = _a.sent(); blocksRewards = feeHistory.reward; if (!blocksRewards.length) throw new Error('Error: block reward was empty'); blocksRewardsPerc10 = blocksRewards.map(function (reward) { return utils_1.weiToGweiNumber(reward[0]); }); blocksRewardsPerc20 = blocksRewards.map(function (reward) { return utils_1.weiToGweiNumber(reward[1]); }); blocksRewardsPerc25 = blocksRewards.map(function (reward) { return utils_1.weiToGweiNumber(reward[2]); }); blocksRewardsPerc30 = blocksRewards.map(function (reward) { return utils_1.weiToGweiNumber(reward[3]); }); blocksRewardsPerc40 = blocksRewards.map(function (reward) { return utils_1.weiToGweiNumber(reward[4]); }); blocksRewardsPerc50 = blocksRewards.map(function (reward) { return utils_1.weiToGweiNumber(reward[5]); }); emaPerc10 = moving_averages_1.ema(blocksRewardsPerc10, blocksRewardsPerc10.length).at(-1); emaPerc20 = moving_averages_1.ema(blocksRewardsPerc20, blocksRewardsPerc20.length).at(-1); emaPerc25 = moving_averages_1.ema(blocksRewardsPerc25, blocksRewardsPerc25.length).at(-1); emaPerc30 = moving_averages_1.ema(blocksRewardsPerc30, blocksRewardsPerc30.length).at(-1); emaPerc40 = moving_averages_1.ema(blocksRewardsPerc40, blocksRewardsPerc40.length).at(-1); emaPerc50 = moving_averages_1.ema(blocksRewardsPerc50, blocksRewardsPerc50.length).at(-1); if (emaPerc10 === undefined || emaPerc20 === undefined || emaPerc25 === undefined || emaPerc30 === undefined || emaPerc40 === undefined || emaPerc50 === undefined) throw new Error('Error: ema was empty'); return [2 /*return*/, { confirmationTimeByPriorityFee: { 15: utils_1.gweiToWei(emaPerc50), 30: utils_1.gweiToWei(emaPerc40), 45: utils_1.gweiToWei(emaPerc30), 60: utils_1.gweiToWei(emaPerc25), 75: utils_1.gweiToWei(emaPerc10), }, maxPriorityFeeSuggestions: { fast: utils_1.gweiToWei(emaPerc30), normal: utils_1.gweiToWei(emaPerc20), urgent: utils_1.gweiToWei(emaPerc40), }, }]; } }); }); }; exports.suggestMaxPriorityFee = suggestMaxPriorityFee; var suggestFees = function (provider) { return __awaiter(void 0, void 0, void 0, function () { var _a, baseFeeSuggestion, baseFeeTrend, _b, maxPriorityFeeSuggestions, confirmationTimeByPriorityFee; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, exports.suggestMaxBaseFee(provider)]; case 1: _a = _c.sent(), baseFeeSuggestion = _a.baseFeeSuggestion, baseFeeTrend = _a.baseFeeTrend; return [4 /*yield*/, exports.suggestMaxPriorityFee(provider)]; case 2: _b = _c.sent(), maxPriorityFeeSuggestions = _b.maxPriorityFeeSuggestions, confirmationTimeByPriorityFee = _b.confirmationTimeByPriorityFee; return [2 /*return*/, { baseFeeSuggestion: baseFeeSuggestion, baseFeeTrend: baseFeeTrend, confirmationTimeByPriorityFee: confirmationTimeByPriorityFee, maxPriorityFeeSuggestions: maxPriorityFeeSuggestions, }]; } }); }); }; exports.suggestFees = suggestFees;