@@ -5,14 +5,13 @@ import Web3 from 'web3';
55// ethgasstation returns values in floating point, one order of magitude
66// more than gwei. see: https://docs.ethgasstation.info
77// we don't want to charge users more than the gas tank funds
8−const minGas = gas => Math.min(gas, MAX_GAS_PRICE_GWEI);
8+const minGas = gas => Math.min(Math.ceil(gas), MAX_GAS_PRICE_GWEI);
99
10−const feeToWei = fee => Web3.utils.toHex(Web3.utils.toWei(String(fee), 'gwei'));
10+const feeToInt = f => (f < 1 ? 1 : Math.round(f));
1111
12−// Gwei with milligwei precision, rounded up so we never underpay
13−const roundGwei = gwei => Math.ceil(gwei * 1000) / 1000;
12+const feeToWei = fee => Web3.utils.toHex(Web3.utils.toWei(String(fee), 'gwei'));
1413
15−const weiToGwei = wei => roundGwei(wei / 1_000_000_000);
14+const weiToGwei = wei => Math.ceil(wei / 1_000_000_000);
1615
1716const calculateMaxFee = (baseFee, maxPriorityFee) =>
1817 feeToWei(Math.round(2 * baseFee + maxPriorityFee));
@@ -25,75 +24,82 @@ export const defaultGasValues = value => ({
2524 price: value,
2625 wait: '1',
2726 maxFeePerGas: value,
28− maxPriorityFeePerGas: Math.min(1, value),
27+ maxPriorityFeePerGas: 1,
2928 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
3029 },
3130 average: {
3231 price: value,
3332 wait: '1',
3433 maxFeePerGas: value,
35− maxPriorityFeePerGas: Math.min(1, value),
34+ maxPriorityFeePerGas: 1,
3635 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
3736 },
3837 low: {
3938 price: value,
4039 wait: '1',
4140 maxFeePerGas: value,
42− maxPriorityFeePerGas: Math.min(1, value),
41+ maxPriorityFeePerGas: 1,
4342 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
4443 },
4544});
4645
47−const median = values => {
48− const sorted = [...values].sort((a, b) => a - b);
49− const mid = Math.floor(sorted.length / 2);
50− return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
51−};
52−
53−// Number of recent blocks to sample, and the priority fee percentiles
54−// used for the low, average and fast tiers respectively
55−const FEE_HISTORY_BLOCKS = 20;
56−const REWARD_PERCENTILES = [10, 50, 90];
57−// Floor for the priority fee, since zero tip transactions may never be mined
58−const MIN_PRIORITY_FEE_GWEI = 0.001;
59−
60−const tierFor = (baseFeeGwei, priorityFeeWei, wait) => {
61− const maxPriorityFeePerGas = Math.max(
62− weiToGwei(priorityFeeWei),
63− MIN_PRIORITY_FEE_GWEI
64− );
65− const maxFeePerGas = roundGwei(baseFeeGwei + maxPriorityFeePerGas);
66− return {
67− price: minGas(maxFeePerGas),
68− wait: formatWait(wait),
69− maxFeePerGas,
70− maxPriorityFeePerGas,
71− suggestedBaseFeePerGas: baseFeeGwei,
72− };
73−};
74−
75−// Estimates fees from the connected node via eth_feeHistory, in the same
76−// manner as wallets do. See: https://docs.alchemy.com/docs/how-to-build-a-gas-fee-estimator-using-eip-1559
77−const getGasForNetwork = async web3 => {
46+const getGasForNetwork = async providerUrl => {
7847 try {
79− const history = await web3.eth.getFeeHistory(
80− FEE_HISTORY_BLOCKS,
81− 'latest',
82− REWARD_PERCENTILES
83− );
84−
85− // baseFeePerGas includes one extra entry: the base fee of the next block
86− const nextBaseFeeWei = Number(history.baseFeePerGas.at(-1));
87− const baseFeeGwei = nextBaseFeeWei / 1_000_000_000;
88−
89− const [low, average, fast] = REWARD_PERCENTILES.map((_, i) =>
90− median(history.reward.map(rewards => Number(rewards[i])))
91− );
92−
48+ const [feeResponse, baseFeeResponse] = await Promise.all([
49+ fetch(
50+ 'https://beaconcha.in/api/v1/execution/gasnow',
51+ {
52+ method: 'get',
53+ cache: 'no-cache',
54+ }
55+ ),
56+ fetch(
57+ `${providerUrl}/v2/api` +
58+ '?module=gastracker' +
59+ '&action=gasoracle' +
60+ '&apikey=CG52E4R96W56GIKUI4IJ8CH9EZIXPUW1W8' +
61+ '&chainid=1',
62+ {
63+ method: 'GET',
64+ cache: 'no-cache',
65+ }
66+ ),
67+ ]);
68+
69+ const [feeJson, baseFeeJson] = await Promise.all([
70+ feeResponse.json(),
71+ baseFeeResponse.json(),
72+ ]);
73+
74+ const suggestedBaseFeePerGas = Number(baseFeeJson.result.suggestBaseFee);
75+
76+ // Calculations derived from:
77+ // https://www.blocknative.com/blog/eip-1559-fees
9378 return {
94− fast: tierFor(baseFeeGwei, fast, 15),
95− average: tierFor(baseFeeGwei, average, 60),
96− low: tierFor(baseFeeGwei, low, 180),
79+ fast: {
80+ price: minGas(weiToGwei(feeJson.data.rapid)),
81+ wait: formatWait(15),
82+ maxFeePerGas: weiToGwei(feeJson.data.rapid),
83+ maxPriorityFeePerGas: feeToInt(
84+ weiToGwei(feeJson.data.rapid) - suggestedBaseFeePerGas
85+ )
86+ },
87+ average: {
88+ price: minGas(weiToGwei(feeJson.data.fast)),
89+ wait: formatWait(60),
90+ maxFeePerGas: weiToGwei(feeJson.data.fast),
91+ maxPriorityFeePerGas: feeToInt(
92+ weiToGwei(feeJson.data.fast) - suggestedBaseFeePerGas
93+ )
94+ },
95+ low: {
96+ price: minGas(weiToGwei(feeJson.data.slow)),
97+ wait: formatWait(180),
98+ maxFeePerGas: weiToGwei(feeJson.data.slow),
99+ maxPriorityFeePerGas: feeToInt(
100+ weiToGwei(feeJson.data.slow) - suggestedBaseFeePerGas
101+ )
102+ },
97103 };
98104 } catch (e) {
99105 console.warn(e);
@@ -101,10 +107,14 @@ const getGasForNetwork = async web3 => {
101107 }
102108};
103109
104−export default async function getSuggestedGasPrice(networkType, web3) {
105− if (networkType === NETWORK_TYPES.OFFLINE || !web3) {
106− return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
110+export default async function getSuggestedGasPrice(networkType) {
111+ switch (networkType) {
112+ case NETWORK_TYPES.GOERLI:
113+ return getGasForNetwork('https://api-goerli.etherscan.io');
114+ case NETWORK_TYPES.OFFLINE:
115+ return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
116+ case NETWORK_TYPES.LOCAL:
117+ default:
118+ return getGasForNetwork('https://api.etherscan.io');
107119 }
108−
109− return getGasForNetwork(web3);
110120}