Modules
Smart contract modules utilities and implementations
ERC-7579 modules that can be installed on any compliant account. Validators authorize user operations and ERC-1271 signatures, while executors trigger actions through the account's executeFromExecutor function. Both are built on top of abstract signer modules that store per-account signer configuration.
ERC7579ECDSA,ERC7579P256,ERC7579WebAuthn: Single-signer modules for ECDSA, P-256 and WebAuthn keys.ERC7579Multisig,ERC7579MultisigWeighted: Threshold and weighted multisignature modules using ERC-7913 signers.ERC7579MultisigConfidential,ERC7579MultisigWeightedConfidential: Multisignature modules that keep signers and thresholds encrypted using FHE.ERC7579SessionKeys: Scoped, time-limited signing authority with per-session policies.ERC7579Validator: Abstract validator module, with implementations for each signer module.ERC7579Executor: Abstract executor module, extended byERC7579DelayedExecutorfor time-delayed execution.ERC7579SocialRecoveryExecutor: Time-delayed account recovery authorized by a weighted set of guardians.ERC7579NativeSpender,ERC7579ERC20Spender: Session-key-driven spending limits for native and ERC-20 tokens.
Signers
Multisig
ERC7579MultisigWeightedConfidential
Validators
ERC7579MultisigWeightedValidator
ERC7579MultisigConfidentialValidator
ERC7579MultisigWeightedConfidentialValidator
Executors
import "@openzeppelin-accounts/contracts/modules/ERC7579ECDSA.sol";Implementation of an IERC7579Module that uses an ECDSA signer for signature
validation.
This module provides a base implementation for ECDSA signer validation that can be
attached to any function through the ERC7579ECDSA._validateECDSA internal function.
Functions
Errors
signer(address account) → address
public
#Returns the authorized signer for the specified account.
onInstall(bytes data)
public
#Sets up the module's initial configuration when installed by an account.
The data parameter should contain the signer address (20 bytes minimum).
If the account doesn't already have a signer configured, the first 20 bytes
of data will be used as the authorized signer address.
See ERC7579ECDSA._extractSigner for more details about the encoding.
Requirements:
- The
datamust be at least 20 bytes. Reverts withERC7579ECDSA.ERC7579ECDSAInvalidSignerLengthif not. - The
datamust contain a valid signer address. Reverts withERC7579ECDSA.ERC7579ECDSAInvalidSignerif not. - The
accountmust have no signer installed. Reverts withERC7579ECDSA.ERC7579ECDSAAlreadyInstalledif not.
Emits a ERC7579ECDSA.ERC7579ECDSASignerSet event if the signer is set.
onUninstall(bytes)
public
#Cleans up the module's configuration when uninstalled from an account. Removes the authorized signer for the calling account.
If the account has a configured signer, it will be cleared. If no signer is configured, this function will behave as a no-op.
Emits a ERC7579ECDSA.ERC7579ECDSASignerSet event if the signer is cleared.
setSigner(address account, address newSigner)
public
#Sets the signer for the specified account.
Requirements:
- The
newSignermust not be the zero address. - The caller must be the specified account.
Emits a ERC7579ECDSA.ERC7579ECDSASignerSet event.
_setSigner(address account, address newSigner)
internal
#Internal version of ERC7579ECDSA.setSigner without access control.
Requirements:
- The
newSignermust not be the zero address.
Emits a ERC7579ECDSA.ERC7579ECDSASignerSet event.
_extractSigner(bytes data) → address
internal
#Extracts the signer address from the provided data for onInstall.
_validateECDSA(address account, bytes32 hash, bytes signature) → bool
internal
#Validates an ECDSA signature against the configured signer for the account.
This function recovers the signer address from the provided signature and hash, then compares it against the authorized signer for the given account.
ERC7579ECDSASignerSet(address indexed account, address signer)
event
#Emitted when the signer is set.
ERC7579ECDSAInvalidSignerLength()
error
#The provided signer data is too short (must be at least 20 bytes for an address).
ERC7579ECDSANotAuthorized(address caller, address account)
error
#The caller is not authorized to set the signer for the specified account.
ERC7579ECDSAInvalidSigner(address signer)
error
#The provided signer address is invalid.
ERC7579ECDSAAlreadyInstalled()
error
#The account has already installed the module.
import "@openzeppelin-accounts/contracts/modules/ERC7579Multisig.sol";Implementation of an IERC7579Module 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 ERC7579Multisig._validateMultisig 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.
Functions
- onInstall(initData)
- onUninstall()
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signer)
- threshold(account)
- addSigners(newSigners)
- removeSigners(oldSigners)
- setThreshold(newThreshold)
- _addSigners(account, newSigners)
- _removeSigners(account, oldSigners)
- _setThreshold(account, newThreshold)
- _validateReachableThreshold(account)
- _validateMultisig(account, hash, signingSigners, signatures)
- _validateSignatures(account, hash, signingSigners, signatures)
- _validateThreshold(account, validatingSigners)
- _decodeMultisigInitData(initData)
IERC7579Module
Events
Errors
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account.
See IERC7579Module-onInstall. The initData encodes signers and threshold.
See ERC7579Multisig._decodeMultisigInitData for the encoding details.
An account can only call onInstall once. If called directly by the account,
the signers will be set to the provided data. Future installations will revert with
ERC7579Multisig.ERC7579MultisigAlreadyInstalled.
Requirements:
- The
accountmust have no signers installed. Reverts withERC7579Multisig.ERC7579MultisigAlreadyInstalledif not. - The
signersarray must be non-empty. Reverts withERC7579Multisig.ERC7579MultisigInvalidInitDataif not. - Those of
ERC7579Multisig._decodeMultisigInitDatafor the decoding requirements. - Those of
ERC7579Multisig._addSignersandERC7579Multisig._setThresholdfor signer configuration requirements.
onUninstall(bytes)
public
#Cleans up module's configuration when uninstalled from an account. Clears all signers and resets the threshold.
See IERC7579Module-onUninstall.
This function has unbounded gas costs and may become uncallable if the set grows too large.
See EnumerableSet-clear.
getSigners(address account, uint64 start, uint64 end) → bytes[]
public
#Returns a slice of the set of authorized signers for the specified account.
Using start = 0 and end = type(uint64).max will return the entire set of signers.
Depending on the start and end, this operation can copy a large amount of data to memory, which
can be expensive. This is designed for view accessors queried without gas fees. Using it in state-changing
functions may become uncallable if the slice grows too large.
getSignerCount(address account) → uint256
public
#Returns the number of authorized signers for the specified account.
isSigner(address account, bytes signer) → bool
public
#Returns whether the signer is an authorized signer for the specified account.
threshold(address account) → uint64
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
newSignersmust be at least 20 bytes long. - Each of
newSignersmust 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
oldSignersmust be authorized. - After removal, the threshold must still be reachable.
setThreshold(uint64 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.
_addSigners(address account, bytes[] newSigners)
internal
#Adds the newSigners to those allowed to sign on behalf of the account.
Requirements:
- Each of
newSignersmust be at least 20 bytes long. Reverts withERC7579Multisig.ERC7579MultisigInvalidSignerif not. - Each of
newSignersmust not be authorized. Reverts withERC7579Multisig.ERC7579MultisigAlreadyExistsif it already exists.
This function does not validate that signers are controlled or represent appropriate entities. Integrators
must ensure signers are properly validated before adding them. Problematic signers can compromise
the multisig's security or functionality. Examples include uncontrolled addresses (e.g., address(0)),
the account's own address (which may cause recursive validation loops), or contracts that may unintentionally
allow arbitrary validation (e.g. using the identity precompile at address(0x04), which would return the
ERC-1271 magic value for any isValidSignature call).
_removeSigners(address account, bytes[] oldSigners)
internal
#Removes the oldSigners from the authorized signers for the account.
Requirements:
- Each of
oldSignersmust be authorized. Reverts withERC7579Multisig.ERC7579MultisigNonexistentSignerif not. - The threshold must remain reachable after removal. See
ERC7579Multisig._validateReachableThresholdfor details.
_setThreshold(address account, uint64 newThreshold)
internal
#Sets the signatures threshold required to approve a multisignature operation.
Requirements:
- The threshold must be greater than 0. Reverts with
ERC7579Multisig.ERC7579MultisigZeroThresholdif not. - The threshold must be reachable with the current number of signers. See
ERC7579Multisig._validateReachableThresholdfor 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
ERC7579Multisig.ERC7579MultisigUnreachableThresholdif not.
_validateMultisig(address account, bytes32 hash, bytes[] signingSigners, bytes[] signatures) → bool
internal
#Validates the multisignature operation by checking the threshold and the signatures.
Where signingSigners are the authorized signers and signatures are their corresponding
signatures of the operation hash.
_validateSignatures(address account, bytes32 hash, bytes[] signingSigners, bytes[] signatures) → bool
internal
#Validates the signatures using the signers and their corresponding signatures. Returns whether the signers are authorized, are not duplicated and the signatures are valid for the given hash.
Sorting the signers by their keccak256 hash will improve the gas
efficiency of this function.
_validateThreshold(address account, bytes[] validatingSigners) → bool
internal
#Validates that the number of signers meets the ERC7579Multisig.threshold requirement.
Assumes the signers were already validated. See ERC7579Multisig._validateSignatures for more details.
decodeMultisigInitData(bytes initData) → bytes[] signers, uint64 threshold
internal
#Decodes multisig initialization data from calldata without memory allocation.
The initData should be encoded as:
abi.encode(bytes[] signers, uint64 threshold)
Requirements:
- The
initDatamust be at least 96 bytes long to contain the minimum ABI-encoded structure. - The signers array offset must be properly aligned and within bounds.
- The array length field must be accessible within the provided data.
ERC7913SignerAdded(address indexed account, bytes signer)
event
#Emitted when signers are added.
ERC7913SignerRemoved(address indexed account, bytes signer)
event
#Emitted when signers are removed.
ERC7913ThresholdSet(address indexed account, uint64 threshold)
event
#Emitted when the threshold is updated.
ERC7579MultisigInvalidInitData()
error
#The initData is invalid.
ERC7579MultisigEmptySigners()
error
#The signers array is empty.
ERC7579MultisigAlreadyExists(bytes signer)
error
#The signer already exists.
ERC7579MultisigNonexistentSigner(bytes signer)
error
#The signer does not exist.
ERC7579MultisigInvalidSigner(bytes signer)
error
#The signer is less than 20 bytes long.
ERC7579MultisigZeroThreshold()
error
#The threshold is zero.
ERC7579MultisigUnreachableThreshold(uint64 signers, uint64 threshold)
error
#The threshold is unreachable given the number of signers.
ERC7579MultisigAlreadyInstalled()
error
#The account has already installed the module.
import "@openzeppelin-accounts/contracts/modules/ERC7579MultisigConfidential.sol";Implementation of an IERC7579Module that provides confidential multisignature
validation.
This module provides a base implementation for multisignature validation that can be
attached to any function through the ERC7579Multisig._validateMultisig internal function. Unlike
the standard ERC7579Multisig module, this confidential version uses Fully Homomorphic
Encryption (FHE) to keep signer identities and the threshold value encrypted on-chain.
This provides privacy benefits by hiding the identity of authorized signers and the
number of signatures required to approve operations.
A smart account with this module installed can require multiple signers to approve operations, such as requiring 3-of-5 guardians to approve a social recovery operation, while keeping both the signer identities and threshold confidential.
Operations are identified by their hash, which represents arbitrary data for the application to validate. Therefore, replay protection and other considersations about the operation must be handled by the application.
To request disclosure of the multisignature validation result for a given operation, the
application must call requestDiscloseOperation to make the result publicly decryptable,
then call discloseOperation to verify the decryption proof and get the result.
Due to the non-deterministic nature of FHE encryption, encrypting the same plaintext
uint256 value multiple times produces different ciphertexts. This means the same underlying signer
can be added multiple times with different encrypted handles, and duplicate detection by
comparing encrypted handles is not possible. Consider duplicate signers may cause the
threshold to become unreachable since ERC7579Multisig._validateSignatures checks for duplicates.
This module supports both address-based signers (EOAs and ERC-1271 contracts) and
ERC-7913 signers (arbitrary keys with verifiers). Addresses are cast to uint256 and
encrypted, while ERC-7913 keys are hashed with keccak256(verifier || key) before
encryption. This hybrid approach allows addresses to be recognizable after decryption,
while ERC-7913 keys require an off-chain mapping from hash to preimage.
The threshold can be set to 0 given it can't be validated in runtime. The account operators must rely on external validation to ensure the threshold is not 0, which allows arbitrary execution.
A per-account configuration nonce (from Nonces) is incremented on every configuration
change (ERC7579Multisig.addSigners, ERC7579Multisig.removeSigners, ERC7579Multisig.setThreshold, ERC7579ECDSA.onInstall). Disclosure state is scoped
to the nonce active at the time of ERC7579MultisigConfidential.requestDiscloseOperation, so any configuration change
automatically invalidates outstanding disclosure entries and closes the stale-approval window.
When using ERC-1271 contract signers, signature validity can change over time (e.g. the
signer contract updates its owner). Because ERC7579MultisigConfidential.requestDiscloseOperation snapshots the validation
result, a later call to ERC7579ECDSAValidator._rawERC7579Validation may accept a stale authorization even if the
ERC-1271 signer would now reject the signature. Prefer signers with non-revocable signatures
(e.g. EOAs), or call ERC7579MultisigConfidential.dismissDisclosure to invalidate outstanding disclosures when an
ERC-1271 signer's validity changes.
This module provides confidentiality of signer identities and threshold values, but
does not provide confidentiality of threshold relationships among participants. Once a valid
disclosure is made via ERC7579MultisigConfidential.requestDiscloseOperation, any party that has observed valid signatures
for the operation hash (e.g. from mempool or calldata) can probe arbitrary signer subsets to
infer the threshold or weight distribution. To limit the exposure window, accounts can perform
a signer reset after sensitive operations: re-encrypting the signer set with fresh FHE handles
(and optionally updating the threshold) invalidates any oracle knowledge accumulated over the
previous handle set, since FHE encryption is non-deterministic and old handles are unlinkable
to new ones.
Functions
- onInstall(initData)
- onUninstall()
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signerId)
- threshold(account)
- requestedDisclosure(relayer, account, hash)
- disclosed(account, hash)
- addSigners(newSignersIds, inputProof)
- removeSigners(oldSignersIds)
- setThreshold(newThreshold, inputProof)
- requestDiscloseOperation(account, hash, signingSigners, signerIdHandles, signatures)
- discloseOperation(relayer, account, hash, decryptionProof)
- discloseOperation(account, hash, decryptionProof)
- dismissDisclosure()
- _addSigners(account, newSignerIds)
- _removeSigners(account, oldSignerIds)
- _setThreshold(account, newThreshold)
- _maxThreshold(account)
- _requestDiscloseOperation(relayer, account, hash, valid)
- _discloseOperation(relayer, account, hash, decryptionProof)
- _validateMultisig(account, hash, signingSigners, signerIdHandles, signatures)
- _validateSignatures(account, hash, signingSigners, signerIdHandles, signatures)
- _signerWeight(account, signerId)
- _validateThreshold(account, reachedThreshold)
- _decodeMultisigInitData(initData)
IERC7579Module
Events
Errors
- ERC7579MultisigInvalidInitData()
- ERC7579MultisigMismatchedValidationLength(signingSignersLength, signerIdHandlesLength, signaturesLength)
- ERC7579MultisigTooManySigners()
- ERC7579MultisigEmptySigners()
- ERC7579MultisigNonexistentSigner(signerId)
- ERC7579MultisigAlreadyInstalled()
- ERC7579MultisigDuplicateHandle(signerId)
- ERC7579MultisigUnknownDisclosure(hash)
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account.
See IERC7579Module-onInstall. The initData encodes externally encrypted
signerIds and threshold. See ERC7579Multisig._decodeMultisigInitData for the encoding details.
An account can only call onInstall once. If called directly by the account,
the signerIds will be set to the provided data. Future installations will revert with
ERC7579Multisig.ERC7579MultisigAlreadyInstalled.
Requirements:
- The
accountmust have no signers installed. Reverts withERC7579Multisig.ERC7579MultisigAlreadyInstalledif not. - The
signerIdsarray must be non-empty. Reverts withERC7579Multisig.ERC7579MultisigEmptySignersif not. - Those of
ERC7579Multisig._decodeMultisigInitDatafor the decoding requirements. - Those of
ERC7579Multisig.addSignersfor encrypted signers configuration requirements.
onUninstall(bytes)
public
#Cleans up module's configuration when uninstalled from an account. Clears all signerIds and resets the threshold.
See IERC7579Module-onUninstall.
This function has unbounded gas costs and may become uncallable if the set grows too large.
See EnumerableSet-clear.
getSigners(address account, uint32 start, uint32 end) → euint256[]
public
#Returns a slice of the set of authorized encrypted signers for the specified account.
The returned signers are encrypted uint256 values (euint256). Using start = 0 and
end = type(uint32).max will return the entire set of signerIds.
Depending on the start and end, this operation can copy a large amount of data to memory, which
can be expensive. This is designed for view accessors queried without gas fees. Using it in state-changing
functions may become uncallable if the slice grows too large.
getSignerCount(address account) → uint256
public
#Returns the number of authorized encrypted signers for the specified account.
isSigner(address account, euint256 signerId) → bool
public
#Returns whether the encrypted signerId is an authorized signer for the specified account.
threshold(address account) → euint32
public
#Returns the encrypted minimum number of signers required to approve a multisignature operation for the specified account.
requestedDisclosure(address relayer, address account, bytes32 hash) → ebool
public
#Returns whether the multisignature validation result for an operation (hash) is requested for disclosure under the account's current configuration nonce.
Returns an uninitialized handle if the configuration has changed since the disclosure was requested.
disclosed(address account, bytes32 hash) → bool
public
#Returns whether the multisignature validation result for an operation (hash) is disclosed under the account's current configuration nonce.
Returns false if the configuration has changed since the operation was disclosed.
addSigners(externalEuint256[] newSignersIds, bytes inputProof)
public
#Adds new encrypted signerIds to the authorized set for the calling account.
Can only be called by the account itself. See ERC7579Multisig._addSigners.
Increments the account's configuration nonce, invalidating any outstanding disclosure entries made under the previous nonce.
The signerIds are provided as externally encrypted uint256 values. Each signerId represents
either an address (cast to uint256) or an ERC-7913 key (hashed with keccak256(verifier || key)
and cast to uint256). The caller must encrypt the signerId off-chain before calling this function.
Requirements:
- Those of
ERC7579Multisig._addSignersfor additional requirements.
removeSigners(euint256[] oldSignersIds)
public
#Removes encrypted signerIds from the authorized set for the calling account.
Can only be called by the account itself. See ERC7579Multisig._removeSigners.
Increments the account's configuration nonce, invalidating any outstanding disclosure entries made under the previous nonce.
setThreshold(externalEuint32 newThreshold, bytes inputProof)
public
#Sets the encrypted threshold for the calling account.
Can only be called by the account itself. See ERC7579Multisig._setThreshold.
Increments the account's configuration nonce, invalidating any outstanding disclosure entries made under the previous nonce.
requestDiscloseOperation(address account, bytes32 hash, bytes[] signingSigners, euint256[] signerIdHandles, bytes[] signatures)
public
#Requests disclosure of the multisignature validation result for an operation (hash).
This function validates the multisignature using encrypted values and makes the
result publicly decryptable. Applications that require using the validation result
must call this function off-chain first to enable the decryption proof to be included
in a subsequent call to discloseOperation to verify the proof.
This function does not perform any access control. Anyone can request disclosure of the multisignature validation result for any operation.
Each call produces a secret-dependent encrypted boolean that is made publicly
decryptable. A caller who possesses valid signatures for the operation hash can
probe arbitrary signer subsets across multiple calls to infer the threshold or weight
distribution, since the decrypted result reveals whether the chosen subset meets the
threshold.
Emits a ERC7579MultisigConfidential.ERC7579MultisigOperationDiscloseRequested event.
discloseOperation(address relayer, address account, bytes32 hash, bytes decryptionProof)
public
#Verifies the decryption proof for a disclosed operation (hash).
Requirements:
- The decryption proof must be cryptographically valid. Reverts if invalid.
Emits a ERC7579MultisigConfidential.ERC7579MultisigOperationDisclosed event.
discloseOperation(address account, bytes32 hash, bytes decryptionProof)
public
#Same as ERC7579MultisigConfidential.discloseOperation but with the relayer set to msg.sender.
dismissDisclosure()
public
#Consumes the caller's configuration nonce, invalidating all outstanding disclosure entries. Useful when an ERC-1271 signer's validity changes and the account needs to revoke previously snapshotted authorization results.
_addSigners(address account, euint256[] newSignerIds)
internal
#Adds the encrypted newSignerIds to those allowed to sign on behalf of the account.
Requirements:
- The number of signers must not exceed
type(uint32).max. Reverts withERC7579MultisigConfidential.ERC7579MultisigTooManySignersif exceeded.
This function does not increment the account's configuration nonce.
Callers that change the configuration must call Nonces-_useNonce to invalidate
outstanding disclosure entries.
FHE encryption is non-deterministic. Encrypting the same plaintext uint256 value multiple times produces different ciphertexts and thus different handles. This allows the same underlying signer to be added multiple times, so duplicate detection by comparing encrypted handles is not possible.
Emits ERC7579MultisigConfidential.ERC7579MultisigSignerAdded for each new signer.
_removeSigners(address account, euint256[] oldSignerIds)
internal
#Removes the encrypted oldSignerIds from the authorized signerIds for the account.
Requirements:
- Each of
oldSignerIdsmust be authorized. Reverts withERC7579Multisig.ERC7579MultisigNonexistentSignerif not. - After removal, at least one signer must remain. Reverts with
ERC7579Multisig.ERC7579MultisigEmptySignersif not.
This function does not increment the account's configuration nonce.
Callers that change the configuration must call Nonces-_useNonce to invalidate
outstanding disclosure entries.
The threshold will be automatically capped at the number of remaining signers to ensure it's always reachable.
Emits ERC7579MultisigConfidential.ERC7579MultisigSignerRemoved for each removed signer.
_setThreshold(address account, euint32 newThreshold)
internal
#Sets the encrypted signatures threshold required to approve a multisignature operation.
This function does not increment the account's configuration nonce.
Callers that change the configuration must call Nonces-_useNonce to invalidate
outstanding disclosure entries.
The threshold is automatically capped at the current number of signers to ensure it's always reachable.
Emits ERC7579MultisigConfidential.ERC7579MultisigThresholdSet with the final threshold value.
_maxThreshold(address account) → euint32
internal
#Returns the maximum threshold for the specified account.
_requestDiscloseOperation(address relayer, address account, bytes32 hash, ebool valid)
internal
#Requests disclosure of the multisignature validation result for an operation (hash).
_discloseOperation(address relayer, address account, bytes32 hash, bytes decryptionProof)
public
#Verifies the decryption proof for a disclosed operation (hash).
_validateMultisig(address account, bytes32 hash, bytes[] signingSigners, euint256[] signerIdHandles, bytes[] signatures) → ebool
internal
#Validates the multisignature operation by checking the threshold and the signatures.
The validation is performed using encrypted values to maintain confidentiality. The function verifies that the number of valid signatures meets or exceeds the encrypted threshold.
Where signingSigners are the plaintext signers (addresses or ERC-7913 keys as bytes),
signerIdHandles are their corresponding encrypted id handles, and signatures are their
signatures of the operation hash.
Given that both the threshold and signers are encrypted, it's possible to pass-in signatures that don't count towards the threshold.
_validateSignatures(address account, bytes32 hash, bytes[] signingSigners, euint256[] signerIdHandles, bytes[] signatures) → euint32
internal
#Validates the signatures using the signers and their corresponding signatures. Returns the number of valid signatures as an encrypted value.
To count towards the threshold, for each signer:
- The handle must exist in the authorized set
- The handle must match the corresponding plaintext signer (address cast to uint256 or keccak256 hash for ERC-7913 keys)
- The signature must be valid for the given hash
- The signer must not be a duplicate of a previous signer in the array
The signers should be ordered by their uint256 representation to ensure efficient duplication check.
Unordered signers are supported, but the uniqueness check will be more expensive. See ERC7579MultisigConfidential._toId.
Requirements:
- The
signingSigners,signerIdHandles, andsignaturesarrays must have the same length. Reverts withERC7579MultisigConfidential.ERC7579MultisigMismatchedValidationLengthif not.
_signerWeight(address account, euint256 signerId) → euint16
internal
#Returns the encrypted weight of an encrypted signerId towards the threshold of the account (0 or 1).
_validateThreshold(address account, euint32 reachedThreshold) → ebool
internal
#Validates that the encrypted reachedThreshold meets or exceeds the encrypted threshold
set for the account.
_decodeMultisigInitData(bytes initData) → bytes inputProof, externalEuint256[] signerIds, externalEuint32 _threshold
internal
#Decodes multisig initialization data from calldata without memory allocation.
The initData should be encoded as:
abi.encode(bytes inputProof, externalEuint256[] signerIds, externalEuint32 threshold)
Requirements:
- The
initDatamust be at least 160 bytes long to contain the minimum structure. - All offsets must be properly aligned and within bounds.
- All array length fields must be accessible within the provided data.
- The proof data must be fully contained within the provided data.
ERC7579MultisigSignerAdded(address indexed account, euint256 signerId)
event
#Emitted when an encrypted signerId is added to the authorized set.
ERC7579MultisigSignerRemoved(address indexed account, euint256 signerId)
event
#Emitted when an encrypted signerId is removed from the authorized set.
ERC7579MultisigThresholdSet(address indexed account, euint32 threshold)
event
#Emitted when the encrypted threshold is updated.
ERC7579MultisigOperationDiscloseRequested(address indexed relayer, address indexed account, bytes32 indexed hash, ebool valid)
event
#Emitted when a disclosure request is made for an operation.
ERC7579MultisigOperationDisclosed(address indexed account, bytes32 indexed hash, ebool valid)
event
#Emitted when an operation is disclosed with its decryption proof verified.
ERC7579MultisigInvalidInitData()
error
#The initData is invalid or improperly formatted.
ERC7579MultisigMismatchedValidationLength(uint256 signingSignersLength, uint256 signerIdHandlesLength, uint256 signaturesLength)
error
#The validation arrays have mismatched lengths.
ERC7579MultisigTooManySigners()
error
#The number of signers exceeds type(uint32).max.
ERC7579MultisigEmptySigners()
error
#The signers array is empty.
ERC7579MultisigNonexistentSigner(euint256 signerId)
error
#The encrypted signerId does not exist in the authorized set.
ERC7579MultisigAlreadyInstalled()
error
#The account has already installed the module.
ERC7579MultisigDuplicateHandle(euint256 signerId)
error
#The encrypted signerId is a duplicate handle.
ERC7579MultisigUnknownDisclosure(bytes32 hash)
error
#The operation (hash) is not requested for disclosure.
import "@openzeppelin-accounts/contracts/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).
Functions
- onInstall(initData)
- onUninstall(data)
- signerWeight(account, signer)
- totalWeight(account)
- setSignerWeights(signers, weights)
- _decodeMultisigWeightedInitData(initData)
- _setSignerWeights(account, signers, weights)
- _addSigners(account, newSigners)
- _removeSigners(account, oldSigners)
- _validateReachableThreshold(account)
- _validateThreshold(account, validatingSigners)
ERC7579Multisig
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signer)
- threshold(account)
- addSigners(newSigners)
- removeSigners(oldSigners)
- setThreshold(newThreshold)
- _setThreshold(account, newThreshold)
- _validateMultisig(account, hash, signingSigners, signatures)
- _validateSignatures(account, hash, signingSigners, signatures)
- _decodeMultisigInitData(initData)
IERC7579Module
Events
Errors
ERC7579Multisig
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, uint64 threshold, uint64[] weights)
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 revert with
ERC7579Multisig.ERC7579MultisigAlreadyInstalled.
This function skips super.onInstall to avoid setting the threshold during installation
since it can be greater than the total amount of signers. This function adds the signers, sets the weights,
and sets the threshold.
Requirements:
- The
accountmust have no signers installed. Reverts withERC7579Multisig.ERC7579MultisigAlreadyInstalledif not. - Those of
ERC7579MultisigWeighted._decodeMultisigWeightedInitDatafor the decoding requirements.
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) → uint64
public
#Gets the weight of a signer for a specific account. Returns 0 if the signer is not authorized.
totalWeight(address account) → uint64
public
#Gets the total weight of all signers for a specific account.
setSignerWeights(bytes[] signers, uint64[] weights)
public
#Sets weights for signers for the calling account. Can only be called by the account itself.
decodeMultisigWeightedInitData(bytes initData) → bytes[] signers, uint64 threshold, uint64[] weights
internal
#Decodes weighted multisig initialization data from calldata without memory allocation.
The initData should be encoded as:
abi.encode(bytes[] signers, uint64 threshold, uint64[] weights)
Requirements:
- The
initDatamust be at least 160 bytes long to contain the minimum ABI-encoded structure. - The signers array offset must be properly aligned and within bounds.
- The weights array offset must be properly aligned and within bounds.
- The array length fields must be accessible within the provided data.
_setSignerWeights(address account, bytes[] signers, uint64[] weights)
internal
#Sets weights for multiple signers at once. Internal version without access control.
Requirements:
signersandweightsarrays must have the same length. Reverts withERC7579MultisigWeighted.ERC7579MultisigMismatchedLengthon mismatch.- Each signer must exist in the set of authorized signers. Reverts with
ERC7579Multisig.ERC7579MultisigNonexistentSignerif not. - Each weight must be greater than 0. Reverts with
ERC7579MultisigWeighted.ERC7579MultisigInvalidWeightif not. - See
ERC7579Multisig._validateReachableThresholdfor the threshold validation.
Emits ERC7579MultisigWeighted.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.
In cases where ERC7579MultisigWeighted.totalWeight is almost type(uint64).max (due to a large _totalExtraWeight), adding new
signers could cause the ERC7579MultisigWeighted.totalWeight computation to overflow. Adding a ERC7579MultisigWeighted.totalWeight call after the new
signers are added ensures no such overflow happens.
_removeSigners(address account, bytes[] oldSigners)
internal
#Override to handle weight tracking during removal. See ERC7579Multisig._removeSigners.
Just like ERC7579Multisig._addSigners, this function does not emit ERC7579MultisigWeighted.ERC7579MultisigWeightChanged events. The
ERC7579Multisig.ERC7913SignerRemoved event emitted by ERC7579Multisig._removeSigners is enough to track weights here.
_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 ERC7579Multisig.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.
ERC7579MultisigWeightChanged(address indexed account, bytes indexed signer, uint64 weight)
event
#Emitted when a signer's weight is changed.
Not emitted in ERC7579Multisig._addSigners or ERC7579Multisig._removeSigners. Indexers must rely on ERC7579Multisig.ERC7913SignerAdded
and ERC7579Multisig.ERC7913SignerRemoved to index a default weight of 1. See ERC7579MultisigWeighted.signerWeight.
ERC7579MultisigInvalidWeight(bytes signer, uint64 weight)
error
#Thrown when a signer's weight is invalid.
ERC7579MultisigMismatchedLength()
error
#Thrown when the arrays lengths don't match.
import "@openzeppelin-accounts/contracts/modules/ERC7579MultisigWeightedConfidential.sol";Extension of ERC7579MultisigConfidential that supports weighted signatures.
This module extends the confidential 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.
Unlike the standard ERC7579MultisigWeighted module, this confidential version uses Fully
Homomorphic Encryption (FHE) to keep signer identities (as uint256), weights, and the threshold
value encrypted on-chain. This provides privacy benefits by hiding the identity of authorized
signers (whether addresses or ERC-7913 keys), their individual weights, and the number of
signatures required to approve operations.
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), while keeping both the signer identities, weights, and threshold confidential.
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).
The threshold can be set to 0 given it can't be validated in runtime. The account operators must rely on external validation to ensure the threshold is not 0, which allows arbitrary execution.
Functions
- onInstall(initData)
- onUninstall(data)
- extraWeight(account, signer)
- totalExtraWeight(account)
- signerWeight(account, signer)
- totalWeight(account)
- setSignerWeights(signers, externalWeights, inputProof)
- _decodeMultisigWeightedInitData(initData)
- _addSigners(account, newSigners)
- _setSignerWeights(account, signers, weights)
- _removeSigners(account, oldSigners)
- _setTotalExtraWeight(account, newTotalExtraWeight)
- _setExtraWeight(account, signer, newExtraWeight)
- _maxThreshold(account)
- _signerWeight(account, signer)
- _totalWeight(account)
ERC7579MultisigConfidential
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signerId)
- threshold(account)
- requestedDisclosure(relayer, account, hash)
- disclosed(account, hash)
- addSigners(newSignersIds, inputProof)
- removeSigners(oldSignersIds)
- setThreshold(newThreshold, inputProof)
- requestDiscloseOperation(account, hash, signingSigners, signerIdHandles, signatures)
- discloseOperation(relayer, account, hash, decryptionProof)
- discloseOperation(account, hash, decryptionProof)
- dismissDisclosure()
- _setThreshold(account, newThreshold)
- _requestDiscloseOperation(relayer, account, hash, valid)
- _discloseOperation(relayer, account, hash, decryptionProof)
- _validateMultisig(account, hash, signingSigners, signerIdHandles, signatures)
- _validateSignatures(account, hash, signingSigners, signerIdHandles, signatures)
- _validateThreshold(account, reachedThreshold)
- _decodeMultisigInitData(initData)
IERC7579Module
Events
Errors
ERC7579MultisigConfidential
- ERC7579MultisigInvalidInitData()
- ERC7579MultisigMismatchedValidationLength(signingSignersLength, signerIdHandlesLength, signaturesLength)
- ERC7579MultisigTooManySigners()
- ERC7579MultisigEmptySigners()
- ERC7579MultisigNonexistentSigner(signerId)
- ERC7579MultisigAlreadyInstalled()
- ERC7579MultisigDuplicateHandle(signerId)
- ERC7579MultisigUnknownDisclosure(hash)
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account. Besides the standard signer and threshold configuration, this can also include encrypted signer weights.
See ERC7579MultisigWeighted._decodeMultisigWeightedInitData for the initData encoding details.
An account can only call onInstall once. If called directly by the account,
the signers, weights, and threshold will be set to the provided data. Future
installations will revert with ERC7579Multisig.ERC7579MultisigAlreadyInstalled.
Requirements:
- The
accountmust have no signers installed. Reverts withERC7579Multisig.ERC7579MultisigAlreadyInstalledif not. - The
signersarray must be non-empty. Reverts withERC7579Multisig.ERC7579MultisigEmptySignersif not. - Those of
ERC7579MultisigWeighted._decodeMultisigWeightedInitDatafor the decoding requirements. - Those of
ERC7579Multisig.addSignersandERC7579MultisigWeighted.setSignerWeightsfor signer and weight configuration requirements.
onUninstall(bytes data)
public
#Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.
extraWeight(address account, euint256 signer) → euint16
public
#Returns the encrypted extra weight of an encrypted signer for the specified account.
totalExtraWeight(address account) → euint32
public
#Returns the encrypted total extra weight of all signers for the specified account.
signerWeight(address account, euint256 signer) → euint16
public
#Returns the encrypted weight of an encrypted signer for the specified account.
totalWeight(address account) → euint32
public
#Returns the encrypted total weight of all signers for the specified account.
setSignerWeights(euint256[] signers, externalEuint16[] externalWeights, bytes inputProof)
public
#Sets encrypted weights for signers for the calling account. Can only be called by the account itself.
Increments the account's configuration nonce, invalidating any outstanding disclosure entries made under the previous nonce.
Requirements:
- Those of
ERC7579MultisigWeighted._setSignerWeightsfor additional requirements.
decodeMultisigWeightedInitData(bytes initData) → bytes inputProof, externalEuint256[] signers, externalEuint32 threshold, externalEuint16[] weights
internal
#Decodes weighted multisig initialization data from calldata without memory allocation.
The initData should be encoded as:
abi.encode(bytes inputProof, externalEuint256[] signers, externalEuint32 threshold, externalEuint16[] weights)
Requirements:
- The
initDatamust be at least 224 bytes long to contain the minimum structure. - All offsets must be properly aligned and within bounds.
- All array length fields must be accessible within the provided data.
- The proof data must be fully contained within the provided data.
_addSigners(address account, euint256[] newSigners)
internal
#Override to add weight tracking and restrict signer count. See ERC7579MultisigConfidential._addSigners.
Each new signer has a default weight of 1.
Requirements:
- The number of signers must not exceed
type(uint16).max. Reverts withERC7579MultisigConfidential.ERC7579MultisigTooManySignersif exceeded.
_setSignerWeights(address account, euint256[] signers, euint16[] weights)
internal
#Sets encrypted weights for multiple signers at once. Internal version without access control.
Requirements:
signersandweightsarrays must have the same length. Reverts withERC7579MultisigWeightedConfidential.ERC7579MultisigMismatchedSignerWeightson mismatch.- Each signer must exist in the set of authorized signers. Reverts with
ERC7579Multisig.ERC7579MultisigNonexistentSignerif not.
This function does not increment the account's configuration nonce.
Callers that change the configuration must call Nonces-_useNonce to invalidate
outstanding disclosure entries.
The threshold will be automatically capped at the total weight to ensure it's always reachable.
Weights of 0 are automatically converted to weight 1 (base weight). See ERC7579MultisigWeighted.signerWeight.
Emits ERC7579MultisigWeighted.ERC7579MultisigWeightChanged for each signer.
_removeSigners(address account, euint256[] oldSigners)
internal
#Override to handle weight tracking during removal. See ERC7579MultisigConfidential._removeSigners.
Cleans up weights for removed signers and updates the total extra weight accordingly.
_setTotalExtraWeight(address account, euint32 newTotalExtraWeight)
internal
#Sets the total extra weight for the specified account.
_setExtraWeight(address account, euint256 signer, euint16 newExtraWeight)
internal
#Sets the extra weight for a signer for the specified account.
_maxThreshold(address account) → euint32
internal
#Override to return the maximum threshold based on total weight instead of signer count.
See ERC7579MultisigConfidential._maxThreshold.
_signerWeight(address account, euint256 signer) → euint16
internal
#Override to return the encrypted weight of a signer instead of a fixed weight of 1.
See ERC7579MultisigConfidential._signerWeight.
Returns 0 if the signer is not authorized, otherwise returns 1 plus the signer's extra weight.
_totalWeight(address account) → euint32
internal
#Returns the encrypted total weight of all signers for the specified account.
ERC7579MultisigWeightChanged(address indexed account, euint256 signer, euint16 weight)
event
#Emitted when an encrypted signer's weight is changed.
ERC7579MultisigMismatchedSignerWeights()
error
#The signers and weights arrays have mismatched lengths.
import "@openzeppelin-accounts/contracts/modules/ERC7579P256.sol";Implementation of an IERC7579Module that uses an P256 signer for signature
validation.
The P256 signer consists of a P256 public key pair (qx, qy coordinates) that is registered during module installation. Signatures must be valid P256 signatures that can be verified against this public key.
This module provides a base implementation for P256 signer validation that can be
attached to any function through the ERC7579P256._validateP256 internal function.
Functions
Errors
signer(address account) → bytes32 qx, bytes32 qy
public
#Returns the authorized P256 signer for the specified account.
onInstall(bytes data)
public
#Sets up the module's initial configuration when installed by an account.
The data parameter should contain the P256 public key coordinates (64 bytes minimum).
If the account doesn't already have a signer configured, the first 64 bytes
of data will be used as the authorized P256 signer (qx, qy).
See ERC7579ECDSA._extractSigner for more details about the encoding.
Requirements:
- The
datamust be at least 64 bytes. Reverts withERC7579P256.ERC7579P256InvalidSignerLengthif not. - The
accountmust have no signer installed. Reverts withERC7579P256.ERC7579P256AlreadyInstalledif not. - Decode
dataintoabi.encode(bytes32 qx, bytes32 qy). - The
datamust contain valid P256 public key coordinates. Reverts withERC7579P256.ERC7579P256InvalidPublicKeyif not.
Emits a ERC7579P256.ERC7579P256SignerSet event if the signer is set.
onUninstall(bytes)
public
#Cleans up the module's configuration when uninstalled from an account. Removes the authorized P256 signer for the calling account.
If the account has a configured signer, it will be cleared. If no signer is configured, this function will behave as a no-op.
Emits a ERC7579P256.ERC7579P256SignerSet event if the signer is cleared.
setSigner(address account, bytes32 qx, bytes32 qy)
public
#Sets the P256 signer for the specified account.
Requirements:
- The
qxandqycoordinates must not be zero. - The
qxandqycoordinates must be valid P256 public key coordinates. - The caller must be the specified account.
Emits a ERC7579P256.ERC7579P256SignerSet event.
_setSigner(address account, bytes32 qx, bytes32 qy)
internal
#Internal version of ERC7579ECDSA.setSigner without access control.
Requirements:
- The
qxandqycoordinates must not be zero. - The
qxandqycoordinates must be valid P256 public key coordinates.
Emits a ERC7579P256.ERC7579P256SignerSet event.
_extractSigner(bytes data) → bytes32 qx, bytes32 qy
internal
#Extracts the P256 public key coordinates from the provided data for onInstall.
_validateP256(address account, bytes32 hash, bytes signature) → bool
internal
#Validates a P256 signature against the configured signer for the account.
This function extracts the r and s components from the signature and verifies them against the P256 public key stored for the given account.
The signature is expected to be at least 64 bytes containing the P256 signature components (r, s) as 32-byte values.
ERC7579P256SignerSet(address indexed account, bytes32 qx, bytes32 qy)
event
#Emitted when the signer is set.
ERC7579P256InvalidSignerLength()
error
#The provided signer data is too short (must be at least 64 bytes for P256 coordinates).
ERC7579P256NotAuthorized(address caller, address account)
error
#The caller is not authorized to set the signer for the specified account.
ERC7579P256InvalidPublicKey(bytes32 qx, bytes32 qy)
error
#The provided P256 public key is invalid.
ERC7579P256AlreadyInstalled()
error
#The account has already installed the module.
import "@openzeppelin-accounts/contracts/modules/ERC7579SessionKeys.sol";Abstract base for ERC-7579 session keys.
This module allows registering ERC-7913 keys as session keys on a smart account, each with
a configurable validity range. A session key is any ERC-7913 signer (an arbitrary
cryptographic key identified by a (verifier, key) pair) that is registered for a
specific account. The key is only considered active between its from and until
timepoints.
The key is encoded in the signature field of the user operations and ERC-1271 requests for
identification and cryptographic verification. See ERC7579SessionKeys._tryDecodeSignature for the encoding details.
This module provides session key management through the ERC7579SessionKeys._validateSessionKey and
ERC7579SessionKeys.isValid internal functions. Concrete implementations should attach it to validation flows such as
ERC7579Validator._rawERC7579Validation.
Functions
- clock()
- CLOCK_MODE()
- installation(account)
- isKey(account, key)
- validity(account, key)
- isValid(account, key)
- keys(account, startIdx, endIdx)
- keyCount(account)
- onInstall(initData)
- onUninstall()
- addKeys(newKeys, starts, ends)
- removeKeys(oldKeys)
- isKey(account, installation, key)
- validity(account, installation, keyId)
- isValid(account, installation, key)
- keys(account, installation, startIdx, endIdx)
- keyCount(account, installation)
- addKeys(account, installation, keys_, starts, ends)
- removeKeys(account, installation, keys_)
- _validateSessionKey(account, hash, signature)
- validateSessionKey(account, installation, hash, signature)
- _keyId(key)
- _tryDecodeSignature(signature)
- _decodeKeysInitData(initData)
IERC7579Module
Events
Errors
clock() → uint48
public
#Clock used for flagging validity ranges. Defaults to timestamp but can be overridden to use block numbers if needed.
CLOCK_MODE() → string
public
#Machine-readable description of the clock as specified in ERC-6372.
Can be overridden to return "mode=blocknumber&from=default" if using block numbers.
installation(address account) → uint256
public
#Returns the installation nonce for an account. See ERC7579ECDSA.onUninstall.
isKey(address account, bytes key) → bool
public
#Returns whether key is a registered session key for account.
validity(address account, bytes key) → uint48 from, uint48 until
public
#Returns the validity range (from, until) for a key on an account.
isValid(address account, bytes key) → bool
public
#Returns whether a key is valid for an account at the current timepoint.
keys(address account, uint256 startIdx, uint256 endIdx) → bytes[]
public
#Returns a slice of the registered session keys for an account.
Using startIdx = 0 and endIdx = type(uint256).max will return all keys.
Depending on the startIdx and endIdx, this operation can copy a large amount
of data to memory, which can be expensive. This is designed for view accessors queried
without gas fees. Using it in state-changing functions may become uncallable if the set
grows too large.
keyCount(address account) → uint256
public
#Returns the number of registered session keys for an account.
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account.
See IERC7579Module-onInstall. The initData encodes arrays of keys and their
corresponding validity ranges.
See ERC7579SessionKeys._decodeKeysInitData for the encoding details.
Requirements:
- Those from
ERC7579SessionKeys._decodeKeysInitDatafor the decoding requirements. - Those from
ERC7579SessionKeys._addKeysfor the key configuration requirements.
Set starts[i] to 0 to activate the key immediately at the current timepoint.
Set ends[i] to 0 to register a key with no expiry.
onUninstall(bytes)
public
#Cleans up the module's configuration when uninstalled from an account. Increments the account's nonce to invalidate all registered session keys.
See IERC7579Module-onUninstall.
This approach avoids unbounded gas costs by not iterating over keys. Previous key data remains in storage but becomes inaccessible due to the nonce increment. Upon reinstallation, the module will use a new nonce and ignore old keys.
addKeys(bytes[] newKeys, uint48[] starts, uint48[] ends)
public
#Adds new session keys to the module for the calling account. Can only be called by the account itself.
Requirements:
- Those from
ERC7579SessionKeys._addKeysfor the key configuration requirements.
removeKeys(bytes[] oldKeys)
public
#Removes session keys from the module for the calling account. Can only be called by the account itself.
Requirements:
- Those from
ERC7579SessionKeys._removeKeysfor the key configuration requirements.
isKey(address account, uint256 installation, bytes key) → bool
internal
#Internal version of ERC7579SessionKeys.isKey that receives an installation_ nonce.
validity(address account, uint256 installation, bytes32 keyId) → uint48 from, uint48 until
internal
#Internal version of ERC7579SessionKeys.validity that receives an installation_ nonce.
isValid(address account, uint256 installation, bytes key) → bool
internal
#Internal version of ERC7579SessionKeys.isValid that receives an installation_ nonce.
keys(address account, uint256 installation, uint256 startIdx, uint256 endIdx) → bytes[]
internal
#Internal version of ERC7579SessionKeys.keys that receives an installation_ nonce.
keyCount(address account, uint256 installation) → uint256
internal
#Internal version of ERC7579SessionKeys.keyCount that receives an installation_ nonce.
addKeys(address account, uint256 installation, bytes[] keys_, uint48[] starts, uint48[] ends)
internal
#Internal function to register session keys for an account.
For each key, stores the ERC-7913 key bytes in the enumerable set and records its
validity range. Keys are identified by keccak256(key) in the validity mapping.
Requirements:
- All arrays must have equal length. Reverts with
ERC7579SessionKeys.ERC7579SessionKeysMismatchedInitDataLengthsif not. - Each of
keys_must not be registered. Reverts withERC7579SessionKeys.ERC7579SessionKeysAlreadyRegisteredKeyif it is.
Setting starts[i] to 0 resolves to the current timepoint.
Setting ends[i] to 0 stores type(uint48).max, meaning no expiry.
Emits an ERC7579SessionKeys.ERC7579SessionKeysERC7913KeySet event for each key.
removeKeys(address account, uint256 installation, bytes[] keys_)
internal
#Internal function to remove session keys from an account.
Removes the key from the enumerable set and clears its validity range in storage.
Requirements:
- Each of
keys_must be registered. Reverts withERC7579SessionKeys.ERC7579SessionKeysNonexistentKeyif not.
Emits an ERC7579SessionKeys.ERC7579SessionKeysERC7913KeyUnset event for each key.
_validateSessionKey(address account, bytes32 hash, bytes signature) → bool
internal
#Validates an ERC-7913 session key signature against the registered keys for the account.
Decodes the ERC-7913 key and its cryptographic signature from the encoded signature
field, then checks that the key is registered and that the cryptographic signature is
valid. Does not validate the ERC7579SessionKeys.validity range.
See ERC7579SessionKeys._tryDecodeSignature for the encoding details.
Does not validate the ERC7579SessionKeys.validity range.
validateSessionKey(address account, uint256 installation, bytes32 hash, bytes signature) → bool
internal
#Variant of ERC7579SessionKeys._validateSessionKey that receives an installation_ nonce.
_keyId(bytes key) → bytes32
internal
#Returns the storage key used to identify a session key in the validity mapping.
_tryDecodeSignature(bytes signature) → bool success, bytes key, bytes keySignature
internal
#Decodes a session key signature into its ERC-7913 key and cryptographic
keySignature components.
The signature must be ABI-encoded as abi.encode(bytes key, bytes keySignature).
Returns (false, emptyBytes, emptyBytes) without reverting if the encoding is invalid
or if any offset or length would read out of bounds.
decodeKeysInitData(bytes initData) → bytes[] keys, uint48[] starts, uint48[] ends
internal
#Decodes the initialization data for the session keys module.
The initData must be ABI-encoded as abi.encode(bytes[] keys, uint48[] starts, uint48[] ends)
where each array has the same length.
Requirements:
initDatamust be at least 192 bytes (6 × 32 bytes for the 3 ABI offsets and 3 array lengths).- All offsets must point within the
initDatabounds.
Reverts with ERC7579SessionKeys.ERC7579SessionKeysInvalidInitData if the encoding is invalid.
ERC7579SessionKeysCleared(address indexed account)
event
#Emitted when all session keys are cleared for an account on uninstall.
ERC7579SessionKeysERC7913KeySet(address indexed account, bytes indexed key, uint48 start, uint48 end)
event
#Emitted when a session key is registered or updated for an account.
ERC7579SessionKeysERC7913KeyUnset(address indexed account, bytes indexed key)
event
#Emitted when a session key is removed from an account.
ERC7579SessionKeysMismatchedInitDataLengths()
error
#The keys, starts, and ends arrays passed to ERC7579ECDSA.onInstall have different lengths.
ERC7579SessionKeysInvalidInitData()
error
#The provided initData is invalid or malformed.
ERC7579SessionKeysAlreadyRegisteredKey(bytes key)
error
#The provided key is already registered.
ERC7579SessionKeysNonexistentKey(bytes key)
error
#The provided key is not registered.
import "@openzeppelin-accounts/contracts/modules/ERC7579WebAuthn.sol";Implementation of an IERC7579Module that uses a WebAuthn credential for signature
validation.
This module provides a base implementation for WebAuthn signature validation that can be
attached to any function through the ERC7579WebAuthn._validateWebAuthn internal function.
Functions
Events
ERC7579P256
Errors
_validateWebAuthn(address account, bytes32 hash, bytes signature) → bool
internal
#Validates a WebAuthn authentication assertion against the configured signer for the account.
Returns false if the signature is not a valid WebAuthn authentication assertion.
The signature is expected to be an abi-encoded WebAuthn-WebAuthnAuth struct.
import "@openzeppelin-accounts/contracts/modules/executors/ERC7579DelayedExecutor.sol";Extension of ERC7579Executor that provides time-delayed execution patterns
for smart accounts with scheduling and expiration capabilities.
This contract provides the basic scheduling functionality through the ERC7579DelayedExecutor._schedule,
ERC7579DelayedExecutor._scheduleAt, and ERC7579DelayedExecutor._cancel functions. Developers must implement their own public
schedule, cancel, and execute functions in derived contracts to define the
specific validation and access control logic for each operation.
Operation Lifecycle
-
Scheduling: Operations are scheduled via
ERC7579DelayedExecutor._schedulewith a specified delay period. The delay period is set duringERC7579ECDSA.onInstalland can be customized viaERC7579DelayedExecutor.setDelay. Each operation enters aScheduledstate and must wait for its delay period to elapse. -
Security Window: During the delay period, operations remain in
Scheduledstate but cannot be executed. Through this period, suspicious operations can be monitored and canceled viaERC7579DelayedExecutor._cancelif appropriate. -
Execution & Expiration: Once the delay period elapses, operations transition to
Readystate. Operations can be executed viaERC7579DelayedExecutor._executeand have an expiration period after becoming executable. If an operation is not executed within the expiration period, it becomesExpiredand 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 ERC7579DelayedExecutor.setDelay.
The delay period is enforced even between installs and uninstalls to prevent
immediate downgrades. When setting a new delay period, the transition period before
the new delay takes effect depends on the direction of the change:
- When increasing the delay, the new value takes effect after
min(newDelay, delayChangeWindow()). - When decreasing the delay, the new value takes effect after
currentDelay - newDelay.
Use ERC7579DelayedExecutor._scheduleAt to schedule operations at specific points in time. This is
useful to pre-schedule operations for non-deployed accounts (e.g. subscriptions).
Scheduling, executing and cancelling doesn't have any access control by default and should be validated accordingly while extending this contract.
Target addresses of an operation may return an arbitrary large amount of data, which may cause the operation to fail due to insufficient gas. Consider that a target could return more data than initially expected (e.g. after being upgraded, or when their return data depends on state), potentially blocking the operation's execution until they expire.
Functions
- state(account, salt, mode, executionCalldata)
- state(operationId)
- delayChangeWindow()
- getDelay(account)
- getExpiration(account)
- getSchedule(account, salt, mode, executionCalldata)
- getSchedule(operationId)
- hashOperation(account, salt, mode, executionCalldata)
- defaultExpiration()
- onInstall(initData)
- setDelay(newDelay)
- setExpiration(newExpiration)
- onUninstall()
- _setDelay(account, newDelay, minimumSetback)
- _setExpiration(account, newExpiration)
- _schedule(account, salt, mode, data)
- _scheduleAt(account, salt, mode, executionCalldata, timepoint, delay)
- _execute(account, salt, mode, executionCalldata)
- _cancel(account, salt, mode, executionCalldata)
- _decodeDelayedExecutorInitData(initData)
- _validateStateBitmap(operationId, allowedStates)
- _encodeStateBitmap(operationState)
ERC7579Executor
Events
Errors
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 ERC7579DelayedExecutor.state, but for a specific operation id.
delayChangeWindow() → uint32
public
#Maximum transition period applied when ERC7579DelayedExecutor.setDelay increases the delay, which takes effect
after min(newDelay, delayChangeWindow()). Delay decreases are unaffected and take effect after
currentDelay - newDelay. Also used as the default delay if not provided during ERC7579ECDSA.onInstall.
getDelay(address account) → uint32 delay, uint32 pendingDelay, uint48 effectTime
public
#Delay for a specific account.
getExpiration(address account) → uint32
public
#Expiration delay for account operations.
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 ERC7579DelayedExecutor.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 ERC7579ECDSA.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 must be abi.encodePacked(uint32(initialDelay), uint32(initialExpiration)).
Otherwise, the delay will be set to ERC7579DelayedExecutor.delayChangeWindow and ERC7579DelayedExecutor.defaultExpiration respectively.
If a previous delay is still active (e.g. from a prior installation that was uninstalled before its
delay reset completed), the same asymmetric transition rules as ERC7579DelayedExecutor.setDelay apply to the new delay,
preventing an uninstall/reinstall cycle from being used to bypass the security window.
Requirements:
- The module must not be already installed on the account. Reverts with
ERC7579DelayedExecutor.ERC7579DelayedExecutorAlreadyInstalledif not. initDatamust be empty or decode correctly to(uint32, uint32).
setDelay(uint32 newDelay)
public
#Allows an account to update its execution delay (see ERC7579DelayedExecutor.getDelay).
The transition period before the new delay takes effect depends on the direction of the change:
- When increasing the delay, the new value takes effect after
min(newDelay, delayChangeWindow()). For example, increasing from 1 day to 3 days takes effect after 3 days. Increasing from 1 day to 10 days takes effect afterERC7579DelayedExecutor.delayChangeWindow(5 days). This cap prevents the account from being locked out by an accidental large increase (e.g. using milliseconds instead of seconds). - When decreasing the delay, the new value takes effect after
currentDelay - newDelay. This guarantees that a recovery operation cannot execute faster than the delay that was active at the moment the change was scheduled. For example, decreasing from 10 days to 3 days takes effect after 7 days.
Can only be called by the account itself.
setExpiration(uint32 newExpiration)
public
#Allows an account to update its execution expiration (see ERC7579DelayedExecutor.getExpiration).
onUninstall(bytes)
public
#Cleans up the ERC7579DelayedExecutor.getDelay and ERC7579DelayedExecutor.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.
Uninstalling the module schedules the delay to reset to 0 after the current delay
(equivalent to calling setDelay(0)), and immediately removes the expiration. If the module
is reinstalled before the delay reset completes, the same asymmetric transition rules as
ERC7579DelayedExecutor.setDelay apply to prevent bypassing the security window through an uninstall/reinstall cycle.
_setDelay(address account, uint32 newDelay, uint32 minimumSetback)
internal
#Internal implementation for setting an account's delay. See ERC7579DelayedExecutor.getDelay.
Emits an ERC7579DelayedExecutor.ERC7579ExecutorDelayUpdated event.
_setExpiration(address account, uint32 newExpiration)
internal
#Internal implementation for setting an account's expiration. See ERC7579DelayedExecutor.getExpiration.
Emits an ERC7579DelayedExecutor.ERC7579ExecutorExpirationUpdated event.
_schedule(address account, bytes32 salt, bytes32 mode, bytes data)
internal
#Schedules an operation to be executed after the account's delay period (see ERC7579DelayedExecutor.getDelay).
Operations are uniquely identified by the combination of account, salt, mode, and data.
This should be called by derived contracts after performing their custom validation logic.
Requirements:
- The module must be installed on the account. Reverts with
ERC7579DelayedExecutor.ERC7579ExecutorModuleNotInstalledif not.
This function does not have any access control validation.
_scheduleAt(address account, bytes32 salt, bytes32 mode, bytes executionCalldata, uint48 timepoint, uint32 delay) → bytes32
internal
#Internal function to schedule an operation at a specific timepoint with a custom delay.
The operation starts its security window at timepoint and becomes executable after delay.
Requirements:
- The operation must be
Unknown.
Emits an ERC7579DelayedExecutor.ERC7579ExecutorOperationScheduled event.
This function does not have any access control validation.
_execute(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → bytes[]
internal
#Requirements:
- The operation must be
Ready.
This function does not have any access control validation.
_cancel(address account, bytes32 salt, bytes32 mode, bytes executionCalldata)
internal
#Internal function to cancel a scheduled operation. This should be called by derived contracts after performing their custom validation logic.
Requirements:
- The operation must be
ScheduledorReady.
Canceled operations can't be rescheduled. Emits an ERC7579DelayedExecutor.ERC7579ExecutorOperationCanceled event.
This function does not have any access control validation.
_decodeDelayedExecutorInitData(bytes initData) → uint32 delay, uint32 expiration
internal
#Decodes the init data into a delay and expiration.
_validateStateBitmap(bytes32 operationId, bytes32 allowedStates) → enum ERC7579DelayedExecutor.OperationState
internal
#Check that the current state of an operation matches the requirements described by the allowedStates bitmap.
This bitmap should be built using ERC7579DelayedExecutor._encodeStateBitmap.
If requirements are not met, reverts with a ERC7579DelayedExecutor.ERC7579ExecutorUnexpectedOperationState error.
Passing a bytes32(0) for allowedStates will always revert.
_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:
0x00...100000
^^^^^^------ ...
^------ Canceled
^----- Executed
^---- Expired
^--- Ready
^-- Scheduled
^- UnknownERC7579ExecutorOperationScheduled(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.
ERC7579DelayedExecutorAlreadyInstalled()
error
#ERC7579ExecutorUnexpectedOperationState(bytes32 operationId, enum ERC7579DelayedExecutor.OperationState currentState, bytes32 allowedStates)
error
#The current state of an operation is not the expected. The allowedStates is a bitmap with the
bits enabled for each OperationState enum position counting from right to left. See ERC7579DelayedExecutor._encodeStateBitmap.
ERC7579ExecutorModuleNotInstalled()
error
#The module is not installed on the account.
import "@openzeppelin-accounts/contracts/modules/executors/ERC7579ERC20Spender.sol";Extension of ERC7579SessionKeysValidator that provides ERC20 token spending limits
granted to session keys with automatic renewal periods for ERC-7579 smart accounts.
This module allows accounts to configure spending allowances for external spenders, enabling use cases like subscription services, recurring payments, or delegated spending with predefined limits.
Same dual-role design as ERC7579NativeSpender: a session key authorizes a UserOp whose
callData targets ERC7579ERC20Spender.transferFrom or ERC7579ERC20Spender.batchTransferFrom on this module, and the module
spends through executeFromExecutor on the account.
Each (session key, ERC-20 token) pair carries its own (limit, renewPeriod) spending
policy. A single key can have policies for multiple tokens by listing it multiple times
(with different tokens) in the executor install initData.
The module should be installed under both module types, in this order:
- Validator install (first):
initDataisabi.encode(bytes[] keys, uint48[] starts, uint48[] ends). Registers session keys with their validity ranges throughERC7579SessionKeys._addKeys. At least one key must be registered, otherwise the install reverts. - Executor install (second):
initDataisabi.encode(bytes[] keys, address[] tokens, uint160[] limits, uint48[] renewPeriods). Writes a spending policy for each(keys[i], tokens[i])row. Each row'skeys[i]MUST already be registered. Keys MAY repeat across rows to configure multiple tokens for the same key. Thekeysarray MUST be non-empty.
Once any (key, token) config is written, the module is "installed" and further onInstall
calls revert with ERC7579ERC20Spender.ERC7579ERC20SpenderAlreadyInstalled. To change the spending policy, the
account must uninstall and reinstall (which bumps the installation nonce and resets state).
This module deliberately disables ERC-1271 signing
(ERC7579ERC20Spender.isValidSignatureWithSender always returns 0xffffffff) so a spending session key cannot be
repurposed to authorize arbitrary off-chain messages on behalf of the account.
Removing a session key via ERC7579SessionKeys.removeKeys clears its validity range but
does NOT clear the (key, token) spending configs. If the same key bytes are re-registered via
ERC7579SessionKeys.addKeys (without addKeysWithConfig), the previously-stored configs become
active again. To re-register a key with a fresh policy, call ERC7579ERC20Spender.addKeysWithConfig instead.
Set starts[i] == 0 to schedule a key from the current timepoint.
Set renewPeriod == type(uint48).max to effectively disable the renewal period.
Functions
- isModuleType(moduleTypeId)
- installed(account)
- tokensCount(account)
- tokens(account, startIdx, endIdx)
- renewPeriod(account, key, token)
- limit(account, key, token)
- spent(account, key, token, period)
- spent(account, key, token, period, start)
- spendingAt(account, key, token, period)
- periodAt(account, key, token, timepoint)
- onInstall(initData)
- addKeysWithConfig(newKeys, starts, ends, tokens_, limits, renewPeriods)
- transferFrom(account, key, token, to, amount)
- batchTransferFrom(account, key, token, tos, amounts)
- isValidSignatureWithSender(, , )
- _validateUserOp(userOp, userOpHash)
- addKeys(account, installation, keys, starts, ends)
- installed(account, installation)
- tokensCount(account, installation)
- renewPeriod(account, installation, keyId, token)
- limit(account, installation, keyId, token)
- spent(account, installation, keyId, token, period, start)
- spendingAt(account, installation, keyId, token, period, start)
- periodAt(account, installation, keyId, token, timepoint)
- checkAccountOrKeySender(account, installation, key)
- transferFrom(account, installation, key, token, to, amount)
- batchTransferFrom(account, installation, key, token, tos, amounts)
- setConfig(account, installation, key, token, newLimit, newRenewPeriod)
- _isAuthorizedCallData(userOp)
- _validateSingleExecutionCalldata(callData)
- _validateTransferKey(exec)
ERC7579Executor
ERC7579SessionKeysValidator
ERC7579Validator
ERC7579SessionKeys
- clock()
- CLOCK_MODE()
- installation(account)
- isKey(account, key)
- validity(account, key)
- isValid(account, key)
- keys(account, startIdx, endIdx)
- keyCount(account)
- onUninstall()
- addKeys(newKeys, starts, ends)
- removeKeys(oldKeys)
- isKey(account, installation, key)
- validity(account, installation, keyId)
- isValid(account, installation, key)
- keys(account, installation, startIdx, endIdx)
- keyCount(account, installation)
- removeKeys(account, installation, keys_)
- _validateSessionKey(account, hash, signature)
- validateSessionKey(account, installation, hash, signature)
- _keyId(key)
- _tryDecodeSignature(signature)
- _decodeKeysInitData(initData)
Events
Errors
- ERC7579ERC20SpenderAlreadyInstalled()
- ERC7579ERC20InsufficientAvailable(account, key, token, amount)
- ERC7579ERC20SpenderInvalidInitData()
- ERC7579ERC20SpendingConfigNotSet(account, keyId, token)
- ERC7579ERC20SpenderMismatchedInitDataLengths(keys, tokens, limits, renewPeriods)
- ERC7579ERC20SpenderEmptyKeys()
- ERC7579ERC20SpenderMismatchedBatchLengths(tos, amounts)
- ERC7579ERC20SpenderInvalidRenewPeriod(renewPeriod)
- ERC7579ERC20SpenderInvalidLimit(limit)
- ERC7579ERC20SpenderInvalidToken(token)
- ERC7579ERC20SpendingConfigNotStarted(account, keyId, token, timepoint, start)
- ERC7579ERC20SpenderUnauthorized(caller, key)
- ERC7579ERC20SpenderInvalidStart(start)
- ERC7579ERC20SpenderFailedTransfer(token)
ERC7579SessionKeys
isModuleType(uint256 moduleTypeId) → bool
public
#Returns boolean value if module is a certain type
installed(address account) → bool
public
#Returns whether the module's first-install step has completed for account.
tokensCount(address account) → uint256
public
#Returns the number of tokens with at least one spending config for account.
tokens(address account, uint256 startIdx, uint256 endIdx) → address[]
public
#Returns a paginated slice of tokens with at least one spending config for account.
Depending on startIdx and endIdx, this operation can copy a large amount of
data to memory. Designed for view accessors queried without gas fees.
renewPeriod(address account, bytes key, address token) → uint256
public
#Returns the renewal period for the (key, token) config on account.
limit(address account, bytes key, address token) → uint256
public
#Returns the spending limit per period for the (key, token) config on account.
spent(address account, bytes key, address token, uint256 period) → uint256
public
#Returns the amount spent by key for token during period, anchored at the key's from.
spent(address account, bytes key, address token, uint256 period, uint256 start) → uint256
public
#Same as ERC7579ERC20Spender.spent but reads from an explicit start timepoint.
spendingAt(address account, bytes key, address token, uint256 period) → uint256 spending, uint256 available
public
#Returns (spending, available) for (key, token) at period.
periodAt(address account, bytes key, address token, uint48 timepoint) → uint256 start, uint256 period
public
#Returns (start, period) for (key, token) at timepoint.
Requirements:
timepointmust be>=the key'sfrom. Reverts withERC7579ERC20Spender.ERC7579ERC20SpendingConfigNotStartedotherwise.- The spending config must be set. Reverts with
ERC7579ERC20Spender.ERC7579ERC20SpendingConfigNotSetotherwise.
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account.
This module is installed under BOTH module types and onInstall is called twice. The
shape of initData differs between the two calls:
- First call (validator install,
installed(msg.sender) == false):abi.encode(bytes[] keys, uint48[] starts, uint48[] ends). - Second call (executor install,
installed(msg.sender) == true):abi.encode(bytes[] keys, address[] tokens, uint160[] limits, uint48[] renewPeriods).
First call requirements:
- The
keysarray must be non-empty. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderEmptyKeysotherwise. - Those from the parent's
ERC7579SessionKeys.onInstall(key registration).
Second call requirements:
- No
(key, token)config has been written yet under this installation. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderAlreadyInstalledotherwise. initData.lengthmust be at least 256 bytes (4 ABI offsets + 4 array lengths). Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderInvalidInitDataotherwise. All offsets must point within bounds.- All four arrays must have equal length.
- The
keysarray must be non-empty. - Each
keys[i]must already be registered. Reverts withERC7579SessionKeys.ERC7579SessionKeysNonexistentKeyotherwise. - Those from
ERC7579ERC20Spender._setConfigfor the spending config requirements.
Emits ERC7579SessionKeys.ERC7579SessionKeysERC7913KeySet on the first call and ERC7579ERC20Spender.ERC7579ERC20SpendingConfigSet
for each row on the second call.
Each start will be set to the current timepoint if it's 0.
addKeysWithConfig(bytes[] newKeys, uint48[] starts, uint48[] ends, address[] tokens_, uint160[] limits, uint48[] renewPeriods)
public
#Adds new session keys with one spending configuration each, in a single call. Can only be called by the account itself.
Requirements:
- All six arrays must have equal length.
- Those from
ERC7579SessionKeys._addKeys(and theERC7579SessionKeys._addKeysoverride's past-start ban). - Those from
ERC7579ERC20Spender._setConfigfor each row.
This helper only supports one (token, limit, renewPeriod) per new key. To configure
multiple tokens for a single key, list it multiple times in the executor install initData.
transferFrom(address account, bytes key, address token, address to, uint256 amount)
public
#Transfers token from account to to on behalf of key.
Tracks the spending limit for the (key, token) pair on account, updates the spent
amount for the current period, and executes the transfer through the account's
IERC7579Execution-executeFromExecutor.
Two authorization paths are accepted:
- Executor flow:
msg.sender == account. The validator has already verified the session key's signature and the calldata shape, so no further checks are performed here. - EOA self-call:
key.length == 20andaddress(bytes20(key)) == msg.sender. The key's validity range is enforced manually.
Requirements:
- The caller is authorized via one of the two paths above. Reverts with
ERC7579ERC20Spender.ERC7579ERC20SpenderUnauthorizedotherwise. amountmust not exceed the available limit for the current period. Reverts withERC7579ERC20Spender.ERC7579ERC20InsufficientAvailableotherwise.- The token's
transfermust returntrueor have no return value. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderFailedTransferotherwise. - Those from
ERC7579ERC20Spender._periodAtfor the current timepoint.
batchTransferFrom(address account, bytes key, address token, address[] tos, uint256[] amounts)
public
#Transfers token from account to multiple recipients in a single batch.
Same authorization paths as ERC7579ERC20Spender.transferFrom. The available limit check is performed once
against the total of amounts.
Requirements:
tos.length == amounts.length. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderMismatchedBatchLengthsotherwise.- The caller is authorized via one of the two paths described in
ERC7579ERC20Spender.transferFrom. - The sum of
amountsmust not exceed the available limit. - Each ERC20
transfermust succeed (return value handled identically toERC7579ERC20Spender.transferFrom).
isValidSignatureWithSender(address, bytes32, bytes) → bytes4
public
#Disables ERC-1271 signing by session keys. See ERC7579NativeSpender.isValidSignatureWithSender
for rationale.
_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
internal
#Validates a user operation. Adds a callData shape gate on top of session-key validation:
userOp.callData MUST call IERC7579Execution.execute(mode, executionCalldata) where:
modeisCALLTYPE_SINGLE.executionCalldatadecodes to(target=address(this), value=0, data).datacallsERC7579ERC20Spender.transferFromorERC7579ERC20Spender.batchTransferFrom.- The
keyparameter insidedatamatches the key inuserOp.signature.
Returns ERC4337Utils.SIG_VALIDATION_FAILED for any shape or key mismatch, otherwise delegates
to the parent for cryptographic verification and validity-range packing.
addKeys(address account, uint256 installation, bytes[] keys, uint48[] starts, uint48[] ends)
internal
#Override of ERC7579SessionKeys._addKeys that disallows registering a key with a past
start timepoint. See ERC7579NativeSpender._addKeys for the full rationale.
Requirements:
- Each
starts[i]must be either0or>= clock(). Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderInvalidStartotherwise. - Those from
ERC7579SessionKeys._addKeys.
Pass starts[i] == 0 to resolve the start to the current timepoint.
installed(address account, uint256 installation) → bool
internal
#Internal version of ERC7579ERC20Spender.installed.
tokensCount(address account, uint256 installation) → uint256
internal
#Internal version of ERC7579ERC20Spender.tokensCount.
renewPeriod(address account, uint256 installation, bytes32 keyId, address token) → uint256
internal
#Internal version of ERC7579ERC20Spender.renewPeriod.
limit(address account, uint256 installation, bytes32 keyId, address token) → uint256
internal
#Internal version of ERC7579ERC20Spender.limit.
spent(address account, uint256 installation, bytes32 keyId, address token, uint256 period, uint256 start) → uint256
internal
#Internal version of ERC7579ERC20Spender.spent with explicit start.
spendingAt(address account, uint256 installation, bytes32 keyId, address token, uint256 period, uint256 start) → uint256 spending, uint256 available
internal
#Internal version of ERC7579ERC20Spender.spendingAt with explicit start.
periodAt(address account, uint256 installation, bytes32 keyId, address token, uint48 timepoint) → uint256 start, uint256 period
internal
#Internal version of ERC7579ERC20Spender.periodAt with explicit installation_.
Requirements:
timepointmust be>=the key'sfrom. Reverts withERC7579ERC20Spender.ERC7579ERC20SpendingConfigNotStartedotherwise.- The spending config must be set. Reverts with
ERC7579ERC20Spender.ERC7579ERC20SpendingConfigNotSetotherwise.
checkAccountOrKeySender(address account, uint256 installation, bytes key)
internal
#Authorizes a ERC7579ERC20Spender.transferFrom / ERC7579ERC20Spender.batchTransferFrom call.
Accepts msg.sender as the account itself (executor flow, validator already verified) or as
a 20-byte EOA matching key (EOA self-call, validity is checked manually).
transferFrom(address account, uint256 installation, bytes key, address token, address to, uint256 amount)
internal
#Internal version of ERC7579ERC20Spender.transferFrom without access control.
batchTransferFrom(address account, uint256 installation, bytes key, address token, address[] tos, uint256[] amounts)
internal
#Internal version of ERC7579ERC20Spender.batchTransferFrom without access control.
setConfig(address account, uint256 installation, bytes key, address token, uint160 newLimit, uint48 newRenewPeriod)
internal
#Writes a spending config for (keyId, token) and adds the token to the per-installation
token set.
Requirements:
newRenewPeriod > 0. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderInvalidRenewPeriodotherwise.newLimit > 0. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderInvalidLimitotherwise.tokenmust be a contract. Reverts withERC7579ERC20Spender.ERC7579ERC20SpenderInvalidTokenotherwise.
_isAuthorizedCallData(struct PackedUserOperation userOp) → bool
internal
#Returns whether userOp.callData is a permitted call into ERC7579ERC20Spender.transferFrom or
ERC7579ERC20Spender.batchTransferFrom on this module, and whether its key argument matches the one
decoded from userOp.signature.
_validateSingleExecutionCalldata(bytes callData) → bool isSingleAndValid, bytes exec
internal
#_validateTransferKey(bytes exec) → bool success, bytes callDataKey
internal
#See ERC7579NativeSpender._validateTransferKey. The key argument is the second
parameter of both ERC7579ERC20Spender.transferFrom and ERC7579ERC20Spender.batchTransferFrom, so the offset slot lies at a
fixed position regardless of which selector is used.
ERC7579ERC20SpendingConfigSet(address indexed account, bytes indexed key, address indexed token, uint256 limit, uint256 renewPeriod)
event
#Emitted when the spending configuration is set or updated for a (key, token) pair.
ERC7579ERC20SpenderAlreadyInstalled()
error
#The account already has a spending configuration installed.
ERC7579ERC20InsufficientAvailable(address account, bytes key, address token, uint256 amount)
error
#The requested amount exceeds the available spending limit for (key, token).
ERC7579ERC20SpenderInvalidInitData()
error
#The provided initData is invalid or malformed.
ERC7579ERC20SpendingConfigNotSet(address account, bytes32 keyId, address token)
error
#The spending config is not set for (keyId, token).
ERC7579ERC20SpenderMismatchedInitDataLengths(uint256 keys, uint256 tokens, uint256 limits, uint256 renewPeriods)
error
#The arrays in the executor initData have mismatched lengths.
ERC7579ERC20SpenderEmptyKeys()
error
#The first install registered zero session keys, or the second install's keys array is empty.
ERC7579ERC20SpenderMismatchedBatchLengths(uint256 tos, uint256 amounts)
error
#The tos and amounts arrays have mismatched lengths in batch transfer.
ERC7579ERC20SpenderInvalidRenewPeriod(uint256 renewPeriod)
error
#The renewPeriod is invalid (must be greater than zero).
ERC7579ERC20SpenderInvalidLimit(uint256 limit)
error
#The limit is invalid (must be greater than zero).
ERC7579ERC20SpenderInvalidToken(address token)
error
#The token address is not a contract.
ERC7579ERC20SpendingConfigNotStarted(address account, bytes32 keyId, address token, uint256 timepoint, uint256 start)
error
#The spending config has not started yet for (keyId, token) at timepoint.
ERC7579ERC20SpenderUnauthorized(address caller, bytes key)
error
#The caller is not authorized to spend on behalf of key.
ERC7579ERC20SpenderInvalidStart(uint256 start)
error
#The start time is invalid (cannot reuse a past start time).
ERC7579ERC20SpenderFailedTransfer(address token)
error
#The ERC20 transfer call on token returned false.
import "@openzeppelin-accounts/contracts/modules/executors/ERC7579Executor.sol";Abstract executor module for ERC-7579 accounts.
This contract provides the base execution functionality for ERC-7579 accounts.
Developers must implement the ERC7579ECDSA.onInstall and ERC7579ECDSA.onUninstall functions, as well as
wrap ERC7579DelayedExecutor._execute in a custom external function in derived contracts to define the
specific validation and access control logic.
Example implementation:
contract MyExecutor is ERC7579Executor {
function execute(
address account,
bytes32 salt,
bytes32 mode,
bytes calldata data
) public virtual returns (bytes[] memory) {
// Implement your validation logic here
// e.g., signature verification, access control, time delays, etc.
// Then call the internal _execute function
return _execute(account, salt, mode, data);
}
}For a more advanced implementation with time-delayed execution patterns and
security features, see ERC7579DelayedExecutor.
Functions
isModuleType(uint256 moduleTypeId) → bool
public
#Returns boolean value if module is a certain type
_execute(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → bytes[]
internal
#Internal function to execute an operation through the account's executor interface.
Emits ERC7579Executor.ERC7579ExecutorOperationExecuted event.
Target addresses included in the executionCalldata may return a large amount of data,
which may cause the operation to fail due to insufficient gas given executeFromExecutor copies
all return datas to memory.
Requirements:
- The
accountmust implement theIERC7579Execution-executeFromExecutorfunction.
This function does not have any access control validation.
ERC7579ExecutorOperationExecuted(address indexed account, bytes32 salt, bytes32 mode, bytes executionCalldata)
event
#Emitted when an operation is executed.
import "@openzeppelin-accounts/contracts/modules/executors/ERC7579NativeSpender.sol";Extension of ERC7579SessionKeysValidator that provides native token (ETH) spending
limits granted to session keys with automatic renewal periods for ERC-7579 smart accounts.
This module allows accounts to configure spending allowances for external spenders, enabling use cases like subscription services, recurring payments, or delegated spending with predefined limits.
The module is both a validator and an executor:
- As a validator, it authorizes user operations signed by a registered session key whose
callDatais a single call into this module'sERC7579ERC20Spender.transferFromorERC7579ERC20Spender.batchTransferFrom. The key's validity range is packed intovalidationDataso the EntryPoint enforces it natively. - As an executor, it spends from the account through
executeFromExecutor, decrementing the per-key, per-period limit.
Each session key carries its own (limit, renewPeriod) spending policy. The start of the
spending periods is the session key's from timepoint. Periods automatically renew every
renewPeriod time units, allowing the same spending capacity to be used again without
requiring manual intervention. It implements ERC-6372 clock functions.
The module should be installed under both module types, in this order:
- Validator install (first):
initDataisabi.encode(bytes[] keys, uint48[] starts, uint48[] ends). This registers session keys with their validity ranges throughERC7579SessionKeys._addKeys. At least one key must be registered, otherwise the install reverts. - Executor install (second):
initDataisabi.encode(bytes[] keys, uint160[] limits, uint48[] renewPeriods). This writes a spending policy for each already-registered key. Thekeysarray MUST be non-empty and reconfiguration is banned: once a key has been configured, neither it nor any other key can be reconfigured under the current installation. To change the spending policy, the account must uninstall and reinstall the module (which bumps the installation nonce and resets state).
This module deliberately disables ERC-1271 signing
(ERC7579ERC20Spender.isValidSignatureWithSender always returns 0xffffffff) so a spending session key cannot be
repurposed to authorize arbitrary off-chain messages on behalf of the account.
Set start to a future timepoint to schedule a spending limit that activates later.
Set ERC7579ERC20Spender.renewPeriod to type(uint48).max to disable the renewal period.
Functions
- isModuleType(moduleTypeId)
- installed(account)
- renewPeriod(account, key)
- limit(account, key)
- spent(account, key, period)
- spent(account, key, period, start)
- spendingAt(account, key, period)
- periodAt(account, key, timepoint)
- onInstall(initData)
- addKeysWithConfig(newKeys, starts, ends, limits, renewPeriods)
- transferFrom(account, key, to, amount)
- batchTransferFrom(account, key, tos, amounts)
- isValidSignatureWithSender(, , )
- _validateUserOp(userOp, userOpHash)
- addKeys(account, installation, keys, starts, ends)
- removeKeys(account, installation, oldKeys)
- installed(account, installation)
- renewPeriod(account, installation, keyId)
- limit(account, installation, keyId)
- spent(account, installation, keyId, period, start)
- spendingAt(account, installation, keyId, period, start)
- periodAt(account, installation, keyId, timepoint)
- checkAccountOrKeySender(account, installation, key)
- transferFrom(account, installation, key, to, amount)
- batchTransferFrom(account, installation, key, tos, amounts)
- setConfig(account, installation, key, newLimit, newRenewPeriod)
- addKeysWithConfig(account, installation, keys, starts, ends, limits, renewPeriods)
- _isAuthorizedCallData(userOp)
- _validateSingleExecutionCalldata(callData)
- _validateTransferKey(exec)
- _decodeKeyConfigsInitData(initData)
ERC7579Executor
ERC7579SessionKeysValidator
ERC7579Validator
ERC7579SessionKeys
- clock()
- CLOCK_MODE()
- installation(account)
- isKey(account, key)
- validity(account, key)
- isValid(account, key)
- keys(account, startIdx, endIdx)
- keyCount(account)
- onUninstall()
- addKeys(newKeys, starts, ends)
- removeKeys(oldKeys)
- isKey(account, installation, key)
- validity(account, installation, keyId)
- isValid(account, installation, key)
- keys(account, installation, startIdx, endIdx)
- keyCount(account, installation)
- _validateSessionKey(account, hash, signature)
- validateSessionKey(account, installation, hash, signature)
- _keyId(key)
- _tryDecodeSignature(signature)
- _decodeKeysInitData(initData)
Events
Errors
- ERC7579NativeSpenderAlreadyInstalled()
- ERC7579NativeInsufficientAvailable(account, key, amount)
- ERC7579NativeSpenderInvalidInitData()
- ERC7579NativeSpendingConfigNotSet(account, keyId)
- ERC7579NativeSpenderMismatchedInitDataLengths(keys, limits, renewPeriods)
- ERC7579NativeSpenderEmptyKeys()
- ERC7579NativeSpenderMismatchedBatchLengths(tos, amounts)
- ERC7579NativeSpenderInvalidRenewPeriod(renewPeriod)
- ERC7579NativeSpenderInvalidLimit(limit)
- ERC7579NativeSpendingConfigNotStarted(account, keyId, timepoint, start)
- ERC7579NativeSpenderUnauthorized(caller, key)
- ERC7579NativeSpenderInvalidStart(start)
ERC7579SessionKeys
isModuleType(uint256 moduleTypeId) → bool
public
#Returns boolean value if module is a certain type
installed(address account) → bool
public
#Returns whether the module's first-install step has completed for account.
renewPeriod(address account, bytes key) → uint256
public
#Returns the renewal period for key's spending config on account.
limit(address account, bytes key) → uint256
public
#Returns the spending limit per period for key on account.
spent(address account, bytes key, uint256 period) → uint256
public
#Returns the amount spent by key during period on account, anchored at the key's from.
spent(address account, bytes key, uint256 period, uint256 start) → uint256
public
#Same as ERC7579ERC20Spender.spent but reads from an explicit start timepoint (useful for inspecting history).
spendingAt(address account, bytes key, uint256 period) → uint256 spending, uint256 available
public
#Returns (spending, available) for key at period on account.
periodAt(address account, bytes key, uint48 timepoint) → uint256 start, uint256 period
public
#Returns (start, period) for key on account at timepoint.
Requirements:
timepointmust be>=the key'sfrom. Reverts withERC7579NativeSpender.ERC7579NativeSpendingConfigNotStartedotherwise.- The spending config must be set. Reverts with
ERC7579NativeSpender.ERC7579NativeSpendingConfigNotSetotherwise.
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account.
This module is installed under BOTH module types and onInstall is called twice. The
shape of initData differs between the two calls:
- First call (validator install,
installed(msg.sender) == false):abi.encode(bytes[] keys, uint48[] starts, uint48[] ends). - Second call (executor install,
installed(msg.sender) == true):abi.encode(bytes[] keys, uint160[] limits, uint48[] renewPeriods).
First call requirements:
- The
keysarray must be non-empty. Reverts withERC7579NativeSpender.ERC7579NativeSpenderEmptyKeysotherwise. - Those from the parent's
ERC7579SessionKeys.onInstall(key registration).
Second call requirements:
- The account must not have configured the keys yet. Reverts with
ERC7579NativeSpender.ERC7579NativeSpenderAlreadyInstalledotherwise. - Those of
ERC7579NativeSpender._decodeKeyConfigsInitDatafor the decoding requirements. - All arrays must have equal length. Reverts with
ERC7579NativeSpender.ERC7579NativeSpenderMismatchedInitDataLengthsif not. - The
keysarray must be non-empty. Reverts withERC7579NativeSpender.ERC7579NativeSpenderEmptyKeysotherwise. - Each key must be registered. Reverts with
ERC7579SessionKeys.ERC7579SessionKeysNonexistentKeyotherwise. - Those from
ERC7579ERC20Spender._setConfigfor the spending config requirements.
Emits ERC7579SessionKeys.ERC7579SessionKeysERC7913KeySet on the first call and ERC7579NativeSpender.ERC7579NativeSpendingConfigSet
for each entry on the second call.
Each start will be set to the current timepoint if it's 0.
addKeysWithConfig(bytes[] newKeys, uint48[] starts, uint48[] ends, uint160[] limits, uint48[] renewPeriods)
public
#Adds new session keys with their spending configurations in a single call. Can only be
called by the account itself. See ERC7579NativeSpender._addKeysWithConfig for requirements.
transferFrom(address account, bytes key, address to, uint256 amount)
public
#Transfers native tokens from account to to on behalf of key.
This function tracks the spending limit for the key on the account and updates
the spent amount for the current period before executing the transfer through the account's
IERC7579Execution-executeFromExecutor function.
Two authorization paths are accepted:
- Executor flow:
msg.sender == account. The validator has already verified the session key's signature and the calldata shape, so no further checks are performed here. - EOA self-call:
key.length == 20andaddress(bytes20(key)) == msg.sender. The key's validity range is enforced manually.
Requirements:
- The caller is authorized via one of the two paths above. Reverts with
ERC7579NativeSpender.ERC7579NativeSpenderUnauthorizedotherwise. amountmust not exceed the available limit for the current period. Reverts withERC7579NativeSpender.ERC7579NativeInsufficientAvailableotherwise.- Those from
ERC7579ERC20Spender._periodAtfor the current timepoint.
batchTransferFrom(address account, bytes key, address[] tos, uint256[] amounts)
public
#Transfers native tokens from account to multiple recipients in a single batch.
Same authorization paths as ERC7579ERC20Spender.transferFrom. The available limit check is performed once
against the total of amounts.
Requirements:
tos.length == amounts.length. Reverts withERC7579NativeSpender.ERC7579NativeSpenderMismatchedBatchLengthsotherwise.- The caller is authorized via one of the two paths described in
ERC7579ERC20Spender.transferFrom. - The sum of
amountsmust not exceed the available limit. - Those from
ERC7579ERC20Spender._periodAtfor the current timepoint.
isValidSignatureWithSender(address, bytes32, bytes) → bytes4
public
#Disables ERC-1271 signing by session keys.
A spending session key is scoped to authorize ERC7579ERC20Spender.transferFrom / ERC7579ERC20Spender.batchTransferFrom user
operations on this module. Letting it sign arbitrary ERC-1271 digests (e.g. EIP-2612
permits) would let a compromised key bypass the spending bound entirely.
_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
internal
#Validates a user operation. Adds a callData shape gate on top of session-key validation:
userOp.callData MUST call IERC7579Execution.execute(mode, executionCalldata) where:
modeisCALLTYPE_SINGLE(other call types are rejected).executionCalldatadecodes to(target=address(this), value=0, data).datacallsERC7579ERC20Spender.transferFromorERC7579ERC20Spender.batchTransferFrom.- The
keyparameter insidedatamatches the key inuserOp.signature(prevents one key's signature from charging another key's allowance).
Returns ERC4337Utils.SIG_VALIDATION_FAILED for any shape or key mismatch, otherwise delegates
to the parent for cryptographic verification and validity-range packing.
addKeys(address account, uint256 installation, bytes[] keys, uint48[] starts, uint48[] ends)
internal
#Override of ERC7579SessionKeys._addKeys that disallows registering a key with a past
start timepoint.
The spending bookkeeping in ERC7579ERC20Spender._spentFor is indexed by (keyId, start, period). Letting a
removed-then-re-registered key pick a past start would let it inherit _spentFor slots
written under a previous validity range, which (combined with a different renewPeriod)
can collide with previously-used period slots. Strictly incremental start values keep
each registration's accounting in its own slot space.
Requirements:
- Each
starts[i]must be either0or>= clock(). Reverts withERC7579NativeSpender.ERC7579NativeSpenderInvalidStartotherwise. - Those from
ERC7579SessionKeys._addKeys.
Pass starts[i] == 0 to resolve the start to the current timepoint.
removeKeys(address account, uint256 installation, bytes[] oldKeys)
internal
#Removes session keys from the calling account. Also clears the spending configuration for each key.
Spending history (per-period spent values) is preserved in storage at the historical
start timepoint. It is no longer surfaced through spent-address-bytes-uint256-- (which uses
the current ERC7579SessionKeys.validity's from, now zero) but can still be read via
spent-address-bytes-uint256-uint256-- with the historical start.
Emits ERC7579SessionKeys.ERC7579SessionKeysERC7913KeyUnset (via the parent) and ERC7579NativeSpender.ERC7579NativeSpendingConfigUnset
for each key.
installed(address account, uint256 installation) → bool
internal
#Internal version of ERC7579ERC20Spender.installed.
renewPeriod(address account, uint256 installation, bytes32 keyId) → uint256
internal
#Internal version of ERC7579ERC20Spender.renewPeriod.
limit(address account, uint256 installation, bytes32 keyId) → uint256
internal
#Internal version of ERC7579ERC20Spender.limit.
spent(address account, uint256 installation, bytes32 keyId, uint256 period, uint256 start) → uint256
internal
#Internal version of ERC7579ERC20Spender.spent with explicit start.
spendingAt(address account, uint256 installation, bytes32 keyId, uint256 period, uint256 start) → uint256 spending, uint256 available
internal
#Internal version of ERC7579ERC20Spender.spendingAt with explicit start.
periodAt(address account, uint256 installation, bytes32 keyId, uint48 timepoint) → uint256 start, uint256 period
internal
#Internal version of ERC7579ERC20Spender.periodAt with explicit installation_.
Requirements:
timepointmust be>=the key'sfrom. Reverts withERC7579NativeSpender.ERC7579NativeSpendingConfigNotStartedotherwise.- The spending config must be set. Reverts with
ERC7579NativeSpender.ERC7579NativeSpendingConfigNotSetotherwise.
checkAccountOrKeySender(address account, uint256 installation, bytes key)
internal
#Authorizes a ERC7579ERC20Spender.transferFrom / ERC7579ERC20Spender.batchTransferFrom call.
Accepts msg.sender as the account itself (executor flow, validator already verified) or as
a 20-byte EOA matching key (EOA self-call, validity is checked manually).
transferFrom(address account, uint256 installation, bytes key, address to, uint256 amount)
internal
#Internal version of ERC7579ERC20Spender.transferFrom without access control.
batchTransferFrom(address account, uint256 installation, bytes key, address[] tos, uint256[] amounts)
internal
#Internal version of ERC7579ERC20Spender.batchTransferFrom without access control.
setConfig(address account, uint256 installation, bytes key, uint160 newLimit, uint48 newRenewPeriod)
internal
#Internal function to set the spending configuration for a key.
Reconfiguration is banned at the ERC7579ECDSA.onInstall level, so this function is only ever invoked
with a freshly-registered (or freshly-reinstalled) key whose existing config slot is zero.
Requirements:
- Renewal periods must be greater than zero. Reverts with
ERC7579NativeSpender.ERC7579NativeSpenderInvalidRenewPeriodif not. - Limits must be greater than zero. Reverts with
ERC7579NativeSpender.ERC7579NativeSpenderInvalidLimitif not.
Emits a ERC7579NativeSpender.ERC7579NativeSpendingConfigSet event.
addKeysWithConfig(address account, uint256 installation, bytes[] keys, uint48[] starts, uint48[] ends, uint160[] limits, uint48[] renewPeriods)
internal
#Registers keys with validity range AND spending config in one pass.
Requirements:
newKeys.length == limits.length == renewPeriods.length. Reverts withERC7579NativeSpender.ERC7579NativeSpenderMismatchedInitDataLengthsotherwise.- Those from the parent's
ERC7579SessionKeys._addKeys(key registration). - Those from
ERC7579ERC20Spender._setConfigfor the spending config requirements.
Emits ERC7579SessionKeys.ERC7579SessionKeysERC7913KeySet and ERC7579NativeSpender.ERC7579NativeSpendingConfigSet for each key.
_isAuthorizedCallData(struct PackedUserOperation userOp) → bool
internal
#Returns whether userOp.callData is a permitted call into ERC7579ERC20Spender.transferFrom or
ERC7579ERC20Spender.batchTransferFrom on this module, and whether its key argument matches the one
decoded from userOp.signature.
_validateSingleExecutionCalldata(bytes callData) → bool isSingleAndValid, bytes exec
internal
#Decodes the outer IERC7579Execution.execute(SINGLE, executionCalldata) call.
Returns (false, "") if the call doesn't match the expected shape.
_validateTransferKey(bytes exec) → bool success, bytes callDataKey
internal
#Verifies the inner single execution targets this module with zero value, calls
ERC7579ERC20Spender.transferFrom or ERC7579ERC20Spender.batchTransferFrom, and returns the key argument extracted from the
inner calldata.
Returns (false, "") if any check fails.
_decodeKeyConfigsInitData(bytes initData) → bytes[] keys, uint160[] limits, uint48[] renewPeriods
internal
#Decodes the initialization data for the second install of the module.
The initData must be ABI-encoded as abi.encode(bytes[] keys, uint160[] limits, uint48[] renewPeriods)
where each array has the same length.
Requirements:
initDatamust be at least 192 bytes (6 × 32 bytes for the 3 ABI offsets and 3 array lengths).- All offsets must point within the
initDatabounds.
Reverts with ERC7579NativeSpender.ERC7579NativeSpenderInvalidInitData if the encoding is invalid.
ERC7579NativeSpendingConfigSet(address indexed account, bytes indexed key, uint256 limit, uint256 renewPeriod)
event
#Emitted when the spending configuration is set or updated for a key.
ERC7579NativeSpendingConfigUnset(address indexed account, bytes indexed key)
event
#Emitted when the spending configuration is removed for a key.
ERC7579NativeSpenderAlreadyInstalled()
error
#The account already has spenders installed.
ERC7579NativeInsufficientAvailable(address account, bytes key, uint256 amount)
error
#The requested amount exceeds the available spending limit for key.
ERC7579NativeSpenderInvalidInitData()
error
#The provided initData is invalid or malformed.
ERC7579NativeSpendingConfigNotSet(address account, bytes32 keyId)
error
#The spending config is not set for key.
ERC7579NativeSpenderMismatchedInitDataLengths(uint256 keys, uint256 limits, uint256 renewPeriods)
error
#The arrays in initData have mismatched lengths.
ERC7579NativeSpenderEmptyKeys()
error
#The first install registered zero session keys.
ERC7579NativeSpenderMismatchedBatchLengths(uint256 tos, uint256 amounts)
error
#The tos and amounts arrays have mismatched lengths in batch transfer.
ERC7579NativeSpenderInvalidRenewPeriod(uint256 renewPeriod)
error
#The renewPeriod is invalid (must be greater than zero).
ERC7579NativeSpenderInvalidLimit(uint256 limit)
error
#The limit is invalid (must be greater than zero).
ERC7579NativeSpendingConfigNotStarted(address account, bytes32 keyId, uint256 timepoint, uint256 start)
error
#The spending config has not started yet for key at timepoint.
ERC7579NativeSpenderUnauthorized(address caller, bytes key)
error
#The caller is not authorized to spend on behalf of key.
ERC7579NativeSpenderInvalidStart(uint256 start)
error
#The start time is invalid (cannot reuse a past start time).
import "@openzeppelin-accounts/contracts/modules/executors/ERC7579SocialRecoveryExecutor.sol";Social recovery executor module for ERC-7579 accounts.
This module combines weighted multisignatures with time-delayed execution to provide a secure and flexible social recovery mechanism. Trusted guardians can collectively schedule recovery operations, which execute after a security delay, giving the legitimate account owner time to detect and cancel malicious recovery attempts.
To schedule operations, guardians must sign an EIP-712 typed data hash of the operation using
the account's domain separator and provide it to the ERC7579SocialRecoveryExecutor.startRecovery function. Once scheduled,
operations must wait for the configured delay period before becoming executable via ERC7579SocialRecoveryExecutor.executeRecovery.
Operations can be canceled either by the account itself or by guardians via ERC7579SocialRecoveryExecutor.cancelRecovery
with a valid multisignature.
See ERC7579DelayedExecutor and ERC7579MultisigWeighted for more details.
This module does not restrict execution by default once the social recovery operation
is ready. Anyone can call ERC7579SocialRecoveryExecutor.executeRecovery once the delay period has elapsed.
Functions
- onInstall(data)
- onUninstall()
- getGuardians(account, start, end)
- getGuardianCount(account)
- isGuardian(account, guardian)
- addGuardians(newGuardians)
- removeGuardians(oldGuardians)
- startRecovery(account, salt, mode, recoveryData, multisignature)
- executeRecovery(account, salt, mode, recoveryData)
- cancelRecovery(account, salt, mode, recoveryData, multisignature)
- cancelRecovery(account, salt, mode, recoveryData)
- SCHEDULE_RECOVER_TYPEHASH()
- CANCEL_RECOVER_TYPEHASH()
ERC7579MultisigWeighted
- signerWeight(account, signer)
- totalWeight(account)
- setSignerWeights(signers, weights)
- _decodeMultisigWeightedInitData(initData)
- _setSignerWeights(account, signers, weights)
- _addSigners(account, newSigners)
- _removeSigners(account, oldSigners)
- _validateReachableThreshold(account)
- _validateThreshold(account, validatingSigners)
ERC7579Multisig
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signer)
- threshold(account)
- addSigners(newSigners)
- removeSigners(oldSigners)
- setThreshold(newThreshold)
- _setThreshold(account, newThreshold)
- _validateMultisig(account, hash, signingSigners, signatures)
- _validateSignatures(account, hash, signingSigners, signatures)
- _decodeMultisigInitData(initData)
ERC7579DelayedExecutor
- state(account, salt, mode, executionCalldata)
- state(operationId)
- delayChangeWindow()
- getDelay(account)
- getExpiration(account)
- getSchedule(account, salt, mode, executionCalldata)
- getSchedule(operationId)
- hashOperation(account, salt, mode, executionCalldata)
- defaultExpiration()
- setDelay(newDelay)
- setExpiration(newExpiration)
- _setDelay(account, newDelay, minimumSetback)
- _setExpiration(account, newExpiration)
- _schedule(account, salt, mode, data)
- _scheduleAt(account, salt, mode, executionCalldata, timepoint, delay)
- _execute(account, salt, mode, executionCalldata)
- _cancel(account, salt, mode, executionCalldata)
- _decodeDelayedExecutorInitData(initData)
- _validateStateBitmap(operationId, allowedStates)
- _encodeStateBitmap(operationState)
ERC7579Executor
Events
ERC7579MultisigWeighted
ERC7579Multisig
ERC7579DelayedExecutor
Errors
ERC7579MultisigWeighted
ERC7579Multisig
onInstall(bytes data)
public
#Installs the social recovery executor module on an account.
This function configures both the delayed executor and weighted multisig components required for the social recovery mechanism. The installation data must contain configuration for both components in the specified encoding format.
Data encoding format: [uint16(executorArgsLength), executorArgs, uint16(multisigArgsLength), multisigArgs]
Requirements:
- See
ERC7579DelayedExecutor.onInstallfor delay/expiration requirements - See
ERC7579MultisigWeighted.onInstallfor guardian configuration requirements
onUninstall(bytes)
public
#Uninstalls the social recovery executor module from an account.
This cleans up both the delayed executor configuration and all guardian-related state. After uninstallation, all social recovery capabilities are removed.
Ensure alternative recovery mechanisms or access methods are in place before uninstalling this module. Uninstalling social recovery without a backup access method could result in account lockout if primary credentials are lost.
See ERC7579DelayedExecutor.onUninstall and ERC7579MultisigWeighted.onUninstall.
getGuardians(address account, uint64 start, uint64 end) → bytes[]
public
#Returns a slice of the set of guardians for the specified account. See ERC7579Multisig.getSigners.
getGuardianCount(address account) → uint256
public
#Returns the number of guardians for the specified account. See ERC7579Multisig.getSignerCount.
isGuardian(address account, bytes guardian) → bool
public
#Returns whether the guardian is an authorized guardian for the specified account. See ERC7579Multisig.isSigner.
addGuardians(bytes[] newGuardians)
public
#Adds new guardians to the authorized set for the calling account. See ERC7579Multisig.addSigners.
removeGuardians(bytes[] oldGuardians)
public
#Removes guardians from the authorized set for the calling account. See ERC7579Multisig.removeSigners.
startRecovery(address account, bytes32 salt, bytes32 mode, bytes recoveryData, bytes multisignature)
public
#Schedules a social recovery operation for the specified account.
The recoveryData is the encoded execution that will be sent to the account, and it
follows the ERC-7579 standard encoding. The multisignature is a valid authorization
from the guardians to schedule the operation and must be signed over the
ERC7579SocialRecoveryExecutor.SCHEDULE_RECOVER_TYPEHASH typed data hash of the operation using the account's domain
separator.
Requirements:
- The multisignature must be valid and meet the threshold requirements.
Reverts with
ERC7579SocialRecoveryExecutor.ERC7579SocialRecoveryExecutorInvalidScheduleif not. - The signature must be encoded properly. See
ERC7579MultisigWeighted-_validateMultisig. - See
ERC7579DelayedExecutor._schedulefor scheduling requirements. - The
accountmust implementIERC5267.
executeRecovery(address account, bytes32 salt, bytes32 mode, bytes recoveryData)
public
#Executes a social recovery operation for the specified account.
This function can be called by any account once the operation is ready (i.e., the delay
period has elapsed). The operation must be in the Ready state to be executed.
Requirements:
- See
ERC7579DelayedExecutor._executefor execution requirements.
cancelRecovery(address account, bytes32 salt, bytes32 mode, bytes recoveryData, bytes multisignature)
public
#Cancels a social recovery operation.
This function allows either the account itself or guardians with a valid multisignature
authorization to cancel a scheduled recovery operation. If the caller is not the account,
the multisignature must contain valid guardian signatures over the ERC7579SocialRecoveryExecutor.CANCEL_RECOVER_TYPEHASH
typed data hash using the account's domain separator.
Uninstalling the module resets the threshold to zero while scheduled operations are retained
(see ERC7579DelayedExecutor.onUninstall). Guardian-authorized cancellation is therefore only
available while a guardian configuration is active. The account keeps its own cancellation path
in either case.
Requirements:
- The caller must be the account or a valid
multisignaturemust be provided. - A
multisignatureonly authorizes the cancellation while theERC7579Multisig.thresholdis nonzero. - See
ERC7579DelayedExecutor._cancelfor cancellation requirements. - The
accountmust implementIERC5267.
cancelRecovery(address account, bytes32 salt, bytes32 mode, bytes recoveryData)
public
#Same as ERC7579SocialRecoveryExecutor.cancelRecovery, but without a multisignature.
SCHEDULE_RECOVER_TYPEHASH() → bytes32
public
#CANCEL_RECOVER_TYPEHASH() → bytes32
public
#ERC7579SocialRecoveryExecutorInvalidSchedule()
error
#The multisignature validation failed.
ERC7579SocialRecoveryExecutorInvalidCancel()
error
#The caller is not authorized to cancel the operation.
import "@openzeppelin-accounts/contracts/modules/validators/ERC7579ECDSAValidator.sol";Implementation of an ERC7579ECDSA as an ERC7579Validator.
This module provides ECDSA signature validation for ERC-7579 accounts. Each account can have a single authorized signer (EOA address) that can approve operations through ECDSA signatures.
Example usage:
// Install the validator with a signer address
bytes memory initData = abi.encodePacked(signerAddress);
account.installModule(MODULE_TYPE_VALIDATOR, validatorAddress, initData);Functions
ERC7579Validator
Events
ERC7579ECDSA
Errors
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
#import "@openzeppelin-accounts/contracts/modules/validators/ERC7579MultisigConfidentialValidator.sol";Implementation of ERC7579Validator that uses ERC7579MultisigConfidential
for confidential multisignature validation of user operations (see ERC-4337).
This extension bypasses the explicit ERC7579MultisigConfidential.discloseOperation call
and performs disclosure implicitly inside _rawERC7579Validation: when the user op
signature includes a valid FHE decryption proof for the multisig result (relayer address
plus proof after the first 20 bytes), the validator accepts the user op without requiring
a prior call to discloseOperation. This reduces the flow to two steps:
- Call
ERC7579MultisigConfidential.requestDiscloseOperationto validate the multisig and make the encrypted result publicly decryptable. - Submit the user operation to the entrypoint with the decryption proof in the signature;
the entrypoint calls
validateUserOp, which invokes_rawERC7579Validationto verify the proof (or that the operation was explicitly disclosed viaERC7579MultisigConfidential.disclosed).
The operation is not marked as disclosed in storage when validation succeeds via the
inline proof. That is intentional: the fact that the user op executed implies the
multisig was valid and effectively disclosed, and offchain indexers can infer it by
watching entrypoint events instead of relying on the ERC7579MultisigConfidential.disclosed boolean.
By definition, the user operation is public in the bundler mempool the moment it's submitted. Users must not assume that the user operation is confidential, and should consider front running risks associated with the user operation payload.
Functions
ERC7579Validator
ERC7579MultisigConfidential
- onInstall(initData)
- onUninstall()
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signerId)
- threshold(account)
- requestedDisclosure(relayer, account, hash)
- disclosed(account, hash)
- addSigners(newSignersIds, inputProof)
- removeSigners(oldSignersIds)
- setThreshold(newThreshold, inputProof)
- requestDiscloseOperation(account, hash, signingSigners, signerIdHandles, signatures)
- discloseOperation(relayer, account, hash, decryptionProof)
- discloseOperation(account, hash, decryptionProof)
- dismissDisclosure()
- _addSigners(account, newSignerIds)
- _removeSigners(account, oldSignerIds)
- _setThreshold(account, newThreshold)
- _maxThreshold(account)
- _requestDiscloseOperation(relayer, account, hash, valid)
- _discloseOperation(relayer, account, hash, decryptionProof)
- _validateMultisig(account, hash, signingSigners, signerIdHandles, signatures)
- _validateSignatures(account, hash, signingSigners, signerIdHandles, signatures)
- _signerWeight(account, signerId)
- _validateThreshold(account, reachedThreshold)
- _decodeMultisigInitData(initData)
Events
Errors
ERC7579MultisigConfidential
- ERC7579MultisigInvalidInitData()
- ERC7579MultisigMismatchedValidationLength(signingSignersLength, signerIdHandlesLength, signaturesLength)
- ERC7579MultisigTooManySigners()
- ERC7579MultisigEmptySigners()
- ERC7579MultisigNonexistentSigner(signerId)
- ERC7579MultisigAlreadyInstalled()
- ERC7579MultisigDuplicateHandle(signerId)
- ERC7579MultisigUnknownDisclosure(hash)
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
#Validates the operation by either verifying an inline FHE decryption proof
in the signature (relayer address in the first 20 bytes, proof thereafter) or by
checking ERC7579MultisigConfidential.disclosed. Does not call ERC7579MultisigConfidential.discloseOperation
or set the disclosed flag when the proof is valid; disclosure is implicit in execution.
import "@openzeppelin-accounts/contracts/modules/validators/ERC7579MultisigValidator.sol";Implementation of ERC7579Validator that uses ERC7579Multisig for multisignature validation.
Functions
ERC7579Validator
ERC7579Multisig
- onInstall(initData)
- onUninstall()
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signer)
- threshold(account)
- addSigners(newSigners)
- removeSigners(oldSigners)
- setThreshold(newThreshold)
- _addSigners(account, newSigners)
- _removeSigners(account, oldSigners)
- _setThreshold(account, newThreshold)
- _validateReachableThreshold(account)
- _validateMultisig(account, hash, signingSigners, signatures)
- _validateSignatures(account, hash, signingSigners, signatures)
- _validateThreshold(account, validatingSigners)
- _decodeMultisigInitData(initData)
Events
Errors
ERC7579Multisig
_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.
See Multisig.tryDecodeMultisignatureCalldata for the encoding details.
Where signingSigners are the authorized signers and signatures are their corresponding
signatures of the operation hash.
import "@openzeppelin-accounts/contracts/modules/validators/ERC7579MultisigWeightedConfidentialValidator.sol";Extension of ERC7579MultisigConfidentialValidator that uses ERC7579MultisigWeightedConfidential
for confidential multisignature validation with weighted signatures.
Functions
- onInstall(initData)
- onUninstall(data)
- _addSigners(account, newSigners)
- _removeSigners(account, oldSigners)
- _maxThreshold(account)
- _signerWeight(account, signer)
ERC7579MultisigWeightedConfidential
- extraWeight(account, signer)
- totalExtraWeight(account)
- signerWeight(account, signer)
- totalWeight(account)
- setSignerWeights(signers, externalWeights, inputProof)
- _decodeMultisigWeightedInitData(initData)
- _setSignerWeights(account, signers, weights)
- _setTotalExtraWeight(account, newTotalExtraWeight)
- _setExtraWeight(account, signer, newExtraWeight)
- _totalWeight(account)
ERC7579MultisigConfidentialValidator
ERC7579Validator
ERC7579MultisigConfidential
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signerId)
- threshold(account)
- requestedDisclosure(relayer, account, hash)
- disclosed(account, hash)
- addSigners(newSignersIds, inputProof)
- removeSigners(oldSignersIds)
- setThreshold(newThreshold, inputProof)
- requestDiscloseOperation(account, hash, signingSigners, signerIdHandles, signatures)
- discloseOperation(relayer, account, hash, decryptionProof)
- discloseOperation(account, hash, decryptionProof)
- dismissDisclosure()
- _setThreshold(account, newThreshold)
- _requestDiscloseOperation(relayer, account, hash, valid)
- _discloseOperation(relayer, account, hash, decryptionProof)
- _validateMultisig(account, hash, signingSigners, signerIdHandles, signatures)
- _validateSignatures(account, hash, signingSigners, signerIdHandles, signatures)
- _validateThreshold(account, reachedThreshold)
- _decodeMultisigInitData(initData)
Events
ERC7579MultisigWeightedConfidential
Errors
ERC7579MultisigWeightedConfidential
ERC7579MultisigConfidential
- ERC7579MultisigInvalidInitData()
- ERC7579MultisigMismatchedValidationLength(signingSignersLength, signerIdHandlesLength, signaturesLength)
- ERC7579MultisigTooManySigners()
- ERC7579MultisigEmptySigners()
- ERC7579MultisigNonexistentSigner(signerId)
- ERC7579MultisigAlreadyInstalled()
- ERC7579MultisigDuplicateHandle(signerId)
- ERC7579MultisigUnknownDisclosure(hash)
onInstall(bytes initData)
public
#Sets up the module's initial configuration when installed by an account. Besides the standard signer and threshold configuration, this can also include encrypted signer weights.
See ERC7579MultisigWeighted._decodeMultisigWeightedInitData for the initData encoding details.
An account can only call onInstall once. If called directly by the account,
the signers, weights, and threshold will be set to the provided data. Future
installations will revert with ERC7579Multisig.ERC7579MultisigAlreadyInstalled.
Requirements:
- The
accountmust have no signers installed. Reverts withERC7579Multisig.ERC7579MultisigAlreadyInstalledif not. - The
signersarray must be non-empty. Reverts withERC7579Multisig.ERC7579MultisigEmptySignersif not. - Those of
ERC7579MultisigWeighted._decodeMultisigWeightedInitDatafor the decoding requirements. - Those of
ERC7579Multisig.addSignersandERC7579MultisigWeighted.setSignerWeightsfor signer and weight configuration requirements.
onUninstall(bytes data)
public
#Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.
_addSigners(address account, euint256[] newSigners)
internal
#Override to add weight tracking and restrict signer count. See ERC7579MultisigConfidential._addSigners.
Each new signer has a default weight of 1.
Requirements:
- The number of signers must not exceed
type(uint16).max. Reverts withERC7579MultisigConfidential.ERC7579MultisigTooManySignersif exceeded.
_removeSigners(address account, euint256[] oldSigners)
internal
#Override to handle weight tracking during removal. See ERC7579MultisigConfidential._removeSigners.
Cleans up weights for removed signers and updates the total extra weight accordingly.
_maxThreshold(address account) → euint32
internal
#Override to return the maximum threshold based on total weight instead of signer count.
See ERC7579MultisigConfidential._maxThreshold.
_signerWeight(address account, euint256 signer) → euint16
internal
#Override to return the encrypted weight of a signer instead of a fixed weight of 1.
See ERC7579MultisigConfidential._signerWeight.
Returns 0 if the signer is not authorized, otherwise returns 1 plus the signer's extra weight.
import "@openzeppelin-accounts/contracts/modules/validators/ERC7579MultisigWeightedValidator.sol";Extension of ERC7579MultisigValidator that uses ERC7579MultisigWeighted for multisignature validation
with weighted signatures.
Functions
- onInstall(initData)
- onUninstall(data)
- _addSigners(account, newSigners)
- _removeSigners(account, oldSigners)
- _validateReachableThreshold(account)
- _validateThreshold(account, validatingSigners)
ERC7579MultisigWeighted
ERC7579MultisigValidator
ERC7579Validator
ERC7579Multisig
- getSigners(account, start, end)
- getSignerCount(account)
- isSigner(account, signer)
- threshold(account)
- addSigners(newSigners)
- removeSigners(oldSigners)
- setThreshold(newThreshold)
- _setThreshold(account, newThreshold)
- _validateMultisig(account, hash, signingSigners, signatures)
- _validateSignatures(account, hash, signingSigners, signatures)
- _decodeMultisigInitData(initData)
Events
ERC7579MultisigWeighted
Errors
ERC7579MultisigWeighted
ERC7579Multisig
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, uint64 threshold, uint64[] weights)
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 revert with
ERC7579Multisig.ERC7579MultisigAlreadyInstalled.
This function skips super.onInstall to avoid setting the threshold during installation
since it can be greater than the total amount of signers. This function adds the signers, sets the weights,
and sets the threshold.
Requirements:
- The
accountmust have no signers installed. Reverts withERC7579Multisig.ERC7579MultisigAlreadyInstalledif not. - Those of
ERC7579MultisigWeighted._decodeMultisigWeightedInitDatafor the decoding requirements.
onUninstall(bytes data)
public
#Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.
_addSigners(address account, bytes[] newSigners)
internal
#Override to add weight tracking. See ERC7579Multisig._addSigners.
Each new signer has a default weight of 1.
In cases where ERC7579MultisigWeighted.totalWeight is almost type(uint64).max (due to a large _totalExtraWeight), adding new
signers could cause the ERC7579MultisigWeighted.totalWeight computation to overflow. Adding a ERC7579MultisigWeighted.totalWeight call after the new
signers are added ensures no such overflow happens.
_removeSigners(address account, bytes[] oldSigners)
internal
#Override to handle weight tracking during removal. See ERC7579Multisig._removeSigners.
Just like ERC7579Multisig._addSigners, this function does not emit ERC7579MultisigWeighted.ERC7579MultisigWeightChanged events. The
ERC7579Multisig.ERC7913SignerRemoved event emitted by ERC7579Multisig._removeSigners is enough to track weights here.
_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 ERC7579Multisig.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.
import "@openzeppelin-accounts/contracts/modules/validators/ERC7579P256Validator.sol";Implementation of an ERC7579P256 as an ERC7579Validator.
This module provides P256 signature validation for ERC-7579 accounts. Each account can have a single authorized P256 signer (public key coordinates) that can approve operations through P256 signatures.
Example usage:
// Install the validator with P256 public key coordinates
bytes memory initData = abi.encodePacked(qx, qy);
account.installModule(MODULE_TYPE_VALIDATOR, validatorAddress, initData);Functions
ERC7579Validator
Events
ERC7579P256
Errors
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
#import "@openzeppelin-accounts/contracts/modules/validators/ERC7579SessionKeysValidator.sol";Implementation of ERC7579SessionKeys as an ERC7579Validator.
This module provides session key validation for ERC-7579 accounts. Each account can have
multiple registered ERC-7913 session keys, each with a configurable validity range. A
session key is only authorized to validate operations within its from to until timepoints.
By default, any registered key within its validity range can validate any user operation and sign any ERC-1271 message on behalf of the account. Derived contracts should restrict the scope of what a session key is authorized to do.
Example usage:
// Install the validator with session keys
bytes memory initData = abi.encode(keys, starts, ends);
account.installModule(MODULE_TYPE_VALIDATOR, validatorAddress, initData);Functions
- isValidSignatureWithSender(sender, hash, signature)
- _validateUserOp(userOp, userOpHash)
- _rawERC7579Validation(account, hash, signature)
ERC7579Validator
ERC7579SessionKeys
- clock()
- CLOCK_MODE()
- installation(account)
- isKey(account, key)
- validity(account, key)
- isValid(account, key)
- keys(account, startIdx, endIdx)
- keyCount(account)
- onInstall(initData)
- onUninstall()
- addKeys(newKeys, starts, ends)
- removeKeys(oldKeys)
- isKey(account, installation, key)
- validity(account, installation, keyId)
- isValid(account, installation, key)
- keys(account, installation, startIdx, endIdx)
- keyCount(account, installation)
- addKeys(account, installation, keys_, starts, ends)
- removeKeys(account, installation, keys_)
- _validateSessionKey(account, hash, signature)
- validateSessionKey(account, installation, hash, signature)
- _keyId(key)
- _tryDecodeSignature(signature)
- _decodeKeysInitData(initData)
Events
Errors
isValidSignatureWithSender(address sender, bytes32 hash, bytes signature) → bytes4
public
#See IERC7579Validator-isValidSignatureWithSender.
Enforces the validity range of the session key manually since the EntryPoint is not
involved in ERC-1271 signature validation. See ERC7579SessionKeys._tryDecodeSignature for the encoding details.
_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
internal
#See ERC7579Validator._validateUserOp.
Decodes the session key from userOp.signature and reads its validity range. The range
is packed into the returned validationData so the EntryPoint enforces it without
requiring an explicit timestamp check in the validator.
See ERC7579SessionKeys._tryDecodeSignature for the signature encoding details.
Returns ERC4337Utils.SIG_VALIDATION_FAILED immediately if ERC7579SessionKeys.CLOCK_MODE returns a mode
other than timestamp or blocknumber from the same chain (default), as the EntryPoint does not support
other validity range formats.
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
#import "@openzeppelin-accounts/contracts/modules/validators/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 ERC7579ECDSA.onInstall, ERC7579ECDSA.onUninstall, and ERC7579ECDSAValidator._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 ERC7579ECDSAValidator._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
}Functions
isModuleType(uint256 moduleTypeId) → bool
public
#Returns boolean value if module is a certain type
validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
public
#Validates a UserOperation
_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256
internal
#isValidSignatureWithSender(address, bytes32 hash, bytes signature) → bytes4
public
#See IERC7579Validator-isValidSignatureWithSender.
Ignores the sender parameter and validates using ERC7579ECDSAValidator._rawERC7579Validation.
Consider overriding this function to implement custom validation logic
based on the original sender.
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
#Validation algorithm.
Validation is a critical security function. Implementations must carefully handle cryptographic verification to prevent unauthorized access.
import "@openzeppelin-accounts/contracts/modules/validators/ERC7579WebAuthnValidator.sol";Implementation of an ERC7579WebAuthn as an ERC7579P256Validator.
This module provides WebAuthn signature validation for ERC-7579 accounts. Each account can have a single authorized WebAuthn credential (P256 public key) that can approve operations through WebAuthn authentication assertions.
Example usage:
// Install the validator with WebAuthn public key coordinates
bytes memory initData = abi.encodePacked(qx, qy);
account.installModule(MODULE_TYPE_VALIDATOR, validatorAddress, initData);Functions
ERC7579Validator
ERC7579WebAuthn
Events
ERC7579P256
Errors
_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool
internal
#