diff --git a/src/utils/TransactionUtils.ts b/src/utils/TransactionUtils.ts index bc6c81043e..bde0b6a0d0 100644 --- a/src/utils/TransactionUtils.ts +++ b/src/utils/TransactionUtils.ts @@ -9,6 +9,7 @@ import { bnZero, Contract, isDefined, + fixedPointAdjustment, TransactionResponse, ethers, getContractInfoFromAddress, @@ -53,6 +54,8 @@ export async function getMultisender(chainId: number, baseSigner: Signer): Promi return sdkUtils.getMulticall3(chainId, baseSigner); } +const DEFAULT_RETRIES = 2; + // Note that this function will throw if the call to the contract on method for given args reverts. Implementers // of this method should be considerate of this and catch the response to deal with the error accordingly. export async function runTransaction( @@ -63,7 +66,8 @@ export async function runTransaction( value = bnZero, gasLimit: BigNumber | null = null, nonce: number | null = null, - retriesRemaining = 2 + retriesRemaining = DEFAULT_RETRIES, + bumpGas = false ): Promise { const { provider } = contract; const { chainId } = await provider.getNetwork(); @@ -88,6 +92,16 @@ export async function runTransaction( await contract.populateTransaction[method](...(args as Array), { value }) ); + // Bump the priority fee incrementally on each retry to try to successfully replace a pending transaction. + // Success is not guaranteed since the bot does not know the gas price of the transaction it is trying to replace. + if (bumpGas && gas.maxPriorityFeePerGas) { + const priorityScaler = 1.1 + (1 + DEFAULT_RETRIES - Math.min(retriesRemaining, DEFAULT_RETRIES)) / 10; + const oldPriorityFee = gas.maxPriorityFeePerGas; + const newPriorityFee = oldPriorityFee.mul(toBNWei(priorityScaler.toString()).div(fixedPointAdjustment)); + gas.maxFeePerGas = gas.maxFeePerGas.add(newPriorityFee).sub(oldPriorityFee); + gas.maxPriorityFeePerGas = newPriorityFee; + } + logger.debug({ at: "TxUtil", message: "Send tx", @@ -118,7 +132,8 @@ export async function runTransaction( retriesRemaining, }); - return await runTransaction(logger, contract, method, args, value, gasLimit, null, retriesRemaining); + const bumpGas = isEthersError(error) && error.message.toLowerCase().includes("underpriced"); + return await runTransaction(logger, contract, method, args, value, gasLimit, null, retriesRemaining, bumpGas); } else { // Empirically we have observed that Ethers can produce nested errors, so we try to recurse down them // and log them as clearly as possible. For example: