@liquality/fee-suggestions
Version:
JavaScript library that suggest fees on Ethereum after EIP-1559 using historical data using ethers.js
133 lines (122 loc) • 5.41 kB
text/typescript
import { JsonRpcProvider } from '@ethersproject/providers';
import { ema } from 'moving-averages';
import { BASE_FEE_ADDITIONAL_PADDING, BASE_FEE_BLOCKS_TO_CONFIRMATION_MULTIPLIERS } from './constants';
import { FeeHistoryResponse, MaxFeeSuggestions, MaxPriorityFeeSuggestions, Suggestions } from './entities';
import {
calculateBaseFeeTrend,
getOutlierBlocksToRemove,
gweiToWei,
multiply,
rewardsFilterOutliers,
suggestBaseFee,
weiToGweiNumber,
weiToString,
} from './utils';
export const suggestMaxBaseFee = async (
provider: JsonRpcProvider,
fromBlock = 'latest',
blockCountHistory = 100
): Promise<MaxFeeSuggestions> => {
const feeHistory: FeeHistoryResponse = await provider.send('eth_feeHistory', [blockCountHistory, fromBlock, []]);
const currentBaseFee = weiToString(feeHistory?.baseFeePerGas[feeHistory?.baseFeePerGas.length - 1]);
const baseFees: number[] = [];
const order = [];
for (let i = 0; i < feeHistory.baseFeePerGas.length; i++) {
baseFees.push(weiToGweiNumber(feeHistory.baseFeePerGas[i]));
order.push(i);
}
const baseFeeTrend = calculateBaseFeeTrend(baseFees, currentBaseFee);
baseFees[baseFees.length - 1] *= 9 / 8;
for (let i = feeHistory.gasUsedRatio.length - 1; i >= 0; i--) {
if (feeHistory.gasUsedRatio[i] > 0.9) {
baseFees[i] = baseFees[i + 1];
}
}
order.sort((a, b) => {
const aa = baseFees[a];
const bb = baseFees[b];
if (aa < bb) {
return -1;
}
if (aa > bb) {
return 1;
}
return 0;
});
const result = [];
let maxBaseFee = 0;
for (let timeFactor = 15; timeFactor >= 0; timeFactor--) {
let bf = suggestBaseFee(baseFees, order, timeFactor, 0.1, 0.3);
if (bf > maxBaseFee) {
maxBaseFee = bf;
} else {
bf = maxBaseFee;
}
result[timeFactor] = bf;
}
const baseFeeSuggestion = gweiToWei(multiply(Math.max(...result), BASE_FEE_ADDITIONAL_PADDING));
const blocksToConfirmationByBaseFee = {
120: multiply(baseFeeSuggestion, BASE_FEE_BLOCKS_TO_CONFIRMATION_MULTIPLIERS[120]).toFixed(0),
240: multiply(baseFeeSuggestion, BASE_FEE_BLOCKS_TO_CONFIRMATION_MULTIPLIERS[240]).toFixed(0),
4: multiply(baseFeeSuggestion, BASE_FEE_BLOCKS_TO_CONFIRMATION_MULTIPLIERS[4]).toFixed(0),
40: multiply(baseFeeSuggestion, BASE_FEE_BLOCKS_TO_CONFIRMATION_MULTIPLIERS[40]).toFixed(0),
8: multiply(baseFeeSuggestion, BASE_FEE_BLOCKS_TO_CONFIRMATION_MULTIPLIERS[8]).toFixed(0),
};
return {
baseFeeSuggestion,
baseFeeTrend,
blocksToConfirmationByBaseFee,
currentBaseFee,
};
};
export const suggestMaxPriorityFee = async (provider: JsonRpcProvider, fromBlock = 'latest'): Promise<MaxPriorityFeeSuggestions> => {
const feeHistory: FeeHistoryResponse = await provider.send('eth_feeHistory', [10, fromBlock, [10, 15, 30, 45]]);
const blocksRewards = feeHistory.reward;
if (!blocksRewards.length) throw new Error('Error: block reward was empty');
const outlierBlocks = getOutlierBlocksToRemove(blocksRewards, 0);
const blocksRewardsPerc10 = rewardsFilterOutliers(blocksRewards, outlierBlocks, 0);
const blocksRewardsPerc15 = rewardsFilterOutliers(blocksRewards, outlierBlocks, 1);
const blocksRewardsPerc30 = rewardsFilterOutliers(blocksRewards, outlierBlocks, 2);
const blocksRewardsPerc45 = rewardsFilterOutliers(blocksRewards, outlierBlocks, 3);
const emaPerc10 = ema(blocksRewardsPerc10, blocksRewardsPerc10.length)[blocksRewardsPerc10.length - 1];
const emaPerc15 = ema(blocksRewardsPerc15, blocksRewardsPerc15.length)[blocksRewardsPerc15.length - 1];
const emaPerc30 = ema(blocksRewardsPerc30, blocksRewardsPerc30.length)[blocksRewardsPerc30.length - 1];
const emaPerc45 = ema(blocksRewardsPerc45, blocksRewardsPerc45.length)[blocksRewardsPerc45.length - 1];
if (emaPerc10 === undefined || emaPerc15 === undefined || emaPerc30 === undefined || emaPerc45 === undefined) {
throw new Error('Error: ema was undefined');
}
return {
blocksToConfirmationByPriorityFee: {
1: gweiToWei(emaPerc45),
2: gweiToWei(emaPerc30),
3: gweiToWei(emaPerc15),
4: gweiToWei(emaPerc10),
},
confirmationTimeByPriorityFee: {
15: gweiToWei(emaPerc45),
30: gweiToWei(emaPerc30),
45: gweiToWei(emaPerc15),
60: gweiToWei(emaPerc10),
},
maxPriorityFeeSuggestions: {
fast: gweiToWei(emaPerc30),
normal: gweiToWei(emaPerc15),
urgent: gweiToWei(emaPerc45),
},
};
};
export const suggestFees = async (provider: JsonRpcProvider): Promise<Suggestions> => {
const { baseFeeSuggestion, baseFeeTrend, currentBaseFee, blocksToConfirmationByBaseFee } = await suggestMaxBaseFee(provider);
const { maxPriorityFeeSuggestions, confirmationTimeByPriorityFee, blocksToConfirmationByPriorityFee } = await suggestMaxPriorityFee(
provider
);
return {
baseFeeSuggestion,
baseFeeTrend,
blocksToConfirmationByBaseFee,
blocksToConfirmationByPriorityFee,
confirmationTimeByPriorityFee,
currentBaseFee,
maxPriorityFeeSuggestions,
};
};