Account
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 ofAccount
that implements support for ERC-7579 modules. -
AccountERC7579Hooked
: An extension ofAccountERC7579
with support for a single hook module (type 4). -
ERC7821
: Minimal batch executor implementation contracts. Useful to enable easy batch execution for smart contracts. -
ERC7579Executor
: An executor module that enables executing calls from accounts where the it’s installed. -
ERC7579DelayedExecutor
: An executor module that adds a delay before executing an account operation. -
ERC7579Validator
: Abstract validator module for ERC-7579 accounts that provides base implementation for signature validation. -
ERC7579Signature
: Implementation ofERC7579Validator
using ERC-7913 signature verification for address-less cryptographic keys and account signatures. -
ERC7579Multisig
: An extension ofERC7579Validator
that enables validation using ERC-7913 signer keys. -
ERC7579MultisigWeighted
: An extension ofERC7579Multisig
that allows different weights to be assigned to signers. -
ERC7579MultisigConfirmation
: An extension ofERC7579Multisig
that requires each signer to provide a confirmation signature. -
PaymasterCore
: 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 allows users to pay for user operations based on ERC-721 ownership. -
PaymasterSigner
: A paymaster that allows users to pay for user operations using an authorized signature.
Core
Account
import "@openzeppelin/community-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 Account 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.
|
-
onlyEntryPointOrSelf()
-
onlyEntryPoint()
-
entryPoint()
-
getNonce()
-
getNonce(key)
-
validateUserOp(userOp, userOpHash, missingAccountFunds)
-
_validateUserOp(userOp, userOpHash)
-
_signableUserOpHash(, userOpHash)
-
_payPrefund(missingAccountFunds)
-
_checkEntryPoint()
-
_checkEntryPointOrSelf()
-
receive()
-
_rawSignatureValidation(hash, signature)
-
AccountUnauthorized(sender)
entryPoint() → contract IEntryPoint
public
Canonical entry point for the account that forwards and validates user operations.
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 contract -
validUntil
(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) → uint256
internal
Returns the validationData for a given user operation. By default, this checks the signature of the
signable hash (produced by _signableUserOpHash
) using the abstract signer (AbstractSigner._rawSignatureValidation
).
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 operations. 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 validateUserOp
.
_checkEntryPoint()
internal
Ensures the caller is the entryPoint
.
_checkEntryPointOrSelf()
internal
Ensures the caller is the entryPoint
or the account itself.
Extensions
AccountERC7579
import "@openzeppelin/community-contracts/account/extensions/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);
}
}
|
Removing all validator modules will render the account inoperable, as no user operations can be validated thereafter. |
-
onlyModule(moduleTypeId, additionalContext)
-
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)
-
_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(, )
-
entryPoint()
-
getNonce()
-
getNonce(key)
-
validateUserOp(userOp, userOpHash, missingAccountFunds)
-
_signableUserOpHash(, userOpHash)
-
_payPrefund(missingAccountFunds)
-
_checkEntryPoint()
-
_checkEntryPointOrSelf()
-
receive()
-
ModuleInstalled(moduleTypeId, module)
-
ModuleUninstalled(moduleTypeId, module)
-
ERC7579MissingFallbackHandler(selector)
-
AccountUnauthorized(sender)
onlyModule(uint256 moduleTypeId, bytes additionalContext)
modifier
Modifier that checks if the caller is an installed module of the given type.
fallback(bytes) → bytes
external
See _fallback
.
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
Implement ERC-1271 through IERC7579Validator modules. If module based validation fails, fallback to "native" validation by the abstract signer.
_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
internal
Validates a user operation with _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 _extractUserOpValidator
for the module extraction logic.
_execute(Mode mode, bytes executionCalldata) → bytes[] returnData
internal
ERC-7579 execution logic. See 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
supportsModule
. Reverts with {ERC7579UnsupportedModuleType}. -
Module must be of the given type. Reverts with {ERC7579MismatchedModuleTypeId}.
-
Module must not be already installed. Reverts with {ERC7579AlreadyInstalledModule}.
Emits a {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 {ERC7579UninstalledModule} otherwise.
_fallback() → bytes
internal
Fallback function that delegates the call to the installed handler for the given selector.
Reverts with 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.
_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/ERC-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 fallback
was called with a selector that doesn’t have an installed handler.
AccountERC7579Hooked
import "@openzeppelin/community-contracts/account/extensions/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 _execute
(including execute
and 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.
|
-
withHook()
-
accountId()
-
hook()
-
supportsModule(moduleTypeId)
-
isModuleInstalled(moduleTypeId, module, data)
-
_installModule(moduleTypeId, module, initData)
-
_uninstallModule(moduleTypeId, module, deInitData)
-
_execute(mode, executionCalldata)
-
_fallback()
-
fallback()
-
supportsExecutionMode(encodedMode)
-
installModule(moduleTypeId, module, initData)
-
uninstallModule(moduleTypeId, module, deInitData)
-
execute(mode, executionCalldata)
-
executeFromExecutor(mode, executionCalldata)
-
isValidSignature(hash, signature)
-
_validateUserOp(userOp, userOpHash)
-
_fallbackHandler(selector)
-
_checkModule(moduleTypeId, module, additionalContext)
-
_extractUserOpValidator(userOp)
-
_extractSignatureValidator(signature)
-
_decodeFallbackData(data)
-
_rawSignatureValidation(, )
-
entryPoint()
-
getNonce()
-
getNonce(key)
-
validateUserOp(userOp, userOpHash, missingAccountFunds)
-
_signableUserOpHash(, userOpHash)
-
_payPrefund(missingAccountFunds)
-
_checkEntryPoint()
-
_checkEntryPointOrSelf()
-
receive()
-
ModuleInstalled(moduleTypeId, module)
-
ModuleUninstalled(moduleTypeId, module)
-
ERC7579HookModuleAlreadyPresent(hook)
-
ERC7579MissingFallbackHandler(selector)
-
AccountUnauthorized(sender)
withHook()
modifier
Calls {IERC7579Hook-preCheck} before executing the modified function and {IERC7579Hook-postCheck} thereafter.
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
.
ERC7821
import "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
Minimal batch executor following ERC-7821.
Only supports supports single batch mode (0x01000000000000000000
). Does not support optional "opData".
-
execute(mode, executionData)
-
supportsExecutionMode(mode)
-
_erc7821AuthorizedExecutor(caller, , )
-
UnsupportedExecutionMode()
execute(bytes32 mode, bytes executionData)
public
Executes the calls in executionData
with no optional opData
support.
Access to this function is controlled by _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 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);
}
Modules
ERC7579Executor
import "@openzeppelin/community-contracts/account/modules/ERC7579Executor.sol";
Basic implementation for ERC-7579 executor modules that provides execution functionality for smart accounts.
The module enables accounts to execute arbitrary operations, leveraging the execution
capabilities defined in the ERC-7579 standard. Developers can customize whether an operation
can be executed with custom rules by implementing the _validateExecution
function in
derived contracts.
This is a simplified executor that directly executes operations without delay or expiration
mechanisms. For a more advanced implementation with time-delayed execution patterns and
security features, see ERC7579DelayedExecutor .
|
-
isModuleType(moduleTypeId)
-
execute(account, salt, mode, data)
-
_validateExecution(account, salt, mode, data)
-
_execute(account, mode, salt, executionCalldata)
-
onInstall(data)
-
onUninstall(data)
-
ERC7579ExecutorOperationExecuted(account, salt, mode, executionCalldata)
execute(address account, bytes32 salt, bytes32 mode, bytes data) → bytes[] returnData
public
Executes an operation and returns the result data from the executed operation.
Restricted to the account itself by default. See _execute
for requirements and
_validateExecution
for authorization checks.
_validateExecution(address account, bytes32 salt, bytes32 mode, bytes data) → bytes
internal
Validates whether the execution can proceed. This function is called before executing the operation and returns the execution calldata to be used.
Example extension:
function _validateExecution(address account, bytes32 salt, bytes32 mode, bytes calldata data)
internal
override
returns (bytes calldata)
{
// custom logic
return data;
}
Pack extra data in the data arguments (e.g. a signature) to be used in the
validation process. Calldata can be sliced to extract it and return only the
execution calldata.
|
_execute(address account, bytes32 mode, bytes32 salt, bytes executionCalldata) → bytes[] returnData
internal
Internal version of execute
. Emits ERC7579ExecutorOperationExecuted
event.
Requirements:
-
The
account
must implement the {IERC7579Execution-executeFromExecutor} function.
ERC7579DelayedExecutor
import "@openzeppelin/community-contracts/account/modules/ERC7579DelayedExecutor.sol";
Extension of ERC7579Executor
that allows scheduling and executing delayed operations
with expiration. This module enables time-delayed execution patterns for smart accounts.
Operation Lifecycle
-
Scheduling: Operations are scheduled via
schedule
with a specified delay period. The delay period is set duringonInstall
and can be customized viasetDelay
. Each operation enters aScheduled
state and must wait for its delay period to elapse. -
Security Window: During the delay period, operations remain in
Scheduled
state but cannot be executed. Through this period, suspicious operations can be monitored and canceled viacancel
if appropriate. -
Execution & Expiration: Once the delay period elapses, operations transition to
Ready
state. Operations can be executed viaexecute
and have an expiration period after becoming executable. If an operation is not executed within the expiration period, it becomesExpired
and can’t be executed. Expired operations must be rescheduled with a different salt.
Delay Management
Accounts can set their own delay periods during installation or via setDelay
.
The delay period is enforced even between installas and uninstalls to prevent
immediate downgrades. When setting a new delay period, the new delay takes effect
after a transition period defined by the current delay or minSetback
, whichever
is longer.
Authorization
Authorization for scheduling and canceling operations is controlled through the _validateSchedule
and _validateCancel
functions. These functions can be overridden to implement custom
authorization logic, such as requiring specific signers or roles.
Use _scheduleAt to schedule operations at a specific points in time. This is
useful to pre-schedule operations for non-deployed accounts (e.g. subscriptions).
|
-
state(account, salt, mode, executionCalldata)
-
state(operationId)
-
minSetback()
-
getDelay(account)
-
getExpiration(account)
-
getSchedule(account, salt, mode, executionCalldata)
-
getSchedule(operationId)
-
hashOperation(account, salt, mode, executionCalldata)
-
defaultExpiration()
-
onInstall(initData)
-
setDelay(newDelay)
-
setExpiration(newExpiration)
-
schedule(account, salt, mode, data)
-
cancel(account, salt, mode, data)
-
onUninstall()
-
_validateExecution(, , , data)
-
_validateCancel(account, , , )
-
_validateSchedule(account, , , )
-
_setDelay(account, newDelay, minimumSetback)
-
_setExpiration(account, newExpiration)
-
_scheduleAt(account, salt, mode, executionCalldata, timepoint, delay)
-
_execute(account, salt, mode, executionCalldata)
-
_cancel(account, mode, executionCalldata, salt)
-
_validateStateBitmap(operationId, allowedStates)
-
_encodeStateBitmap(operationState)
-
isModuleType(moduleTypeId)
-
execute(account, salt, mode, data)
-
ERC7579ExecutorOperationScheduled(account, operationId, salt, mode, executionCalldata, schedule)
-
ERC7579ExecutorOperationCanceled(account, operationId)
-
ERC7579ExecutorDelayUpdated(account, newDelay, effectTime)
-
ERC7579ExecutorExpirationUpdated(account, newExpiration)
-
ERC7579ExecutorOperationExecuted(account, salt, mode, executionCalldata)
-
ERC7579ExecutorUnexpectedOperationState(operationId, currentState, allowedStates)
-
ERC7579ExecutorModuleNotInstalled()
state(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → enum ERC7579DelayedExecutor.OperationState
public
Current state of an operation.
state(bytes32 operationId) → enum ERC7579DelayedExecutor.OperationState
public
Same as state
, but for a specific operation id.
getDelay(address account) → uint32 delay, uint32 pendingDelay, uint48 effectTime
public
Delay for a specific account.
getSchedule(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → uint48 scheduledAt, uint48 executableAt, uint48 expiresAt
public
Schedule for an operation. Returns default values if not set (i.e. uint48(0)
, uint48(0)
, uint48(0)
).
getSchedule(bytes32 operationId) → uint48 scheduledAt, uint48 executableAt, uint48 expiresAt
public
Same as getSchedule
but with the operation id.
hashOperation(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → bytes32
public
Returns the operation id.
defaultExpiration() → uint32
public
Default expiration for account operations. Set if not provided during onInstall
.
onInstall(bytes initData)
public
Sets up the module’s initial configuration when installed by an account. The account calling this function becomes registered with the module.
The initData
may be abi.encode(uint32(initialDelay), uint32(initialExpiration))
.
The delay will be set to the maximum of this value and the minimum delay if provided.
Otherwise, the delay will be set to minSetback
and defaultExpiration
respectively.
Behaves as a no-op if the module is already installed.
Requirements:
-
The account (i.e
msg.sender
) must implement the {IERC7579ModuleConfig} interface. -
initData
must be empty or decode correctly to(uint32, uint32)
.
setDelay(uint32 newDelay)
public
Allows an account to update its execution delay (see getDelay
).
The new delay will take effect after a transition period defined by the current delay
or minSetback
, whichever is longer. This prevents immediate security downgrades.
Can only be called by the account itself.
setExpiration(uint32 newExpiration)
public
Allows an account to update its execution expiration (see getExpiration
).
schedule(address account, bytes32 salt, bytes32 mode, bytes data)
public
Schedules an operation to be executed after the account’s delay period (see getDelay
).
Operations are uniquely identified by the combination of salt
, mode
, and data
.
See _validateSchedule
for authorization checks.
cancel(address account, bytes32 salt, bytes32 mode, bytes data)
public
Cancels a previously scheduled operation. Can only be called by the account that
scheduled the operation. See _cancel
.
onUninstall(bytes)
public
Cleans up the getDelay
and getExpiration
values by scheduling them to 0
and respecting the previous delay and expiration values.
This function does not clean up scheduled operations. This means operations could potentially be re-executed if the module is reinstalled later. This is a deliberate design choice for efficiency, but module implementations may want to override this behavior to clear scheduled operations during uninstallation for their specific use cases. |
Calling this function directly will remove the expiration (getExpiration ) value and
will schedule a reset of the delay (getDelay ) to 0 for the account. Reinstalling the
module will not immediately reset the delay if the delay reset hasn’t taken effect yet.
|
_validateExecution(address, bytes32, bytes32, bytes data) → bytes
internal
Returns data
as the execution calldata. See ERC7579Executor._execute
.
This function relies on the operation state validation in _execute for
authorization. Extensions of this module should override this function to implement
additional validation logic if needed.
|
_validateCancel(address account, bytes32, bytes32, bytes)
internal
Validates whether an operation can be canceled.
Example extension:
function _validateCancel(address account, bytes32 salt, bytes32 mode, bytes calldata data) internal override {
// e.g. require(msg.sender == account);
}
_validateSchedule(address account, bytes32, bytes32, bytes)
internal
Validates whether an operation can be scheduled.
Example extension:
function _validateSchedule(address account, bytes32 salt, bytes32 mode, bytes calldata data) internal override {
// e.g. require(msg.sender == account);
}
_setDelay(address account, uint32 newDelay, uint32 minimumSetback)
internal
Internal implementation for setting an account’s delay. See getDelay
.
Emits an ERC7579ExecutorDelayUpdated
event.
_setExpiration(address account, uint32 newExpiration)
internal
Internal implementation for setting an account’s expiration. See getExpiration
.
Emits an ERC7579ExecutorExpirationUpdated
event.
_scheduleAt(address account, bytes32 salt, bytes32 mode, bytes executionCalldata, uint48 timepoint, uint32 delay) → bytes32 operationId, struct ERC7579DelayedExecutor.Schedule schedule_
internal
Internal version of schedule
that takes an account
address to schedule
an operation that starts its security window at at
and expires after delay
.
Requirements:
-
The operation must be
Unknown
.
Emits an ERC7579ExecutorOperationScheduled
event.
_execute(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → bytes[] returnData
internal
Requirements:
-
The operation must be
Ready
.
_cancel(address account, bytes32 mode, bytes executionCalldata, bytes32 salt)
internal
Internal version of cancel
that takes an account
address as an argument.
Requirements:
-
The operation must be
Scheduled
orReady
.
Canceled operations can’t be rescheduled. Emits an ERC7579ExecutorOperationCanceled
event.
_validateStateBitmap(bytes32 operationId, bytes32 allowedStates) → enum ERC7579DelayedExecutor.OperationState
internal
Check that the current state of a operation matches the requirements described by the allowedStates
bitmap.
This bitmap should be built using _encodeStateBitmap
.
If requirements are not met, reverts with a ERC7579ExecutorUnexpectedOperationState
error.
_encodeStateBitmap(enum ERC7579DelayedExecutor.OperationState operationState) → bytes32
internal
Encodes a OperationState
into a bytes32
representation where each bit enabled corresponds to
the underlying position in the OperationState
enum. For example:
0x000...10000
^^^^^^------ ...
^----- Canceled
^---- Executed
^--- Ready
^-- Scheduled
^- Unknown
ERC7579ExecutorOperationScheduled(address indexed account, bytes32 indexed operationId, bytes32 salt, bytes32 mode, bytes executionCalldata, uint48 schedule)
event
Emitted when a new operation is scheduled.
ERC7579ExecutorOperationCanceled(address indexed account, bytes32 indexed operationId)
event
Emitted when a new operation is canceled.
ERC7579ExecutorDelayUpdated(address indexed account, uint32 newDelay, uint48 effectTime)
event
Emitted when the execution delay is updated.
ERC7579ExecutorExpirationUpdated(address indexed account, uint32 newExpiration)
event
Emitted when the expiration delay is updated.
ERC7579ExecutorUnexpectedOperationState(bytes32 operationId, enum ERC7579DelayedExecutor.OperationState currentState, bytes32 allowedStates)
error
The current state of a operation is not the expected. The expectedStates
is a bitmap with the
bits enabled for each OperationState enum position counting from right to left. See _encodeStateBitmap
.
If expectedState is bytes32(0) , the operation is expected to not be in any state (i.e. not exist).
|
ERC7579Validator
import "@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol";
Abstract validator module for ERC-7579 accounts.
This contract provides the base implementation for signature validation in ERC-7579 accounts.
Developers must implement the onInstall, onUninstall, and _rawERC7579Validation
functions in derived contracts to define the specific signature validation logic.
Example usage:
contract MyValidatorModule is ERC7579Validator {
function onInstall(bytes calldata data) public {
// Install logic here
}
function onUninstall(bytes calldata data) public {
// Uninstall logic here
}
function _rawERC7579Validation(
address account,
bytes32 hash,
bytes calldata signature
) internal view override returns (bool) {
// Signature validation logic here
}
}
Developers can restrict other operations by using the internal _rawERC7579Validation
.
Example usage:
function execute(
address account,
Mode mode,
bytes calldata executionCalldata,
bytes32 salt,
bytes calldata signature
) public virtual {
require(_rawERC7579Validation(account, hash, signature));
// ... rest of execute logic
}
-
isModuleType(moduleTypeId)
-
validateUserOp(userOp, userOpHash)
-
isValidSignatureWithSender(, hash, signature)
-
_rawERC7579Validation(account, hash, signature)
-
onInstall(data)
-
onUninstall(data)
validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
public
Validates a UserOperation
isValidSignatureWithSender(address, bytes32 hash, bytes signature) → bytes4
public
See {IERC7579Validator-isValidSignatureWithSender}.
Ignores the sender
parameter and validates using _rawERC7579Validation
.
Consider overriding this function to implement custom validation logic
based on the original sender.
ERC7579Signature
import "@openzeppelin/community-contracts/account/modules/ERC7579Signature.sol";
Implementation of ERC7579Validator
module using ERC-7913 signature verification.
This validator allows ERC-7579 accounts to integrate with address-less cryptographic keys
and account signatures through the ERC-7913 signature verification system. Each account
can store its own ERC-7913 formatted signer (a concatenation of a verifier address and a
key: verifier || key
).
This enables accounts to use signature schemes without requiring each key to have its own Ethereum address.A smart account with this module installed can keep an emergency key as a backup.
-
signer(account)
-
onInstall(data)
-
onUninstall()
-
setSigner(signer_)
-
_setSigner(account, signer_)
-
_rawERC7579Validation(account, hash, signature)
-
isModuleType(moduleTypeId)
-
validateUserOp(userOp, userOpHash)
-
isValidSignatureWithSender(, hash, signature)
-
ERC7579SignatureSignerSet(account, signer)
-
ERC7579SignatureInvalidSignerLength()
onInstall(bytes data)
public
See {IERC7579Module-onInstall}.
An account can only call onInstall once. If called directly by the account, the signer will be set to the provided data. Future installations will behave as a no-op. |
onUninstall(bytes)
public
See {IERC7579Module-onUninstall}.
The signer’s key will be removed if the account calls this function, potentially
making the account unusable. As an account operator, make sure to uninstall to a predefined path
in your account that properly handles side effects of uninstallation. See AccountERC7579.uninstallModule .
|
setSigner(bytes signer_)
public
Sets the ERC-7913 signer (i.e. verifier || key
) for the calling account.
_setSigner(address account, bytes signer_)
internal
Internal version of setSigner
that takes an account
as argument without validating signer_
.
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
Validates a signature
using ERC-7913 verification.
This base implementation ignores the sender
parameter and validates using
the account’s stored signer. Derived contracts can override this to implement
custom validation logic based on the sender.
ERC7579Multisig
import "@openzeppelin/community-contracts/account/modules/ERC7579Multisig.sol";
Implementation of an ERC7579Validator
that uses ERC-7913 signers for multisignature
validation.
This module provides a base implementation for multisignature validation that can be
attached to any function through the _rawERC7579Validation
internal function. The signers
are represented using the ERC-7913 format, which concatenates a verifier address and
a key: verifier || key
.
A smart account with this module installed can require multiple signers to approve operations before they are executed, such as requiring 3-of-5 guardians to approve a social recovery operation.
-
onInstall(initData)
-
onUninstall()
-
signers(account)
-
isSigner(account, signer)
-
_signers(account)
-
threshold(account)
-
addSigners(newSigners)
-
removeSigners(oldSigners)
-
setThreshold(newThreshold)
-
_rawERC7579Validation(account, hash, signature)
-
_addSigners(account, newSigners)
-
_removeSigners(account, oldSigners)
-
_setThreshold(account, newThreshold)
-
_validateReachableThreshold(account)
-
_validateSignatures(account, hash, signingSigners, signatures)
-
_validateThreshold(account, validatingSigners)
-
isModuleType(moduleTypeId)
-
validateUserOp(userOp, userOpHash)
-
isValidSignatureWithSender(, hash, signature)
-
ERC7913SignerAdded(account, signer)
-
ERC7913SignerRemoved(account, signer)
-
ERC7913ThresholdSet(account, threshold)
-
ERC7579MultisigAlreadyExists(signer)
-
ERC7579MultisigNonexistentSigner(signer)
-
ERC7579MultisigInvalidSigner(signer)
-
ERC7579MultisigUnreachableThreshold(signers, threshold)
onInstall(bytes initData)
public
Sets up the module’s initial configuration when installed by an account.
See ERC7579DelayedExecutor.onInstall
. Besides the delay setup, the initdata
can
include signers
and threshold
.
The initData should be encoded as:
abi.encode(bytes[] signers, uint256 threshold)
If no signers or threshold are provided, the multisignature functionality will be disabled until they are added later.
An account can only call onInstall once. If called directly by the account, the signer will be set to the provided data. Future installations will behave as a no-op. |
onUninstall(bytes)
public
Cleans up module’s configuration when uninstalled from an account. Clears all signers and resets the threshold.
This function has unbounded gas costs and may become uncallable if the set grows too large.
See EnumerableSetExtended.clear .
|
signers(address account) → bytes[]
public
Returns the set of authorized signers for the specified account.
This operation copies the entire signers set to memory, which can be expensive or may result in unbounded computation. |
isSigner(address account, bytes signer) → bool
public
Returns whether the signer
is an authorized signer for the specified account.
_signers(address account) → struct EnumerableSetExtended.BytesSet
internal
Returns the set of authorized signers for the specified account.
threshold(address account) → uint256
public
Returns the minimum number of signers required to approve a multisignature operation for the specified account.
addSigners(bytes[] newSigners)
public
Adds new signers to the authorized set for the calling account. Can only be called by the account itself.
Requirements:
-
Each of
newSigners
must be at least 20 bytes long. -
Each of
newSigners
must not be already authorized.
removeSigners(bytes[] oldSigners)
public
Removes signers from the authorized set for the calling account. Can only be called by the account itself.
Requirements:
-
Each of
oldSigners
must be authorized. -
After removal, the threshold must still be reachable.
setThreshold(uint256 newThreshold)
public
Sets the threshold for the calling account. Can only be called by the account itself.
Requirements:
-
The threshold must be reachable with the current number of signers.
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
Returns whether the number of valid signatures meets or exceeds the threshold set for the target account.
The signature should be encoded as:
abi.encode(bytes[] signingSigners, bytes[] signatures)
Where signingSigners
are the authorized signers and signatures are their corresponding
signatures of the operation hash
.
_addSigners(address account, bytes[] newSigners)
internal
Adds the newSigners
to those allowed to sign on behalf of the account.
Requirements:
-
Each of
newSigners
must be at least 20 bytes long. Reverts withERC7579MultisigInvalidSigner
if not. -
Each of
newSigners
must not be authorized. Reverts withERC7579MultisigAlreadyExists
if it already exists.
_removeSigners(address account, bytes[] oldSigners)
internal
Removes the oldSigners
from the authorized signers for the account.
Requirements:
-
Each of
oldSigners
must be authorized. Reverts withERC7579MultisigNonexistentSigner
if not. -
The threshold must remain reachable after removal. See
_validateReachableThreshold
for details.
_setThreshold(address account, uint256 newThreshold)
internal
Sets the signatures threshold
required to approve a multisignature operation.
Requirements:
-
The threshold must be reachable with the current number of signers. See
_validateReachableThreshold
for details.
_validateReachableThreshold(address account)
internal
Validates the current threshold is reachable with the number of signers
.
Requirements:
-
The number of signers must be >= the threshold. Reverts with
ERC7579MultisigUnreachableThreshold
if not.
_validateSignatures(address account, bytes32 hash, bytes[] signingSigners, bytes[] signatures) → bool valid
internal
Validates the signatures using the signers and their corresponding signatures. Returns whether the signers are authorized and the signatures are valid for the given hash.
The signers must be ordered by their keccak256
hash to prevent duplications and to optimize
the verification process. The function will return false
if any signer is not authorized or
if the signatures are invalid for the given hash.
Requirements:
-
The
signatures
array must be at least thesigners
array’s length.
_validateThreshold(address account, bytes[] validatingSigners) → bool
internal
Validates that the number of signers meets the threshold
requirement.
Assumes the signers were already validated. See _validateSignatures
for more details.
ERC7579MultisigWeighted
import "@openzeppelin/community-contracts/account/modules/ERC7579MultisigWeighted.sol";
Extension of ERC7579Multisig
that supports weighted signatures.
This module extends the multisignature module to allow assigning different weights to each signer, enabling more flexible governance schemes. For example, some guardians could have higher weight than others, allowing for weighted voting or prioritized authorization.
Example use case:
A smart account with this module installed can schedule social recovery operations after obtaining approval from guardians with sufficient total weight (e.g., requiring a total weight of 10, with 3 guardians weighted as 5, 3, and 2), and then execute them after the time delay has passed.
When setting a threshold value, ensure it matches the scale used for signer weights. For example, if signers have weights like 1, 2, or 3, then a threshold of 4 would require signatures with a total weight of at least 4 (e.g., one with weight 1 and one with weight 3). |
-
onInstall(initData)
-
onUninstall(data)
-
signerWeight(account, signer)
-
totalWeight(account)
-
setSignerWeights(signers, weights)
-
_signerWeight(account, signer)
-
_setSignerWeights(account, signers, newWeights)
-
_addSigners(account, newSigners)
-
_removeSigners(account, oldSigners)
-
_validateReachableThreshold(account)
-
_validateThreshold(account, validatingSigners)
-
_weightSigners(account, signers)
-
signers(account)
-
isSigner(account, signer)
-
_signers(account)
-
threshold(account)
-
addSigners(newSigners)
-
removeSigners(oldSigners)
-
setThreshold(newThreshold)
-
_rawERC7579Validation(account, hash, signature)
-
_setThreshold(account, newThreshold)
-
_validateSignatures(account, hash, signingSigners, signatures)
-
isModuleType(moduleTypeId)
-
validateUserOp(userOp, userOpHash)
-
isValidSignatureWithSender(, hash, signature)
-
ERC7579MultisigWeightChanged(account, signer, weight)
-
ERC7913SignerAdded(account, signer)
-
ERC7913SignerRemoved(account, signer)
-
ERC7913ThresholdSet(account, threshold)
-
ERC7579MultisigInvalidWeight(signer, weight)
-
ERC7579MultisigMismatchedLength()
-
ERC7579MultisigAlreadyExists(signer)
-
ERC7579MultisigNonexistentSigner(signer)
-
ERC7579MultisigInvalidSigner(signer)
-
ERC7579MultisigUnreachableThreshold(signers, threshold)
onInstall(bytes initData)
public
Sets up the module’s initial configuration when installed by an account. Besides the standard delay and signer configuration, this can also include signer weights.
The initData should be encoded as:
abi.encode(bytes[] signers, uint256 threshold, uint256[] weights)
If weights are not provided but signers are, all signers default to weight 1.
An account can only call onInstall once. If called directly by the account, the signer will be set to the provided data. Future installations will behave as a no-op. |
onUninstall(bytes data)
public
Cleans up module’s configuration when uninstalled from an account. Clears all signers, weights, and total weights.
signerWeight(address account, bytes signer) → uint256
public
Gets the weight of a signer for a specific account. Returns 0 if the signer is not authorized.
totalWeight(address account) → uint256
public
Gets the total weight of all signers for a specific account.
setSignerWeights(bytes[] signers, uint256[] weights)
public
Sets weights for signers for the calling account. Can only be called by the account itself.
_signerWeight(address account, bytes signer) → uint256
internal
Gets the weight of the current signer. Returns 1 if not explicitly set. This internal function doesn’t check if the signer is authorized.
_setSignerWeights(address account, bytes[] signers, uint256[] newWeights)
internal
Sets weights for multiple signers at once. Internal version without access control.
Requirements:
-
signers
andweights
arrays must have the same length. Reverts withERC7579MultisigMismatchedLength
on mismatch. -
Each signer must exist in the set of authorized signers. Reverts with
ERC7579MultisigNonexistentSigner
if not. -
Each weight must be greater than 0. Reverts with
ERC7579MultisigInvalidWeight
if not. -
See
_validateReachableThreshold
for the threshold validation.
Emits ERC7579MultisigWeightChanged
for each signer.
_addSigners(address account, bytes[] newSigners)
internal
Override to add weight tracking. See ERC7579Multisig._addSigners
.
Each new signer has a default weight of 1.
_removeSigners(address account, bytes[] oldSigners)
internal
Override to handle weight tracking during removal. See ERC7579Multisig._removeSigners
.
_validateReachableThreshold(address account)
internal
Override to validate threshold against total weight instead of signer count.
This function intentionally does not call super._validateReachableThreshold because the base implementation
assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple
implementations of this function may exist in the contract, so important side effects may be missed
depending on the linearization order.
|
_validateThreshold(address account, bytes[] validatingSigners) → bool
internal
Validates that the total weight of signers meets the threshold
requirement.
Overrides the base implementation to use weights instead of count.
This function intentionally does not call super._validateThreshold because the base implementation
assumes each signer has a weight of 1, which is incompatible with this weighted implementation.
|
_weightSigners(address account, bytes[] signers) → uint256
internal
Calculates the total weight of a set of signers.
ERC7579MultisigWeightChanged(address indexed account, bytes indexed signer, uint256 weight)
event
Emitted when a signer’s weight is changed.
ERC7579MultisigConfirmation
import "@openzeppelin/community-contracts/account/modules/ERC7579MultisigConfirmation.sol";
Extension of ERC7579Multisig
that requires explicit confirmation signatures
from new signers when they are being added to the multisig.
This module ensures that only willing participants can be added as signers to a multisig by requiring each new signer to provide a valid signature confirming their consent to be added. Each signer must sign an EIP-712 message to confirm their addition.
Use this module to ensure that all guardians in a social recovery or multisig setup have explicitly agreed to their roles. |
-
_signableConfirmationHash(account, deadline)
-
_addSigners(account, newSigners)
-
_domainSeparatorV4()
-
_hashTypedDataV4(structHash)
-
eip712Domain()
-
_EIP712Name()
-
_EIP712Version()
-
onInstall(initData)
-
onUninstall()
-
signers(account)
-
isSigner(account, signer)
-
_signers(account)
-
threshold(account)
-
addSigners(newSigners)
-
removeSigners(oldSigners)
-
setThreshold(newThreshold)
-
_rawERC7579Validation(account, hash, signature)
-
_removeSigners(account, oldSigners)
-
_setThreshold(account, newThreshold)
-
_validateReachableThreshold(account)
-
_validateSignatures(account, hash, signingSigners, signatures)
-
_validateThreshold(account, validatingSigners)
-
isModuleType(moduleTypeId)
-
validateUserOp(userOp, userOpHash)
-
isValidSignatureWithSender(, hash, signature)
-
EIP712DomainChanged()
-
ERC7913SignerAdded(account, signer)
-
ERC7913SignerRemoved(account, signer)
-
ERC7913ThresholdSet(account, threshold)
-
ERC7579MultisigInvalidConfirmationSignature(signer)
-
ERC7579MultisigExpiredConfirmation(deadline)
-
ERC7579MultisigAlreadyExists(signer)
-
ERC7579MultisigNonexistentSigner(signer)
-
ERC7579MultisigInvalidSigner(signer)
-
ERC7579MultisigUnreachableThreshold(signers, threshold)
_signableConfirmationHash(address account, uint256 deadline) → bytes32
internal
Generates a hash that signers must sign to confirm their addition to the multisig of account
.
_addSigners(address account, bytes[] newSigners)
internal
Extends ERC7579Multisig._addSigners
_addSigners to require confirmation signatures
Each entry in newSigners must be ABI-encoded as:
abi.encode(deadline,signer,signature); // uint256, bytes, bytes
-
signer: The ERC-7913 signer to add
-
signature: The signature from this signer confirming their addition
The function verifies each signature before adding the signer. If any signature is invalid,
the function reverts with ERC7579MultisigInvalidConfirmationSignature
.
Paymaster
PaymasterCore
import "@openzeppelin/community-contracts/account/paymaster/PaymasterCore.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 PaymasterCore._validatePaymasterUserOp
function to define the paymaster’s validation
and payment logic. The context
parameter is used to pass data between the validation and execution phases.
The paymaster includes support to call the {IEntryPointStake} interface to manage the paymaster’s deposits and stakes
through the internal functions deposit
, withdraw
, addStake
, unlockStake
and 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.
See [Paymaster’s unstaked reputation rules](https://eips.ethereum.org/EIPS/eip-7562#unstaked-paymasters-reputation-rules) for more details on the paymaster’s storage access limitations. |
-
onlyEntryPoint()
-
onlyWithdrawer()
-
entryPoint()
-
validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
-
_validatePaymasterUserOp(userOp, userOpHash, requiredPreFund)
-
_postOp(, , , )
-
deposit()
-
withdraw(to, value)
-
addStake(unstakeDelaySec)
-
unlockStake()
-
withdrawStake(to)
-
_checkEntryPoint()
-
_authorizeWithdraw()
-
PaymasterUnauthorized(sender)
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 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
.
_authorizeWithdraw()
internal
Checks whether msg.sender
withdraw funds stake or deposit from the entrypoint on paymaster’s behalf.
Use of an access control modifier such as {Ownable-onlyOwner} is recommended.
function _authorizeUpgrade() internal onlyOwner {}
PaymasterERC20
import "@openzeppelin/community-contracts/account/paymaster/PaymasterERC20.sol";
Extension of PaymasterCore
that enables users to pay gas with ERC-20 tokens.
To enable this feature, developers must implement the _fetchDetails
function:
function _fetchDetails(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal view override returns (uint256 validationData, IERC20 token, uint256 tokenPrice) {
// Implement logic to fetch the token, and token price from the userOp
}
The contract follows a pre-charge and refund model: 1. During validation, it pre-charges the maximum possible gas cost 2. After execution, it refunds any unused gas back to the user
-
_validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
_prefund(userOp, , token, tokenPrice, prefunder_, maxCost)
-
_postOp(, context, actualGasCost, actualUserOpFeePerGas)
-
_refund(token, tokenPrice, actualGasCost, actualUserOpFeePerGas, prefunder, prefundAmount, )
-
_fetchDetails(userOp, userOpHash)
-
_postOpCost()
-
_tokenPriceDenominator()
-
_erc20Cost(cost, feePerGas, tokenPrice)
-
withdrawTokens(token, recipient, amount)
-
entryPoint()
-
validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
-
deposit()
-
withdraw(to, value)
-
addStake(unstakeDelaySec)
-
unlockStake()
-
withdrawStake(to)
-
_checkEntryPoint()
-
_authorizeWithdraw()
-
UserOperationSponsored(userOpHash, token, tokenAmount, tokenPrice)
-
PaymasterERC20FailedRefund(token, prefundAmount, actualAmount, prefundContext)
-
PaymasterUnauthorized(sender)
_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) → bytes context, uint256 validationData
internal
Attempts to retrieve the token
and tokenPrice
from the user operation (see _fetchDetails
)
and prefund the user operation using these values and the maxCost
argument (see _prefund
).
Returns abi.encodePacked(userOpHash, token, tokenPrice, prefundAmount, prefunder, prefundContext)
in
context
if the prefund is successful. Otherwise, it returns empty bytes.
_prefund(struct PackedUserOperation userOp, bytes32, contract IERC20 token, uint256 tokenPrice, address prefunder_, uint256 maxCost) → bool prefunded, uint256 prefundAmount, address prefunder, bytes prefundContext
internal
Prefunds the userOp
by charging the maximum possible gas cost (maxCost
) in ERC-20 token
.
The token
and tokenPrice
is obtained from the {fetchDetails} function and are funded by the prefunder
,
which is the user operation sender by default. The prefundAmount
is calculated using _erc20Cost
.
Returns a prefundContext
that’s passed to the _postOp
function through its context
return value.
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 _refund
.
Reverts with 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 tokenPrice, uint256 actualGasCost, uint256 actualUserOpFeePerGas, address prefunder, uint256 prefundAmount, bytes) → bool refunded, uint256 actualAmount
internal
Refunds any unused gas back to the user (i.e. prefundAmount - actualAmount
) in token
.
The actualAmount
is calculated using _erc20Cost
and the actualGasCost
, actualUserOpFeePerGas
, prefundContext
and the tokenPrice
from the _postOp
's context.
_fetchDetails(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256 validationData, contract IERC20 token, uint256 tokenPrice
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. -
tokenPrice
: Price of the token in native currency, scaled by_tokenPriceDenominator()
.
Calculating the token price
Given gas fees are paid in native currency, developers can use the ERC20 price unit / native price unit
ratio to
calculate the price of an ERC20 token price in native currency. However, the token may have a different number of decimals
than the native currency. For a a generalized formula considering prices in USD and decimals, consider using:
(<ERC-20 token price in $> / 10**<ERC-20 decimals>) / (<Native token price in $> / 1e18) * _tokenPriceDenominator()
For example, suppose token is USDC ($1 with 6 decimals) and native currency is ETH (assuming $2524.86 with 18 decimals),
then each unit (1e-6) of USDC is worth (1 / 1e6) / ((252486 / 1e2) / 1e18) = 396061563.8094785
wei. The _tokenPriceDenominator()
ensures precision by avoiding fractional value loss. (i.e. the 0.8094785 part).
_tokenPriceDenominator() → uint256
internal
Denominator used for interpreting the tokenPrice
returned by _fetchDetails
as "fixed point" in _erc20Cost
.
_erc20Cost(uint256 cost, uint256 feePerGas, uint256 tokenPrice) → uint256
internal
Calculates the cost of the user operation in ERC-20 tokens.
withdrawTokens(contract IERC20 token, address recipient, uint256 amount)
public
Public function that allows the withdrawer to extract ERC-20 tokens resulting from gas payments.
UserOperationSponsored(bytes32 indexed userOpHash, address indexed token, uint256 tokenAmount, uint256 tokenPrice)
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 tokenPrice
is the price of the token in native currency (e.g., ETH).
PaymasterERC20Guarantor
import "@openzeppelin/community-contracts/account/paymaster/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: 1. If the user repays the guarantor, the guarantor gets their funds back 2. 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 _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.
-
_prefund(userOp, userOpHash, token, tokenPrice, prefunder_, maxCost)
-
_refund(token, tokenPrice, actualGasCost, actualUserOpFeePerGas, prefunder, prefundAmount, prefundContext)
-
_fetchGuarantor(userOp)
-
_guaranteedPostOpCost()
-
_validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
_postOp(, context, actualGasCost, actualUserOpFeePerGas)
-
_fetchDetails(userOp, userOpHash)
-
_postOpCost()
-
_tokenPriceDenominator()
-
_erc20Cost(cost, feePerGas, tokenPrice)
-
withdrawTokens(token, recipient, amount)
-
entryPoint()
-
validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
-
deposit()
-
withdraw(to, value)
-
addStake(unstakeDelaySec)
-
unlockStake()
-
withdrawStake(to)
-
_checkEntryPoint()
-
_authorizeWithdraw()
-
UserOperationGuaranteed(userOpHash, guarantor, prefundAmount)
-
UserOperationSponsored(userOpHash, token, tokenAmount, tokenPrice)
-
PaymasterERC20FailedRefund(token, prefundAmount, actualAmount, prefundContext)
-
PaymasterUnauthorized(sender)
_prefund(struct PackedUserOperation userOp, bytes32 userOpHash, contract IERC20 token, uint256 tokenPrice, address prefunder_, uint256 maxCost) → bool prefunded, uint256 prefundAmount, address prefunder, bytes prefundContext
internal
Prefunds the user operation using either the guarantor or the default prefunder.
See PaymasterERC20._prefund
.
Returns abi.encodePacked(…, userOp.sender)
in prefundContext
to allow
the refund process to identify the user operation sender.
_refund(contract IERC20 token, uint256 tokenPrice, uint256 actualGasCost, uint256 actualUserOpFeePerGas, address prefunder, uint256 prefundAmount, bytes prefundContext) → bool refunded, uint256 actualAmount
internal
Handles the refund process for guaranteed operations.
If the operation was guaranteed, it attempts to get repayment from the user first and then refunds the guarantor.
Otherwise, fallback to PaymasterERC20._refund
.
For guaranteed user operations where the user paid the actualGasCost back, this function
doesn’t call super._refund . Consider whether there are side effects in the parent contract that need to be executed.
|
_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.
|
PaymasterERC721Owner
import "@openzeppelin/community-contracts/account/paymaster/PaymasterERC721Owner.sol";
Extension of PaymasterCore
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 (or via _setToken
).
-
constructor(token_)
-
token()
-
_setToken(token_)
-
_validatePaymasterUserOp(userOp, , )
-
entryPoint()
-
validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
-
_postOp(, , , )
-
deposit()
-
withdraw(to, value)
-
addStake(unstakeDelaySec)
-
unlockStake()
-
withdrawStake(to)
-
_checkEntryPoint()
-
_authorizeWithdraw()
-
PaymasterERC721OwnerTokenSet(token)
-
PaymasterUnauthorized(sender)
_setToken(contract IERC721 token_)
internal
Sets the 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 _postOp to process the context passed along.
|
PaymasterSigner
import "@openzeppelin/community-contracts/account/paymaster/PaymasterSigner.sol";
Extension of PaymasterCore
that adds signature validation. See SignerECDSA
, SignerP256
or SignerRSA
.
Example of usage:
contract MyPaymasterECDSASigner is PaymasterSigner, SignerECDSA {
constructor() EIP712("MyPaymasterECDSASigner", "1") {
// Will revert if the signer is already initialized
_setSigner(signerAddr);
}
}
-
_signableUserOpHash(userOp, validAfter, validUntil)
-
_validatePaymasterUserOp(userOp, , )
-
_decodePaymasterUserOp(userOp)
-
entryPoint()
-
validatePaymasterUserOp(userOp, userOpHash, maxCost)
-
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
-
_postOp(, , , )
-
deposit()
-
withdraw(to, value)
-
addStake(unstakeDelaySec)
-
unlockStake()
-
withdrawStake(to)
-
_checkEntryPoint()
-
_authorizeWithdraw()
-
_domainSeparatorV4()
-
_hashTypedDataV4(structHash)
-
eip712Domain()
-
_EIP712Name()
-
_EIP712Version()
-
_rawSignatureValidation(hash, signature)
-
EIP712DomainChanged()
-
PaymasterUnauthorized(sender)
_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 _postOp to process the context passed along.
|