# 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); } } } ```