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
JavaScript
;
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;