diff --git a/packages/user-operation-controller/src/UserOperationController.test.ts b/packages/user-operation-controller/src/UserOperationController.test.ts index 1d03bc55c38..f20651f53d9 100644 --- a/packages/user-operation-controller/src/UserOperationController.test.ts +++ b/packages/user-operation-controller/src/UserOperationController.test.ts @@ -871,6 +871,46 @@ describe('UserOperationController', () => { ); }); + it('does not update gas fees nor regenerate if paymaster is set but updated gas fees are zero', async () => { + const controller = new UserOperationController(optionsMock); + + approvalControllerAddRequestMock.mockResolvedValue({ + value: { + txMeta: { + txParams: { + ...ADD_USER_OPERATION_REQUEST_MOCK, + maxFeePerGas: '0x0', + maxPriorityFeePerGas: '0x0', + }, + }, + }, + }); + + const { hash, id } = await addUserOperation( + controller, + ADD_USER_OPERATION_REQUEST_MOCK, + { + ...ADD_USER_OPERATION_OPTIONS_MOCK, + smartContractAccount, + }, + ); + + await hash(); + + expect(controller.state.userOperations[id].userOperation).toStrictEqual( + expect.objectContaining({ + maxFeePerGas: '0x4', + maxPriorityFeePerGas: '0x5', + }), + ); + expect(smartContractAccount.prepareUserOperation).toHaveBeenCalledTimes( + 1, + ); + expect(smartContractAccount.updateUserOperation).toHaveBeenCalledTimes( + 1, + ); + }); + it('regenerates if gas fees updated and paymaster data set', async () => { const controller = new UserOperationController(optionsMock); diff --git a/packages/user-operation-controller/src/UserOperationController.ts b/packages/user-operation-controller/src/UserOperationController.ts index 5e82673ff72..af36c5393c2 100644 --- a/packages/user-operation-controller/src/UserOperationController.ts +++ b/packages/user-operation-controller/src/UserOperationController.ts @@ -770,10 +770,21 @@ export class UserOperationController extends BaseController< const previousMaxFeePerGas = userOperation.maxFeePerGas; const previousMaxPriorityFeePerGas = userOperation.maxPriorityFeePerGas; - if ( + const gasFeesUpdated = previousMaxFeePerGas !== updatedMaxFeePerGas || - previousMaxPriorityFeePerGas !== updatedMaxPriorityFeePerGas - ) { + previousMaxPriorityFeePerGas !== updatedMaxPriorityFeePerGas; + + /** + * true when we detect {@link getTransactionMetadata} has set the gas fees to zero + * because the userOperation has a paymaster. This should not be mistaken for gas + * fees being updated during the approval process. + */ + const areGasFeesZeroBecauseOfPaymaster = + usingPaymaster && + updatedMaxFeePerGas === VALUE_ZERO && + updatedMaxPriorityFeePerGas === VALUE_ZERO; + + if (gasFeesUpdated && !areGasFeesZeroBecauseOfPaymaster) { log('Gas fees updated during approval', { previousMaxFeePerGas, previousMaxPriorityFeePerGas,