ERC-4337: Account Abstraction via Entry Point Contract specification

Echoing a comment I made over at EIP idea: eth_signUserOperation RPC - #3 by SamWilsn : I’d really like to see a JSON Schema for all the JSON-RPC requests and responses.

What you have now is much too ambiguous for people to implement consistently.

For example, nothing in the eth_sendUserOperation section says that maxFeePerGas is optional. You have to get down to the note in eth_estimateUserOperationGas where it’s only implied:

same as eth_sendUserOperation gas limits (and prices) parameters are optional

The schema of the rpc calls are here

Can we get those in the EIP itself? Would be a huge improvement for visibility.

For IAccount in the ERC, might it be more accommodating to set the visibility of validateUserOp to public rather than external (shorter, no gas difference, more flexible)?

Can’t: its a linter issue:
Functions in interfaces must be declared external.

ah, I see, but was looking at things like EIP-20 which declare the methods public. I think my suggestion on IAccount may have confused my suggestion (which is that implementation might use public and just to clarify). This was something that came up on a recent Solady 4337 PR.

I believe nonce retrieving and validation logic should not be enshrined in the entry point and that logic should be left as in older reasons, here’s some reasons why I think this should be the case:

  1. Different wallets may want to validate nonces differently, enshrining the logic limits this.
  2. The current enshrined nonce scheme prevents wallets from taking advantage of storage packing and other optimization techniques that may decrease the cost of using and updating the nonce by >4.9k.
  3. If nonces are stored with the entry point and wallets rely on this what happens when the entry point is upgraded? Do all past user ops become re-validated again? Will the new entrypoint have to query the old one adding even more gas?

The original version had the nonce entirely in the account. But this has a drawback: a nonce can’t be enforced to be unique for the account. This also meant that userOp hash couldn’t be considered unique.
To support native account abstraction (e.g. EIP-7560), we must enforce the uniqueness at the protocol level, and not leave it to the account - or add another uniqueness mechanism at the protocol level, besides the account-based nonce.
We decided to introduce a protocol-level nonce (at the EntryPoint), but still make it flexible so the account can control how sequential or parallel it is.

In case of entry point upgrade, the nonce count resets back from zero for each key.
The hashes are still unique, since the EntryPoint address (and chain-id) are hashed into the userOpHash

(Note that unlike the sequential nonce of ethereum, which is basically the total transaction count of an account, with 4337 we have a notion of “key”, and the sequence is only within that key, and the account may introduce new keys.
When an entrypoint is upgraded, the counter for each key has to reset)

1 Like

This only seems to explain reasons why one might want to enshrine nonce invalidation into the entrypoint but not why these points are necessarily more critical than the ones I mentioned above?

1 Like

Hi everyone.

In ERC4337, verificationGasLimit on the official EIP page is explained such as:

The amount of gas to allocate for the verification step

On the following page, it’s said to be:

The VGL refers to the maximum gas available for a User Operation to
execute its validation phase. This includes:

Deploying a smart account on the first transaction. Running the
validateUserOp and validatePaymasterUserOp function on the smart
account and paymaster. Running postOp function on the paymaster after

I thought that verificationGasLimit, whatever is set to, is the total that will be used for all the validation logic + deploying the contract + running paymaster’s pre and post op. Clearly, in the code , that’s not the case. here is why:

  1. deploy contract is run with “verificationGasLimit” - i.e
senderCreator.createSender{gas : opInfo.mUserOp.verificationGasLimit}(initCode);
  1. Then:
try IAccount(sender).validateUserOp{gas : mUserOp.verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds)
  1. Then:
IPaymaster(paymaster).postOp{gas : mUserOp.verificationGasLimit}(

As one can see, the code doesn’t match the explanation. It should be written as follows: If deploy contract code is run, then only pass “verificationGasLimit - gasUsedByDeploy” to validateUserOp. and so on.
I might be completely missing something though.

  1. Is it mandatory for a paymaster to cover complete gas fee for a UserOperation if it is sponsored?
    From the existing code it need to sponsor gas completely

  2. Is it possible to retrieve the amount of funds (gas) covered by the user’s account and the paymaster for an userOperation during the validateUserOps function?
    The intention is to establish validation logic that relies on these values.

The existing signature for validateUserOps includes missingAccountFunds, but it cannot be utilized for this purpose as it returns a value after accounting for the account deposit. One approach to convey this information could involve encoding (account_share, paymaster_share, missingAccountFunds) within a uint256 variable (renaming the variable is necessary for clarity).

This don’t make sense if paymaster pay the the gas fee completely if userOperation is sponsored.

  1. The above is for v0.6 of the entrypoint. While we do pass verificationGasLimit as gas limit for the call, we later further limit the total verification gas usage (including creation) to verificationGasLimit
  2. With version v0.7, we separated the gas-limits, and now verificationGasLimit covers the creation+validation of the account, and a separate paymasterVerificationGasLimit is used to cover the cost of paymaster verification. Without this separation, paymasters become vulnerable.

There is no “split-gas-payment”: either the account pays (in full) for the transaction gas, or the paymaster.
The “max possible UserOp cost” is calculated based on the absolute maximum cost of the transaction - the maxFeePerGas multiplied by the sum of the validation and execution gas limits.
This amount is deducted from the account’s deposit (or paymaster’s) before the UserOperation, and the excess is returned afterwards.
The “missingFunds” is the minimum value the account has to transfer into the EntryPoint so that its deposit is at least “max possible gas”. If there is enough deposit from a previous UserOp, then it might not need to transfer anything.

1 Like

Hello - I wanted to point out what I think might be an incompatibility between UserOps and EIP-3540 (scheduled for Pectra). The summary is available in that EIP’s discussion, but would appreciate if the experts here could take a look.
Thank you!
reference post: EIP-7069: Revamped CALL instructions - #14 by ryley-o

Question on the definition of validateUserOp:

The spec states that

The userOpHash is a hash over the userOp (except signature), entryPoint and chainId.


If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the userOpHash, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert.

So basically it is defined that validateUserOp must simply run Verify(sig, hash, user), where hash = Hash(userop - sig, entrypoint, chainId).

I’d like to understand better why such strong requirements are made here. In particular, this disallows constructs where the userop has some variable part, that is not available to the user at the time of signing and would only later be provided by a third party.

For example, this disallows transaction flows where the validity of a transaction does depend on an additional signature from a paymaster, but the paymaster signature is not available to the user at the time of it signing the transaction.