Summary
eth_estimateUserOperationGas appears to support userOp.eip7702Auth in the execution layer, but its request DTO does not declare or validate that field. In contrast, eth_sendUserOperation has an eip7702Auth DTO with nested validation.
This creates a schema drift between the send and estimate RPC paths: callers can pass an EIP-7702 authorization object to estimation, the executor will use it, but malformed or incomplete authorization fields are not rejected by the same request validation layer that protects the send path.
Affected code
packages/api/src/dto/SendUserOperation.dto.ts defines and validates eip7702Auth:
export class Eip7702Auth {
@IsBigNumberish()
chainId!: BigNumberish;
@IsBigNumberish()
nonce!: BigNumberish;
@IsEthereumAddress()
address!: Hex;
@IsString()
r!: Hex;
@IsString()
s!: Hex;
yParity!: "0x0" | "0x1";
}
export class SendUserOperation {
...
/**
* Eip-7702 property
*/
@IsOptional()
@ValidateNested()
@Type(() => Eip7702Auth)
eip7702Auth?: Eip7702Auth;
}
But packages/api/src/dto/EstimateUserOperation.dto.ts does not include this field:
export class EstimateUserOperation {
/**
* Common Properties
*/
@IsEthereumAddress()
sender!: Hex;
@IsBigNumberish()
nonce!: BigNumberish;
@IsString()
callData!: Hex;
@IsString()
signature!: Hex;
/**
* EntryPoint v7 Properties
*/
@IsValidFactory()
@IsOptional()
factory?: Hex;
@IsString()
@IsOptional()
factoryData?: Hex;
@IsEthereumAddress()
@IsOptional()
paymaster?: Hex;
@IsBigNumberish()
@IsOptional()
paymasterVerificationGasLimit?: BigNumberish;
@IsBigNumberish()
@IsOptional()
paymasterPostOpGasLimit?: BigNumberish;
@IsString()
@IsOptional()
paymasterData?: Hex;
}
The API layer does validate estimation requests with that DTO:
@RpcMethodValidator(EstimateUserOperationGasArgs)
async estimateUserOperationGas(
args: EstimateUserOperationGasArgs
): Promise<EstimatedUserOperationGas> {
return await this.ethModule.estimateUserOperationGas(args);
}
However, the executor later reads userOp.eip7702Auth in the estimation path and uses its fields to build an authorizationList:
if (userOp.eip7702Auth && !this.config.eip7702) {
throw new RpcError(
"EIP7702 is not supported in this network",
RpcErrorCodes.INVALID_USEROP
);
}
...
if (userOp.eip7702Auth) {
ethEstimateGas = await this.publicClient
.estimateGas({
account: entryPoint as `0x${string}`,
to: userOp.sender as `0x${string}`,
data: userOp.callData as `0x${string}`,
authorizationList: [
{
address: userOp.eip7702Auth.address,
chainId: Number(BigInt(userOp.eip7702Auth.chainId)),
nonce: Number(BigInt(userOp.eip7702Auth.nonce)),
r: userOp.eip7702Auth.r
.toString()
.replace(/^0x0+(?=\d)/, "0x") as `0x${string}`,
s: userOp.eip7702Auth.s
.toString()
.replace(/^0x0+(?=\d)/, "0x") as `0x${string}`,
yParity: userOp.eip7702Auth.yParity === "0x0" ? 0 : 1,
},
],
})
I also only found the DTO declaration in the send path during a quick test search:
packages/api/src/dto/SendUserOperation.dto.ts:86: eip7702Auth?: Eip7702Auth;
Why this matters
For EIP-7702 UserOperations, estimation and submission generally need to agree on the same authorization semantics. If eth_estimateUserOperationGas accepts an eip7702Auth object but does not validate its nested fields, malformed estimation requests can fail later with conversion/runtime errors such as BigInt(...)/.toString() failures, or they may be handled differently from eth_sendUserOperation.
This is not a fund-loss report. It is an RPC/API consistency and hardening issue that can make EIP-7702 integration debugging confusing.
Suggested fix
Consider reusing the same Eip7702Auth DTO in EstimateUserOperation:
export class EstimateUserOperation {
...
@IsOptional()
@ValidateNested()
@Type(() => Eip7702Auth)
eip7702Auth?: Eip7702Auth;
}
It may also be worth validating yParity explicitly, since it currently has a TypeScript literal type but no runtime validator.
Useful regression tests:
eth_estimateUserOperationGas with a well-formed eip7702Auth passes request validation and reaches the EIP-7702 estimation branch.
eth_estimateUserOperationGas with an incomplete/malformed eip7702Auth returns an INVALID_REQUEST-style validation error rather than a later runtime/internal error.
Summary
eth_estimateUserOperationGasappears to supportuserOp.eip7702Authin the execution layer, but its request DTO does not declare or validate that field. In contrast,eth_sendUserOperationhas aneip7702AuthDTO with nested validation.This creates a schema drift between the send and estimate RPC paths: callers can pass an EIP-7702 authorization object to estimation, the executor will use it, but malformed or incomplete authorization fields are not rejected by the same request validation layer that protects the send path.
Affected code
packages/api/src/dto/SendUserOperation.dto.tsdefines and validateseip7702Auth:But
packages/api/src/dto/EstimateUserOperation.dto.tsdoes not include this field:The API layer does validate estimation requests with that DTO:
However, the executor later reads
userOp.eip7702Authin the estimation path and uses its fields to build anauthorizationList:I also only found the DTO declaration in the send path during a quick test search:
Why this matters
For EIP-7702 UserOperations, estimation and submission generally need to agree on the same authorization semantics. If
eth_estimateUserOperationGasaccepts aneip7702Authobject but does not validate its nested fields, malformed estimation requests can fail later with conversion/runtime errors such asBigInt(...)/.toString()failures, or they may be handled differently frometh_sendUserOperation.This is not a fund-loss report. It is an RPC/API consistency and hardening issue that can make EIP-7702 integration debugging confusing.
Suggested fix
Consider reusing the same
Eip7702AuthDTO inEstimateUserOperation:It may also be worth validating
yParityexplicitly, since it currently has a TypeScript literal type but no runtime validator.Useful regression tests:
eth_estimateUserOperationGaswith a well-formedeip7702Authpasses request validation and reaches the EIP-7702 estimation branch.eth_estimateUserOperationGaswith an incomplete/malformedeip7702Authreturns anINVALID_REQUEST-style validation error rather than a later runtime/internal error.