# EIP-7702 support in ERC-4337 bundlers
## Abstract
EIP-7702 allows EOAs to behave as smart contracts by issuing an authorization list which is then included in the new transaction type. This proposal defines one way to integrate features introduced by EIP-7702 with existing ERC-4337 infrastructure as seemlessly as possible.
## Motivation
In EIP-7702, an EOA must either propagate an authorization itself or rely on a delegate to sponsor the authorization transaction. Only after sending this initial authorization transaction can the EOA send user operations.
Rather than building new infrastructure for delegations or requiring users to send authorizations themselves, we can modify the existing workflow to include the authorization along with the user operation sent to the bundler.
This approach enables EOAs to leverage the existing ERC-4337 infrastructure (bundler + paymaster), allowing them to send both authorizations and user operations simultaneously.
## Specification
### eth_estimateUserOperationGas
New `authorizationContract` (address) field is added in the first param of eth_estimateUserOperation
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_estimateUserOperationGas",
"params": [
{
sender, // address
nonce, // uint256
factory, // address
factoryData, // bytes
callData, // bytes
paymaster, // address
paymasterData, // bytes
signature, // bytes
authorizationContract // address
},
entryPoint // address
]
}
```
The bundler:
- MUST fetch the code of the contract at `authorizationContract` address and override the code of `sender` with that code during simulation
- CAN add additional `PER_EMPTY_ACCOUNT_COST` + `PER_AUTH_BASE_COST` = 37500 gas in preVerificationGas
### eth_sendUserOperation
1 new tuple (json array) is added:
- authorization: [chainId, contract, nonce, signature]
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendUserOperation",
"params": [
{
sender, // address
nonce, // uint256
factory, // address
factoryData, // bytes
callData, // bytes
callGasLimit, // uint256
verificationGasLimit, // uint256
preVerificationGas, // uint256
maxFeePerGas, // uint256
maxPriorityFeePerGas, // uint256
paymaster, // address
paymasterVerificationGasLimit, // uint256
paymasterPostOpGasLimit, // uint256
paymasterData, // bytes
signature, // bytes
authorization, // [uint64, address, uint256, bytes]
},
entryPoint // address
]
}
```
The bundler:
- MUST validate that nonce of userop.sender matches with authorization.nonce
- MUST validate the authorization signature before putting userop into mempool
- MUST add authorization from each userops in `authorization_list` of the bundle
- MUST check that if authorization is present, factoryData should be null
### Implications for smart contract wallets
*UPD 19.11.2024 - Reference implementation of ERC-7579 now has EIP-7702 validator.*
Initial authorization userop may or may not work depending wallet implementation. For example, if we take ERC-7579's reference implementation of a wallet, the authorization userop will revert because it won't pass `validateUserOp`'s validation.
https://github.com/erc7579/erc7579-implementation/blob/main/src/MSABasic.sol#L153-L179
```solidity=
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) {
// ...
if (!_isValidatorInstalled(validator)) return VALIDATION_FAILED;
validSignature = IValidator(validator).validateUserOp(userOp, userOpHash);
}
```
`validateUserOp` checks if the validator specified in the nonce is installed. It's not installed at the time of authorization - the wallet's storage is empty.
One way to address this is to create a EIP-7702 validator and embed it in the wallet:
```solidity
contract EIP7702Validator is IValidator {
// ...
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external override returns (uint256) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
address signer = hash.recover(userOp.signature);
// checks if the signature was issued by EOA itself
// msg.sender = userop.sender
if (signer != msg.sender) {
return VALIDATION_FAILED;
}
return VALIDATION_SUCCESS;
}
// ...
}
```
And embed this validator in the wallet:
```solidity
contract MSABasic is IMSA, ExecutionHelper, ModuleManager {
// eip-7702 validator hardcoded in the wallet
address public immutable eip7702Validator = address(new EIP7702Validator());
// ...
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
)
external
payable
virtual
override
onlyEntryPoint
payPrefund(missingAccountFunds)
returns (uint256 validSignature)
{
// ...
uint256 nonce = userOp.nonce;
assembly {
validator := shr(96, nonce)
}
// if validator is not set, use EIP-7702 validator
if (validator == address(0)) {
return IValidator(eip7702Validator).validateUserOp(userOp, userOpHash);
}
}
}
```