UNPKG

@rainbow-me/fee-suggestions

Version:

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

213 lines (201 loc) 5.74 kB
import { JsonRpcProvider } from '@ethersproject/providers'; import { utils } from 'ethers'; 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', [ utils.hexStripZeros(utils.hexlify(blockCountHistory)), fromBlock, [], ]); const currentBaseFee = weiToString( feeHistory?.baseFeePerGas[feeHistory?.baseFeePerGas.length - 1] ); const baseFees: number[] = []; const order: number[] = []; 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: number[] = []; 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', [ utils.hexStripZeros(utils.hexlify(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'); const boundedNormalPriorityFee = Math.min(Math.max(emaPerc15, 1), 1.8); const boundedFastMaxPriorityFee = Math.min(Math.max(emaPerc30, 1.5), 3); const boundedUrgentPriorityFee = Math.min(Math.max(emaPerc45, 2), 9); 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(boundedFastMaxPriorityFee), normal: gweiToWei(boundedNormalPriorityFee), urgent: gweiToWei(boundedUrgentPriorityFee), }, }; }; 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, }; };