Best Practice Guide on How to implement User Operation Retries
Why do I need retries?
Proper wait and retry mechanisms are imperative for any production dApp to ensure all UOs land effectively. Due to network market fluctuations or unexpected congestion, the time required for UOs to mine can fluctuate, similar to normal transactions.
Why is my UO taking so long to land?
It’s very common for fee markets to fluctuate quickly, especially on testnets. When this happens, UOs in the mempool can become underpriced and take longer than expected to mine.
How to resolve?
Using SDK
1 ) Wait for UOs to land before sending another from the same user
a. Simplify with React Hooks - combine sending and waiting.
Use our React hooks to streamline your code. Use the useSendUserOperation
hook with await and retry (waitForTxn flag
= true) to wait for UOs to land and attempt to dropAndReplace
. Ensure to catch errors where the wait method times out - see #2.
b. Without React Hooks - combine sending and waiting for UOs
sendTransaction or sendTransactions methods combine sending and waiting for the user operation in one call. Ensure to catch errors where the wait method times out - see #2.
c. Send and Wait for User Operations Separately
If you prefer to send and then wait, use the waitForUserOperationTransaction method. This continuously polls for the user operation receipt to wait for the UO to be confirmed on-chain. Retry mechanisms are defined on the client config (see #2)
Ensure to catch errors if this method times out - you can drop and replace the previous user operation or continue waiting.
2) Increase wait time
On the smart account client
opts
params, you can customize the retry configurations to your needs -txMaxRetries
,txRetryIntervalMs
,txRetryMultiplier
.All client wait functionality will use these configurations to attempt to get the UO receipt and this method will throw an error if no receipt is found after the retry period.
It’s still possible for wait methods to error out and you should catch these errors. When this happens you should either continue to wait or retry.
Retry sending using drop and replace
If a UO has not landed after defined wait times (i.e. wait methods error out), you can retry sending the UO with higher fees - this will require another user signature.
Use the dropAndReplaceUserOperation endpoint. This endpoint automatically applies the required 10% increase on
maxPriorityFeePerGas
andmaxFeePerGas
. Alternatively, you can submit a new UO with the same 10% fee increase.
Handle Fee Market Fluctuations using multipliers
If your user operations are not being confirmed on-chain for an extended period, this could be due to fee market fluctuations.
We recommend adding overrides (multiplier) to your gas estimates to better handle these fluctuations.
Using API
1) Wait for UOs to land before sending another from the same user
Use the
eth_getUserOperationReceipt
endpoint to check for the UO landing on chain. Do not attempt to send another UO until the previous one has successfully been mined and the receipt is returned.
2) Retry sending using drop and replace
userOps can become "stuck" in the mempool if their gas fee limits are too low to be included in a bundle. You may want to force drop and replace UOs after some time.
Re-estimate gas fees: Re-estimate the gas fees required for your operation. This can be done in various ways which are mentioned below:
Use the
eth_maxPriorityFeePerGas
method to obtainmaxPriorityFeePerGas
.
Choose the suitable increase values: Once you have the re-estimated gas fees, choose the maximum of a 10% increase or the re-estimated values. This ensures that your new gas fee is competitively priced to be included in a block.
Account for Rundler's service tip: Rundler requires a small tip for its services via
maxPriorityFeePerGas
. Detailed information about this can be found on the Bundler API Fee Logic page, but in summary:On Arbitrum, the recommended tip is a minimum 5% of the estimated network base fee when the bundle is mined.
On all other mainnets Alchemy supports, the recommended tip is at least 25% higher than the estimated priority fee on the network.
No tip is required on testnets.
After calculating the new values, send your
userOp
again with the updated fees and it should go through successfully.
3) Handle fee market fluctuations using multipliers
When calling
alchemy_requestGasAndPaymasterAndData
, use theoverrides
param to applymultipliers
on top of gas and fee estimates. This will support getting UOs prioritized in the mempool and handle possible market fluctuations better.
Common scenario without proper mechanisms in place
Below is a common scenario customers run in to at scale due to not implementing recommended wait and retry mechanisms. It may lead to multiple downstream affects.
Scenario
1 UO sent to the bundler and accepted, however, market fees fluctuated and the UO becomes underpriced
That UO is now pending for a long period of time in the mempool because it is underpriced and in a "stuck" state
Without error catching for this, you continue to request gas manager signatures and attempt to send UOs
Paymaster signatures will be granted successfully, BUT bundler will reject the UO because there is already an existing userOp in the mempool for the sender
This leads to 2 issues
Since paymaster signatures are valid and contribute to the pending amount, you may hit your gas manager limits.
These signatures are valid for the expiry set on the policy itself and they cannot be invalidated until timeout
You will experience replacement underpriced errors until the first UO is either landed or forcibly dropped
To avoid these issues we highly recommend you implement the practices above.