import { BigNumber, ethers, PopulatedTransaction } from 'ethers'; import { TransactionManagerRequest } from '../transaction-inputs/transaction-manager-request'; import { AlchemyApiConfig, TenderlyApiConfig } from '../types'; import { GasLimitEstimate } from './gas-limit'; // create a call to handle gas related operations export class GasOperations { private provider: ethers.providers.Provider; private tenderlyConfig: TenderlyApiConfig; private alchemyConfig: AlchemyApiConfig; constructor( provider: ethers.providers.Provider, tenderlyConfig: TenderlyApiConfig, alchemyConfig: AlchemyApiConfig ) { this.provider = provider; this.tenderlyConfig = tenderlyConfig; this.alchemyConfig = alchemyConfig; } // update gas price /** * * @param gasPrice Current gas price on optimizer * @returns */ public async increaseGasPrice(gasPrice: BigNumber): Promise { // get gas price const recommendedGasPrice: BigNumber = await this.provider.getGasPrice(); // increase gas price by 10% const updatedGasPrice = gasPrice.add(recommendedGasPrice.div(10)); // get gas price return updatedGasPrice; } private removeOutliers( numbers: BigNumber[], outlierThreshold: BigNumber, ): BigNumber[] { const sorted = numbers.sort((a, b) => a.sub(b).toNumber()); const median = sorted[Math.floor(sorted.length / 2)]; const iqr = median.mul(15).div(10); const lowerBound = median.sub(iqr); const upperBound = median.add(iqr); return sorted.filter((number) => { return ( number.gte(lowerBound) && number.lte(upperBound) && number.gt(outlierThreshold) ); }); } private async manuallyEstimateGasLimit() { const blockNumber = await this.provider.getBlockNumber(); const transactionsGasLimit: BigNumber[] = []; // loop through last 10 blocks for (let i = 0; i < 5; i++) { const block = await this.provider.getBlock(blockNumber - i); // check if block has transactions if (block.transactions.length > 0) { const transactionHash = block.transactions[0]; // get transaction const transaction = await this.provider.getTransaction(transactionHash); // check if transaction has gas limit if (transaction.gasLimit) { transactionsGasLimit.push(transaction.gasLimit); } } } // remove outliers const filteredTransactionsGasLimit = this.removeOutliers( transactionsGasLimit, BigNumber.from('0'), ); // calculate avg of gas limit const avgGasLimit = filteredTransactionsGasLimit .reduce((a, b) => a.add(b), BigNumber.from('0')) .div(filteredTransactionsGasLimit.length); return avgGasLimit; } private async estimateGasWithoutGasParams( transaction: PopulatedTransaction, ): Promise { const newTransaction: PopulatedTransaction = { ...transaction, }; if (transaction.gasPrice) { delete newTransaction['gasPrice']; } if (transaction.maxFeePerGas) { delete newTransaction['maxFeePerGas']; } if (transaction.maxPriorityFeePerGas) { delete newTransaction['maxPriorityFeePerGas']; } const estimatedGas = await this.provider.estimateGas(newTransaction); return estimatedGas; } // get gas estimate private async estimateGasLimit( transaction: PopulatedTransaction, gasLimit: BigNumber, incrementGasLimit: boolean, ): Promise<{ updatedGasLimit: BigNumber; estimatedGas: BigNumber; }> { // get gas limit let estimatedGas = BigNumber.from('0'); try { estimatedGas = await this.provider.estimateGas(transaction); } catch (error) { // if its goerli network estimatedGas = await this.estimateGasWithoutGasParams(transaction); } if (estimatedGas.isZero()) { process.env.DEBUG == 'true' && console.log( 'Error estimating gas limit. Using manually estimated gas limit', ); estimatedGas = await this.manuallyEstimateGasLimit(); } let updatedGasLimit = BigNumber.from('0'); if (estimatedGas.gt(gasLimit)) { // increasing gas limit by 10% from estimated gas updatedGasLimit = estimatedGas.mul(110).div(100); } else { // only in case of gas errors increase gas limit by 10% if (incrementGasLimit) { process.env.DEBUG == 'true' && console.log('Increasing gas limit by 10%'); updatedGasLimit = gasLimit.mul(110).div(100); } else { updatedGasLimit = gasLimit; } } return {updatedGasLimit, estimatedGas}; } // update gas limit public async increaseGasLimit( transaction: PopulatedTransaction, incrementGasLimit: boolean, transactionRequest: TransactionManagerRequest, gasMultiplier: number, defaultGasMultiplier?: number, ): Promise<{ gasLimit: BigNumber; simulatedGasLimit: BigNumber; }> { const gasLimit = transaction.gasLimit || BigNumber.from('0'); const {updatedGasLimit, estimatedGas } = await this.estimateGasLimit( transaction, gasLimit, incrementGasLimit, ); transaction.gasLimit = updatedGasLimit; const gasLimitEstimate = new GasLimitEstimate( this.provider, transaction, transactionRequest, gasMultiplier, this.tenderlyConfig, this.alchemyConfig, defaultGasMultiplier ); const gasEstimate = await gasLimitEstimate.getEstimate(estimatedGas); process.env.DEBUG == 'true' && console.log('Final gaslimit to be placed : ', gasEstimate); return gasEstimate; } }