Account
Smart contract account utilities and implementations
This directory includes contracts to build accounts for ERC-4337. These include:
Account: An ERC-4337 smart account implementation that includes the core logic to process user operations.AccountERC7579: An extension ofAccountthat implements support for ERC-7579 modules.AccountERC7579Hooked: An extension ofAccountERC7579with support for a single hook module (type 4).ERC7821: Minimal batch executor implementation contracts. Useful to enable easy batch execution for smart contracts.ERC4337Utils: Utility functions for working with ERC-4337 user operations.ERC7579Utils: Utility functions for working with ERC-7579 modules and account modularity.Paymaster: An ERC-4337 paymaster implementation that includes the core logic to validate and pay for user operations.PaymasterERC20: A paymaster that allows users to pay for user operations using ERC-20 tokens.PaymasterERC20Guarantor: A paymaster that enables third parties to guarantee user operations by pre-funding gas costs, with the option for users to repay or for guarantors to absorb the cost.PaymasterERC721Owner: A paymaster that sponsors user operations for ERC-721 token holders, covering gas costs based on NFT ownership.PaymasterSigner: A paymaster that sponsors user operations authorized by a signature.
Core
Extensions
Paymasters
Utilities
import "@openzeppelin/contracts/account/Account.sol";A simple ERC4337 account implementation. This base implementation only includes the minimal logic to process user operations.
Developers must implement the AbstractSigner._rawSignatureValidation function to define the account's validation logic.
This core account doesn't include any mechanism for performing arbitrary external calls. This is an essential feature that all Accounts should have. We leave it up to the developers to implement the mechanism of their choice. Common choices include ERC-6900, ERC-7579 and ERC-7821 (among others).
Implementing a mechanism to validate signatures is a security-sensitive operation as it may allow an
attacker to bypass the account's security measures. Check out SignerECDSA, SignerP256, or SignerRSA for
digital signature validation implementations.
@custom:stateless
Modifiers
Functions
Errors
onlyEntryPointOrSelf()
internal
#Revert if the caller is not the entry point or the account itself.
onlyEntryPoint()
internal
#Revert if the caller is not the entry point.
entryPoint() → contract IEntryPoint
public
#Canonical entry point for the account that forwards and validates user operations.
getNonce() → uint256
public
#Return the account nonce for the canonical sequence.
getNonce(uint192 key) → uint256
public
#Return the account nonce for a given sequence (key).
validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 missingAccountFunds) → uint256
public
#Validates a user operation.
- MUST validate the caller is a trusted EntryPoint
- MUST validate that 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.
- MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might be zero, in case the current account’s deposit is high enough)
Returns an encoded packed validation data that is composed of the following elements:
authorizer(address): 0 for success, 1 for failure, otherwise the address of an authorizer contractvalidUntil(uint48): The UserOp is valid only up to this time. Zero for “infinite”.validAfter(uint48): The UserOp is valid only after this time.
_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, bytes signature) → uint256
internal
#Returns the validationData for a given user operation. By default, this checks the signature of the
signable hash (produced by Account._signableUserOpHash) using the abstract signer (AbstractSigner._rawSignatureValidation).
The signature parameter is taken directly from the user operation's signature field.
This design enables derived contracts to implement custom signature handling logic,
such as embedding additional data within the signature and processing it by overriding this function
and optionally invoking super.
The userOpHash is assumed to be correct. Calling this function with a userOpHash that does not match the userOp will result in undefined behavior.
_signableUserOpHash(struct PackedUserOperation, bytes32 userOpHash) → bytes32
internal
#Virtual function that returns the signable hash for a user operation. Since v0.8.0 of the entrypoint,
userOpHash is an EIP-712 hash that can be signed directly.
_payPrefund(uint256 missingAccountFunds)
internal
#Sends the missing funds for executing the user operation to the entrypoint.
The missingAccountFunds must be defined by the entrypoint when calling Account.validateUserOp.
_checkEntryPoint()
internal
#Ensures the caller is the entrypoint.
_checkEntryPointOrSelf()
internal
#Ensures the caller is the entrypoint or the account itself.
receive()
external
#Receive Ether.
AccountUnauthorized(address sender)
error
#Unauthorized call to the account.
import "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol";Extension of Account that implements support for ERC-7579 modules.
To comply with the ERC-1271 support requirement, this contract defers signature validation to
installed validator modules by calling IERC7579Validator.isValidSignatureWithSender.
This contract does not implement validation logic for user operations since this functionality is often delegated to self-contained validation modules. Developers must install a validator module upon initialization (or any other mechanism to enable execution from the account):
contract MyAccountERC7579 is AccountERC7579, Initializable {
function initializeAccount(address validator, bytes calldata validatorData) public initializer {
_installModule(MODULE_TYPE_VALIDATOR, validator, validatorData);
}
}- Hook support is not included. See
AccountERC7579Hookedfor a version that hooks to execution. - Validator selection, when verifying either ERC-1271 signature or ERC-4337 UserOperation is implemented in
internal virtual functions
AccountERC7579._extractUserOpValidatorandAccountERC7579._extractSignatureValidator. Both are implemented following common practices. However, this part is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions. - When combined with
ERC7739, resolution ordering ofAccountERC7579.isValidSignaturemay have an impact (ERC7739does not call super). Manual resolution might be necessary. - Static calls (using callType
0xfe) are currently NOT supported.
Removing all validator modules will render the account inoperable, as no user operations can be validated thereafter.
Functions
- fallback()
- accountId()
- supportsExecutionMode(encodedMode)
- supportsModule(moduleTypeId)
- installModule(moduleTypeId, module, initData)
- uninstallModule(moduleTypeId, module, deInitData)
- isModuleInstalled(moduleTypeId, module, additionalContext)
- execute(mode, executionCalldata)
- executeFromExecutor(mode, executionCalldata)
- isValidSignature(hash, signature)
- _validateUserOp(userOp, userOpHash, signature)
- _execute(mode, executionCalldata)
- _installModule(moduleTypeId, module, initData)
- _uninstallModule(moduleTypeId, module, deInitData)
- _fallback()
- _fallbackHandler(selector)
- _checkModule(moduleTypeId, module, additionalContext)
- _extractUserOpValidator(userOp)
- _extractSignatureValidator(signature)
- _decodeFallbackData(data)
- _rawSignatureValidation(, )
Events
IERC7579ModuleConfig
Errors
onlyModule(uint256 moduleTypeId, bytes additionalContext)
internal
#Modifier that checks if the caller is an installed module of the given type.
fallback(bytes) → bytes
external
#accountId() → string
public
#Returns the account id of the smart account
supportsExecutionMode(bytes32 encodedMode) → bool
public
#Supported call types:
- Single (
0x00): A single transaction execution. - Batch (
0x01): A batch of transactions execution. - Delegate (
0xff): A delegate call execution.
Supported exec types:
- Default (
0x00): Default execution type (revert on failure). - Try (
0x01): Try execution type (emits ERC7579TryExecuteFail on failure).
supportsModule(uint256 moduleTypeId) → bool
public
#Supported module types:
- Validator: A module used during the validation phase to determine if a transaction is valid and should be executed on the account.
- Executor: A module that can execute transactions on behalf of the smart account via a callback.
- Fallback Handler: A module that can extend the fallback functionality of a smart account.
installModule(uint256 moduleTypeId, address module, bytes initData)
public
#Installs a Module of a certain type on the smart account
uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)
public
#Uninstalls a Module of a certain type on the smart account
isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) → bool
public
#Returns whether a module is installed on the smart account
execute(bytes32 mode, bytes executionCalldata)
public
#Executes a transaction on behalf of the account.
executeFromExecutor(bytes32 mode, bytes executionCalldata) → bytes[] returnData
public
#Executes a transaction on behalf of the account. This function is intended to be called by Executor Modules
isValidSignature(bytes32 hash, bytes signature) → bytes4
public
#_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, bytes signature) → uint256
internal
#Validates a user operation with Account._signableUserOpHash and returns the validation data
if the module specified by the first 20 bytes of the nonce key is installed. Falls back to
Account._validateUserOp otherwise.
See AccountERC7579._extractUserOpValidator for the module extraction logic.
_execute(Mode mode, bytes executionCalldata) → bytes[] returnData
internal
#ERC-7579 execution logic. See AccountERC7579.supportsExecutionMode for supported modes.
Reverts if the call type is not supported.
_installModule(uint256 moduleTypeId, address module, bytes initData)
internal
#Installs a module of the given type with the given initialization data.
For the fallback module type, the initData is expected to be the (packed) concatenation of a 4-byte
selector and the rest of the data to be sent to the handler when calling IERC7579Module.onInstall.
Requirements:
- Module type must be supported. See
AccountERC7579.supportsModule. Reverts withERC7579Utils.ERC7579UnsupportedModuleType. - Module must be of the given type. Reverts with
ERC7579Utils.ERC7579MismatchedModuleTypeId. - Module must not be already installed. Reverts with
ERC7579Utils.ERC7579AlreadyInstalledModule.
Emits a IERC7579ModuleConfig.ModuleInstalled event.
_uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)
internal
#Uninstalls a module of the given type with the given de-initialization data.
For the fallback module type, the deInitData is expected to be the (packed) concatenation of a 4-byte
selector and the rest of the data to be sent to the handler when calling IERC7579Module.onUninstall.
Requirements:
- Module must be already installed. Reverts with
ERC7579Utils.ERC7579UninstalledModuleotherwise.
_fallback() → bytes
internal
#Fallback function that delegates the call to the installed handler for the given selector.
Reverts with AccountERC7579.ERC7579MissingFallbackHandler if the handler is not installed.
Calls the handler with the original msg.sender appended at the end of the calldata following
the ERC-2771 format.
_fallbackHandler(bytes4 selector) → address
internal
#Returns the fallback handler for the given selector. Returns address(0) if not installed.
_checkModule(uint256 moduleTypeId, address module, bytes additionalContext)
internal
#Checks if the module is installed. Reverts if the module is not installed.
_extractUserOpValidator(struct PackedUserOperation userOp) → address
internal
#Extracts the nonce validator from the user operation.
To construct a nonce key, set nonce as follows:
<module address (20 bytes)> | <key (4 bytes)> | <nonce (8 bytes)>The default behavior of this function replicates the behavior of Safe adapter, Etherspot's Prime Account, and ERC7579 reference implementation.
This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions.
For example, Biconomy's Nexus uses a similar yet incompatible approach (the validator address is also part of the nonce, but not at the same location)
_extractSignatureValidator(bytes signature) → address module, bytes innerSignature
internal
#Extracts the signature validator from the signature.
To construct a signature, set the first 20 bytes as the module address and the remaining bytes as the signature data:
<module address (20 bytes)> | <signature data>The default behavior of this function replicates the behavior of Safe adapter, Biconomy's Nexus, Etherspot's Prime Account, and ERC7579 reference implementation.
This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions.
This function expects the signature to be at least 20 bytes long. Panics with Panic.ARRAY_OUT_OF_BOUNDS (0x32) otherwise.
_decodeFallbackData(bytes data) → bytes4 selector, bytes remaining
internal
#Extract the function selector from initData/deInitData for MODULE_TYPE_FALLBACK
If we had calldata here, we could use calldata slice which are cheaper to manipulate and don't require
actual copy. However, this would require _installModule to get a calldata bytes object instead of a memory
bytes object. This would prevent calling _installModule from a contract constructor and would force the use
of external initializers. That may change in the future, as most accounts will probably be deployed as
clones/proxy/EIP-7702 delegates and therefore rely on initializers anyway.
_rawSignatureValidation(bytes32, bytes) → bool
internal
#By default, only use the modules for validation of userOp and signature. Disable raw signatures.
ERC7579MissingFallbackHandler(bytes4 selector)
error
#The account's AccountERC7579.fallback was called with a selector that doesn't have an installed handler.
ERC7579CannotDecodeFallbackData()
error
#The provided initData/deInitData for a fallback module is too short to extract a selector.
import "@openzeppelin/contracts/account/extensions/draft-AccountERC7579Hooked.sol";Extension of AccountERC7579 with support for a single hook module (type 4).
If installed, this extension will call the hook module's IERC7579Hook.preCheck before executing any operation
with AccountERC7579._execute (including AccountERC7579.execute and AccountERC7579.executeFromExecutor by default) and IERC7579Hook.postCheck thereafter.
Hook modules break the check-effect-interaction pattern. In particular, the IERC7579Hook.preCheck hook can
lead to potentially dangerous reentrancy. Using the withHook() modifier is safe if no effect is performed
before the preHook or after the postHook. That is the case on all functions here, but it may not be the case if
functions that have this modifier are overridden. Developers should be extremely careful when implementing hook
modules or further overriding functions that involve hooks.
Modifiers
Functions
- accountId()
- hook()
- supportsModule(moduleTypeId)
- isModuleInstalled(moduleTypeId, module, data)
- _installModule(moduleTypeId, module, initData)
- _uninstallModule(moduleTypeId, module, deInitData)
- _execute(mode, executionCalldata)
- _fallback()
AccountERC7579
- fallback()
- supportsExecutionMode(encodedMode)
- installModule(moduleTypeId, module, initData)
- uninstallModule(moduleTypeId, module, deInitData)
- execute(mode, executionCalldata)
- executeFromExecutor(mode, executionCalldata)
- isValidSignature(hash, signature)
- _validateUserOp(userOp, userOpHash, signature)
- _fallbackHandler(selector)
- _checkModule(moduleTypeId, module, additionalContext)
- _extractUserOpValidator(userOp)
- _extractSignatureValidator(signature)
- _decodeFallbackData(data)
- _rawSignatureValidation(, )
Events
IERC7579ModuleConfig
Errors
withHook()
internal
#Calls IERC7579Hook.preCheck before executing the modified function and IERC7579Hook.postCheck
thereafter.
accountId() → string
public
#Returns the account id of the smart account
hook() → address
public
#Returns the hook module address if installed, or address(0) otherwise.
supportsModule(uint256 moduleTypeId) → bool
public
#Supports hook modules. See AccountERC7579.supportsModule
isModuleInstalled(uint256 moduleTypeId, address module, bytes data) → bool
public
#Returns whether a module is installed on the smart account
_installModule(uint256 moduleTypeId, address module, bytes initData)
internal
#Installs a module with support for hook modules. See AccountERC7579._installModule
_uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)
internal
#Uninstalls a module with support for hook modules. See AccountERC7579._uninstallModule
_execute(Mode mode, bytes executionCalldata) → bytes[]
internal
#Hooked version of AccountERC7579._execute.
_fallback() → bytes
internal
#Hooked version of AccountERC7579._fallback.
ERC7579HookModuleAlreadyPresent(address hook)
error
#A hook module is already present. This contract only supports one hook module.
import "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol";Minimal batch executor following ERC-7821.
Only supports single batch mode (0x01000000000000000000). Does not support optional "opData".
@custom:stateless
Functions
Errors
execute(bytes32 mode, bytes executionData)
public
#Executes the calls in executionData with no optional opData support.
Access to this function is controlled by ERC7821._erc7821AuthorizedExecutor. Changing access permissions, for
example to approve calls by the ERC-4337 entrypoint, should be implemented by overriding it.
Reverts and bubbles up error if any call fails.
supportsExecutionMode(bytes32 mode) → bool result
public
#This function is provided for frontends to detect support. Only returns true for:
bytes32(0x01000000000000000000...): does not support optionalopData.bytes32(0x01000000000078210001...): supports optionalopData.
_erc7821AuthorizedExecutor(address caller, bytes32, bytes) → bool
internal
#Access control mechanism for the AccountERC7579.execute function.
By default, only the contract itself is allowed to execute.
Override this function to implement custom access control, for example to allow the ERC-4337 entrypoint to execute.
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}UnsupportedExecutionMode()
error
#import "@openzeppelin/contracts/account/paymaster/Paymaster.sol";A simple ERC4337 paymaster implementation. This base implementation only includes the minimal logic to validate and pay for user operations.
Developers must implement the Paymaster._validatePaymasterUserOp function to define the paymaster's validation
and payment logic, and Paymaster._postOp function to define the post-operation logic. The context parameter
is used to pass data between the validation and post execution phases.
The paymaster includes support to call the IEntryPointStake interface to manage the paymaster's deposits and stakes
through the internal functions Paymaster._deposit, Paymaster._withdraw, Paymaster._addStake, Paymaster._unlockStake and Paymaster._withdrawStake.
- Deposits are used to pay for user operations.
- Stakes are used to guarantee the paymaster's reputation and obtain more flexibility in accessing storage.
The deposit and stake functions are internal so that developers can expose them under the public interface and
authorization mechanism of their choice. Public versions of Paymaster._withdraw, Paymaster._unlockStake and Paymaster._withdrawStake MUST
be exposed and properly authorized, otherwise the deposit and stake will be permanently locked.
Example implementation exposing the deposit and stake functions using AccessControl:
contract MyPaymaster is Paymaster, AccessControl {
bytes32 private constant WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE");
bytes32 private constant UNSTAKER_ROLE = keccak256("UNSTAKER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function deposit() public payable virtual {
_deposit(msg.value);
}
function withdraw(address payable to, uint256 value) public virtual onlyRole(WITHDRAWER_ROLE) {
_withdraw(to, value);
}
function addStake(uint32 unstakeDelaySec) public payable virtual {
_addStake(msg.value, unstakeDelaySec);
}
function unlockStake() public virtual onlyRole(UNSTAKER_ROLE) {
_unlockStake();
}
function withdrawStake(address payable to) public virtual onlyRole(UNSTAKER_ROLE) {
_withdrawStake(to);
}
function _validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 requiredPreFund
) internal virtual override returns (bytes memory context, uint256 validationData) {
// validation logic
}
}See Paymaster's unstaked reputation rules for more details on the paymaster's storage access limitations.
Modifiers
Functions
- entryPoint()
- validatePaymasterUserOp(userOp, userOpHash, maxCost)
- postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
- _validatePaymasterUserOp(userOp, userOpHash, requiredPreFund)
- _postOp(, , , )
- _checkEntryPoint()
- _deposit(value)
- _withdraw(to, value)
- _addStake(value, unstakeDelaySec)
- _unlockStake()
- _withdrawStake(to)
onlyEntryPoint()
internal
#Revert if the caller is not the entry point.
entryPoint() → contract IEntryPoint
public
#Canonical entry point for the account that forwards and validates user operations.
validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) → bytes context, uint256 validationData
public
#Validates whether the paymaster is willing to pay for the user operation. See
IAccount.validateUserOp for additional information on the return value.
Bundlers will reject this method if it modifies the state, unless it's whitelisted.
postOp(enum IPaymaster.PostOpMode mode, bytes context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)
public
#Verifies the sender is the entrypoint.
_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 requiredPreFund) → bytes context, uint256 validationData
internal
#Internal validation of whether the paymaster is willing to pay for the user operation. Returns the context to be passed to postOp and the validation data.
The requiredPreFund is the amount the paymaster has to pay (in native tokens). It's calculated
as requiredGas * userOp.maxFeePerGas, where required gas can be calculated from the user operation
as verificationGasLimit + callGasLimit + paymasterVerificationGasLimit + paymasterPostOpGasLimit + preVerificationGas
_postOp(enum IPaymaster.PostOpMode, bytes, uint256, uint256)
internal
#Handles post user operation execution logic. The caller must be the entry point.
It receives the context returned by _validatePaymasterUserOp. Function is not called if no context
is returned by Paymaster.validatePaymasterUserOp.
The actualUserOpFeePerGas is not tx.gasprice. A user operation can be bundled with other transactions
making the gas price of the user operation to differ.
_checkEntryPoint()
internal
#Ensures the caller is the entrypoint.
_deposit(uint256 value)
internal
#Calls IEntryPointStake.depositTo.
_withdraw(address payable to, uint256 value)
internal
#Calls IEntryPointStake.withdrawTo.
_addStake(uint256 value, uint32 unstakeDelaySec)
internal
#Calls IEntryPointStake.addStake.
_unlockStake()
internal
#Calls IEntryPointStake.unlockStake.
_withdrawStake(address payable to)
internal
#PaymasterUnauthorized(address sender)
error
#Unauthorized call to the paymaster.
import "@openzeppelin/contracts/account/paymaster/extensions/PaymasterERC20.sol";Extension of Paymaster that enables users to pay gas with ERC-20 tokens.
To enable this feature, developers must implement the PaymasterERC20._fetchDetails function:
function _fetchDetails(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal view override returns (uint256 validationData, IERC20 token, uint256 tokenPerNative) {
// Implement logic to fetch the token, and token price from the userOp
}The contract follows a pre-charge and refund model:
- During validation, it pre-charges the maximum possible gas cost
- After execution, it refunds any unused gas back to the user
PaymasterERC20._prefund performs a transferFrom during the validation phase, writing to the token contract's storage.
ERC-7562 restricts unstaked paymasters from such accesses, and public mempool bundlers will reject these operations.
Stake the paymaster (see Paymaster._addStake) when deploying against a public mempool.
The PaymasterERC20._withdrawTokens function is internal so that developers can expose it under the public interface and
authorization mechanism of their choice. Public versions of PaymasterERC20._withdrawTokens MUST be exposed and properly authorized,
otherwise the tokens will be permanently stuck in the paymaster.
Example implementation exposing the PaymasterERC20._withdrawTokens function using AccessControl:
contract MyPaymaster is Paymaster, AccessControl {
bytes32 private constant WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function withdrawTokens(IERC20 token, address recipient, uint256 amount) public virtual onlyRole(WITHDRAWER_ROLE) {
_withdrawTokens(token, recipient, amount);
}
...
}Functions
- _validatePaymasterUserOp(userOp, userOpHash, maxCost)
- prefund(, , token, , prefunder, prefundAmount_)
- _postOp(, context, actualGasCost, actualUserOpFeePerGas)
- refund(token, , actualAmount, , prefunder, prefundAmount, )
- _fetchDetails(userOp, userOpHash)
- _postOpCost()
- _tokenPerNativeDenominator()
- _minTokensPerNative()
- _erc20Cost(nativeCost, tokenPerNative)
- _withdrawTokens(token, recipient, amount)
Errors
_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) → bytes context, uint256 validationData
internal
#See Paymaster._validatePaymasterUserOp.
Attempts to retrieve the token and tokenPerNative from the user operation (see PaymasterERC20._fetchDetails)
and prefund the user operation using these values and the maxCost argument (see PaymasterERC20._prefund).
Returns abi.encodePacked(userOpHash, token, tokenPerNative, prefundAmount, prefunder, prefundContext) in
context if the prefund is successful. Otherwise, it returns empty bytes.
prefund(struct PackedUserOperation, bytes32, contract IERC20 token, uint256, address prefunder, uint256 prefundAmount_) → bool success, address prefunder, uint256 prefundAmount, bytes prefundContext
internal
#Charges prefundAmount of token from prefunder_ and returns the effective prefund actually pulled.
The base implementation pulls exactly the requested prefundAmount. Extensions may inflate the amount
(e.g. a guarantor adds the cost of the extra postOp work it performs) and must return the effective value.
Returns (success, prefunder, effectivePrefundAmount, prefundContext). prefundContext is forwarded to
Paymaster._postOp through its context argument and may be used by overrides to carry data into PaymasterERC20._refund.
Consider not reverting if the prefund fails when overriding this function. This is to avoid reverting during the validation phase of the user operation, which may penalize the paymaster's reputation according to ERC-7562 validation rules.
_postOp(enum IPaymaster.PostOpMode, bytes context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)
internal
#Attempts to refund the user operation after execution. See PaymasterERC20._refund.
Reverts with PaymasterERC20.PaymasterERC20FailedRefund if the refund fails.
This function may revert after the user operation has been executed without reverting the user operation itself. Consider implementing a mechanism to handle this case gracefully.
refund(contract IERC20 token, uint256, uint256 actualAmount, uint256, address prefunder, uint256 prefundAmount, bytes) → bool success, uint256 actualAmount
internal
#Refunds prefundAmount - actualAmount of token back to prefunder and returns the
actualAmount actually charged.
actualAmount is pre-computed by Paymaster._postOp via PaymasterERC20._erc20Cost. Extensions may change it (e.g. a
guarantor adds its extra postOp cost or zeroes it out after pulling from the user) and must
return the value that was effectively charged.
Requirements:
actualAmount <= prefundAmount.
_fetchDetails(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256 validationData, contract IERC20 token, uint256 tokenPerNative
internal
#Retrieves payment details for a user operation.
The values returned by this internal function are:
validationData: ERC-4337 validation data, indicating success/failure and optional time validity (validAfter,validUntil).token: Address of the ERC-20 token used for payment to the paymaster.tokenPerNative: Token units charged per unit of native currency. This is scaled by_tokenPerNativeDenominator()which defaults to 1e18 (wei per eth), making it effectively a number of token units per eth, and not per wei.
Calculating the token price
tokenPerNative is the multiplier PaymasterERC20._erc20Cost applies to a native-currency gas cost to produce a token amount:
tokenAmount = (nativeCost * tokenPerNative) / _tokenPerNativeDenominator(). Each elements is denominated as follows:
tokenAmount: token units.nativeCost: wei.tokenPerNative: token units per eth._tokenPerNativeDenominator(): wei per native coin (1e18 on EVM chains).
For a token priced from USD oracles, derive tokenPerNative from the inverse exchange rate:
tokenPerNative = (<Native token price in $> / 1e18) / (<ERC-20 token price in $> / 10**<ERC-20 decimals>) * _tokenPerNativeDenominator()
For example, suppose the token is USDC (2524.86 with 18 decimals).
Then 1 wei of gas costs (2524.86 / 1e18) / (1 / 1e6) = 2.52486e-9 USDC units, so with
_tokenPerNativeDenominator() = 1e18 we have tokenPerNative = 2_524_860_000 (i.e. 2.52486e-9 * 1e18). Charging
actualGasCost wei yields actualGasCost * 2_524_860_000 / 1e18 USDC units.
_postOpCost() → uint256
internal
#Over-estimates the cost of the post-operation logic.
_tokenPerNativeDenominator() → uint256
internal
#Denominator used for interpreting the tokenPerNative returned by PaymasterERC20._fetchDetails as "fixed point" in PaymasterERC20._erc20Cost.
_minTokensPerNative() → uint256
internal
#Lower bound on tokenPerNative (see PaymasterERC20._fetchDetails for units). Operations whose tokenPerNative
is strictly below this value are rejected with SIG_VALIDATION_FAILED before PaymasterERC20._prefund runs.
To pick a value, decide:
minCharge: smallest token amount you want to bill per op (e.g.0.01 USDC = 10_000units).minGasCost: smallestactualGasCost + _postOpCost() * actualUserOpFeePerGasyou expect, in wei (=minGas * minFeePerGas;minFeePerGascan be as low as 1 wei on some L2s).
Then set _minTokensPerNative() >= minCharge * _tokenPerNativeDenominator() / minGasCost.
Example: a USDC (6 decimals) paymaster on a chain with minFeePerGas = 1 gwei, sponsoring
ops of at least 100_000 gas and charging at least 0.01 USDC per op:
function _minTokensPerNative() internal view override returns (uint256) {
return 100e6; // = 1e4 (0.01 USDC) * 1e18 / 1e14 (100_000 gas * 1 gwei) = 100 USDC/ETH
}Setting _minTokensPerNative() below minCharge * _tokenPerNativeDenominator() / minGasCost
lets PaymasterERC20._erc20Cost round to zero or to dust for the cheapest ops the paymaster accepts,
sponsoring them at a low (or zero) price.
_erc20Cost(uint256 nativeCost, uint256 tokenPerNative) → uint256
internal
#Calculates native currency cost to ERC-20 token cost.
Returns type(uint256).max if computation overflows.
_withdrawTokens(contract IERC20 token, address recipient, uint256 amount)
internal
#Internal function that allows the withdrawer to extract ERC-20 tokens resulting from gas payments.
UserOperationSponsored(bytes32 indexed userOpHash, address indexed token, uint256 tokenAmount, uint256 tokenPerNative)
event
#Emitted when a user operation identified by userOpHash is sponsored by this paymaster
using the specified ERC-20 token. The tokenAmount is the amount charged for the operation,
and tokenPerNative is the valuation of the token in units of token per native currency (e.g., ETH).
PaymasterERC20FailedRefund(contract IERC20 token, uint256 prefundAmount, uint256 actualAmount, bytes prefundContext)
error
#Thrown when the paymaster fails to refund the difference between the prefundAmount
and the actualAmount of token.
import "@openzeppelin/contracts/account/paymaster/extensions/PaymasterERC20Guarantor.sol";Extension of PaymasterERC20 that enables third parties to guarantee user operations.
This contract allows a guarantor to pre-fund user operations on behalf of users. The guarantor pays the maximum possible gas cost upfront, and after execution:
- If the user repays the guarantor, the guarantor gets their funds back
- If the user fails to repay, the guarantor absorbs the cost
A common use case is for guarantors to pay for the operations of users claiming airdrops. In this scenario:
- The guarantor pays the gas fees upfront
- The user claims their airdrop tokens
- The user repays the guarantor from the claimed tokens
- If the user fails to repay, the guarantor absorbs the cost
The guarantor is identified through the PaymasterERC20Guarantor._fetchGuarantor function, which must be implemented
by developers to determine who can guarantee operations. This allows for flexible guarantor selection
logic based on the specific requirements of the application.
Functions
Events
Errors
prefund(struct PackedUserOperation userOp, bytes32 userOpHash, contract IERC20 token, uint256 tokenPrice, address prefunder, uint256 prefundAmount_) → bool success, address prefunder, uint256 prefundAmount, bytes prefundContext
internal
#Prefunds the user operation using either the guarantor or the default prefunder, and
appends userOp.sender to the tail of prefundContext so the refund process can identify
the user operation sender.
For guaranteed ops, prefundAmount is inflated by PaymasterERC20Guarantor._guaranteedPostOpCost worth of tokens
so the prefund pulled from the guarantor covers the extra postOp work done in PaymasterERC20._refund
(SafeERC20.trySafeTransferFrom from the user + SafeERC20.trySafeTransfer to the guarantor).
_refund(contract IERC20 token, uint256 tokenPrice, uint256 actualAmount, uint256 actualUserOpFeePerGas, address prefunder, uint256 prefundAmount, bytes prefundContext) → bool refunded, uint256 effectiveAmount
internal
#Handles the refund process for guaranteed operations.
- Non-guaranteed (
prefunder == userOp.sender): pass the baseactualAmountthrough toPaymasterERC20._refund. - Guaranteed: augment
actualAmountbyPaymasterERC20Guarantor._guaranteedPostOpCost*actualUserOpFeePerGas(priced in tokens), pull it fromuserOp.sender, and callPaymasterERC20._refundwithactualAmount = 0so the guarantor gets the fullprefundAmountback. If the user fails to pay, the guarantor absorbs the GUARANTEED cost (not the base cost).
_fetchGuarantor(struct PackedUserOperation userOp) → address guarantor
internal
#Fetches the guarantor address and validation data from the user operation.
Return address(0) to disable the guarantor feature. If supported, ensure
explicit consent (e.g., signature verification) to prevent unauthorized use.
_guaranteedPostOpCost() → uint256
internal
#Over-estimates the cost of the post-operation logic. Added on top of PaymasterERC20._postOpCost for guaranteed userOps.
UserOperationGuaranteed(bytes32 indexed userOpHash, address indexed guarantor, uint256 prefundAmount)
event
#Emitted when a user operation identified by userOpHash is guaranteed by a guarantor for prefundAmount.
import "@openzeppelin/contracts/account/paymaster/extensions/PaymasterERC721Owner.sol";Extension of Paymaster that supports account based on ownership of an ERC-721 token.
This paymaster will sponsor user operations if the user has at least 1 token of the token specified during construction.
Paymaster._validatePaymasterUserOp reads token.balanceOf during the validation phase, accessing storage in
an external contract. ERC-7562 restricts unstaked paymasters from such accesses, and public mempool bundlers
will reject these operations when the token contract is proxied or upgradeable. Stake the paymaster
(see Paymaster._addStake) when deploying against a public mempool.
Functions
Errors
Paymaster
constructor(contract IERC721 token_)
internal
#token() → contract IERC721
public
#ERC-721 token used to validate the user operation.
_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32, uint256) → bytes context, uint256 validationData
internal
#Internal validation of whether the paymaster is willing to pay for the user operation. Returns the context to be passed to postOp and the validation data.
The default context is bytes(0). Developers that add a context when overriding this function MUST
also override Paymaster._postOp to process the context passed along.
import "@openzeppelin/contracts/account/paymaster/extensions/PaymasterSigner.sol";Extension of Paymaster that adds signature validation. See SignerECDSA, SignerP256 or SignerRSA.
Example of usage:
contract MyPaymasterECDSASigner is PaymasterSigner, SignerECDSA {
constructor(address signerAddr) EIP712("MyPaymasterECDSASigner", "1") SignerECDSA(signerAddr) {}
}Functions
- _signableUserOpHash(userOp, validAfter, validUntil)
- _validatePaymasterUserOp(userOp, , )
- _decodePaymasterUserOp(userOp)
Paymaster
EIP712
AbstractSigner
Events
IERC5267
Errors
Paymaster
_signableUserOpHash(struct PackedUserOperation userOp, uint48 validAfter, uint48 validUntil) → bytes32
internal
#Virtual function that returns the signable hash for a user operations. Given the userOpHash
contains the paymasterAndData itself, it's not possible to sign that value directly. Instead,
this function must be used to provide a custom mechanism to authorize an user operation.
_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32, uint256) → bytes context, uint256 validationData
internal
#Internal validation of whether the paymaster is willing to pay for the user operation. Returns the context to be passed to postOp and the validation data.
The context returned is bytes(0). Developers overriding this function MUST
override Paymaster._postOp to process the context passed along.
_decodePaymasterUserOp(struct PackedUserOperation userOp) → uint48 validAfter, uint48 validUntil, bytes signature
internal
#Decodes the user operation's data from paymasterAndData.
import "@openzeppelin/contracts/account/utils/EIP7702Utils.sol";Library with common EIP-7702 utility functions.
See EIP-7702.
Functions
fetchDelegate(address account) → address
internal
#Returns the address of the delegate if account has an EIP-7702 delegation setup, or address(0) otherwise.
import "@openzeppelin/contracts/account/utils/ERC4337Utils.sol";This is available on all entrypoint since v0.4.0, but is not formally part of the ERC.
Functions
getUserOpHash(struct PackedUserOperation userOp) → bytes32
external
#import "@openzeppelin/contracts/account/utils/ERC4337Utils.sol";Library with common ERC-4337 utility functions.
See ERC-4337.
Functions
- parseValidationData(validationData)
- packValidationData(aggregator, validAfter, validUntil)
- packValidationData(aggregator, validAfter, validUntil, range)
- packValidationData(sigSuccess, validAfter, validUntil)
- packValidationData(sigSuccess, validAfter, validUntil, range)
- combineValidationData(validationData1, validationData2)
- getValidationData(validationData)
- hash(self, entrypoint)
- factory(self)
- factoryData(self)
- verificationGasLimit(self)
- callGasLimit(self)
- maxPriorityFeePerGas(self)
- maxFeePerGas(self)
- gasPrice(self)
- paymaster(self)
- paymasterVerificationGasLimit(self)
- paymasterPostOpGasLimit(self)
- paymasterData(self)
- paymasterSignature(self)
parseValidationData(uint256 validationData) → address aggregator, uint48 validAfter, uint48 validUntil, enum ERC4337Utils.ValidationRange range
internal
#Parses the validation data into its components and the validity range. See ERC4337Utils.packValidationData.
Strips away the highest bit flag from the validAfter and validUntil fields.
packValidationData(address aggregator, uint48 validAfter, uint48 validUntil) → uint256
internal
#Packs the validation data into a single uint256. See ERC4337Utils.parseValidationData.
packValidationData(address aggregator, uint48 validAfter, uint48 validUntil, enum ERC4337Utils.ValidationRange range) → uint256
internal
#Variant of ERC4337Utils.packValidationData that forces which validity range to use. This overwrites the presence of
flags in validAfter and validUntil).
packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) → uint256
internal
#Variant of ERC4337Utils.packValidationData that uses a boolean success flag instead of an aggregator address.
packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil, enum ERC4337Utils.ValidationRange range) → uint256
internal
#Variant of ERC4337Utils.packValidationData that uses a boolean success flag instead of an aggregator address and that
forces which validity range to use. This overwrites the presence of flags in validAfter and validUntil).
combineValidationData(uint256 validationData1, uint256 validationData2) → uint256
internal
#Combines two validation data into a single one.
The aggregator is set to ERC4337Utils.SIG_VALIDATION_SUCCESS if both are successful, while
the validAfter is the maximum and the validUntil is the minimum of both.
Returns SIG_VALIDATION_FAILED if the validation ranges differ.
getValidationData(uint256 validationData) → address aggregator, bool outOfTimeRange
internal
#Returns the aggregator of the validationData and whether it is out of time range.
hash(struct PackedUserOperation self, address entrypoint) → bytes32
internal
#Get the hash of a user operation for a given entrypoint
factory(struct PackedUserOperation self) → address
internal
#Returns factory from the PackedUserOperation, or address(0) if the initCode is empty or not properly formatted.
factoryData(struct PackedUserOperation self) → bytes
internal
#Returns factoryData from the PackedUserOperation, or empty bytes if the initCode is empty or not properly formatted.
verificationGasLimit(struct PackedUserOperation self) → uint256
internal
#Returns verificationGasLimit from the PackedUserOperation.
callGasLimit(struct PackedUserOperation self) → uint256
internal
#Returns callGasLimit from the PackedUserOperation.
maxPriorityFeePerGas(struct PackedUserOperation self) → uint256
internal
#Returns the first section of gasFees from the PackedUserOperation.
maxFeePerGas(struct PackedUserOperation self) → uint256
internal
#Returns the second section of gasFees from the PackedUserOperation.
gasPrice(struct PackedUserOperation self) → uint256
internal
#Returns the total gas price for the PackedUserOperation (ie. maxFeePerGas or maxPriorityFeePerGas + basefee).
paymaster(struct PackedUserOperation self) → address
internal
#Returns the first section of paymasterAndData from the PackedUserOperation.
paymasterVerificationGasLimit(struct PackedUserOperation self) → uint256
internal
#Returns the second section of paymasterAndData from the PackedUserOperation.
paymasterPostOpGasLimit(struct PackedUserOperation self) → uint256
internal
#Returns the third section of paymasterAndData from the PackedUserOperation.
paymasterData(struct PackedUserOperation self) → bytes
internal
#Returns the fourth section of paymasterAndData from the PackedUserOperation.
If a paymaster signature is present, it is excluded from the returned data.
paymasterSignature(struct PackedUserOperation self) → bytes
internal
#Returns the paymaster signature from paymasterAndData (EntryPoint v0.9+).
Returns empty bytes if no paymaster signature is present.
import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";Library with common ERC-7579 utility functions.
See ERC-7579.
Functions
- execSingle(executionCalldata, execType)
- execBatch(executionCalldata, execType)
- execDelegateCall(executionCalldata, execType)
- encodeMode(callType, execType, selector, payload)
- decodeMode(mode)
- encodeSingle(target, value, callData)
- decodeSingle(executionCalldata)
- encodeDelegate(target, callData)
- decodeDelegate(executionCalldata)
- encodeBatch(executionBatch)
- decodeBatch(executionCalldata)
Errors
execSingle(bytes executionCalldata, ExecType execType) → bytes[] returnData
internal
#Executes a single call.
execBatch(bytes executionCalldata, ExecType execType) → bytes[] returnData
internal
#Executes a batch of calls.
execDelegateCall(bytes executionCalldata, ExecType execType) → bytes[] returnData
internal
#Executes a delegate call.
encodeMode(CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) → Mode mode
internal
#Encodes the mode with the provided parameters. See ERC7579Utils.decodeMode.
decodeMode(Mode mode) → CallType callType, ExecType execType, ModeSelector selector, ModePayload payload
internal
#Decodes the mode into its parameters. See ERC7579Utils.encodeMode.
encodeSingle(address target, uint256 value, bytes callData) → bytes executionCalldata
internal
#Encodes a single call execution. See ERC7579Utils.decodeSingle.
decodeSingle(bytes executionCalldata) → address target, uint256 value, bytes callData
internal
#Decodes a single call execution. See ERC7579Utils.encodeSingle.
encodeDelegate(address target, bytes callData) → bytes executionCalldata
internal
#Encodes a delegate call execution. See ERC7579Utils.decodeDelegate.
decodeDelegate(bytes executionCalldata) → address target, bytes callData
internal
#Decodes a delegate call execution. See ERC7579Utils.encodeDelegate.
encodeBatch(struct Execution[] executionBatch) → bytes executionCalldata
internal
#Encodes a batch of executions. See ERC7579Utils.decodeBatch.
decodeBatch(bytes executionCalldata) → struct Execution[] executionBatch
internal
#Decodes a batch of executions. See ERC7579Utils.encodeBatch.
This function runs some checks and will throw a ERC7579Utils.ERC7579DecodingError if the input is not properly formatted.
ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata)
event
#Emits when an ERC7579Utils.EXECTYPE_TRY execution fails.
ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module)
error
#The provided module doesn't match the provided module type.
ERC7579UninstalledModule(uint256 moduleTypeId, address module)
error
#The module is not installed.
ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module)
error
#The module is already installed.
ERC7579UnsupportedModuleType(uint256 moduleTypeId)
error
#The module type is not supported.
ERC7579DecodingError()
error
#Input calldata not properly formatted and possibly malicious.
import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";Compares two CallType values for equality.
eqCallType(CallType a, CallType b) → bool
internal
#Compares two CallType values for equality.
import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";Compares two ExecType values for equality.
eqExecType(ExecType a, ExecType b) → bool
internal
#Compares two ExecType values for equality.
import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";Compares two ModeSelector values for equality.
eqModeSelector(ModeSelector a, ModeSelector b) → bool
internal
#Compares two ModeSelector values for equality.
import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";Compares two ModePayload values for equality.
eqModePayload(ModePayload a, ModePayload b) → bool
internal
#Compares two ModePayload values for equality.