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.

Signers

ERC7579ECDSA

ERC7579P256

ERC7579WebAuthn

ERC7579SessionKeys

Multisig

ERC7579Multisig

ERC7579MultisigWeighted

ERC7579MultisigConfidential

ERC7579MultisigWeightedConfidential

Validators

ERC7579Validator

ERC7579ECDSAValidator

ERC7579P256Validator

ERC7579WebAuthnValidator

ERC7579MultisigValidator

ERC7579MultisigWeightedValidator

ERC7579MultisigConfidentialValidator

ERC7579MultisigWeightedConfidentialValidator

ERC7579SessionKeysValidator

Executors

ERC7579Executor

ERC7579DelayedExecutor

ERC7579SocialRecoveryExecutor

ERC7579NativeSpender

ERC7579ERC20Spender

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.

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:

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 newSigner must 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 newSigner must 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.

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:

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 newSigners must be at least 20 bytes long.
  • Each of newSigners must not be already authorized.

removeSigners(bytes[] oldSigners)

public

#

Removes signers from the authorized set for the calling account. Can only be called by the account itself.

Requirements:

  • Each of oldSigners must be authorized.
  • After removal, the threshold must still be reachable.

setThreshold(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:

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:

_setThreshold(address account, uint64 newThreshold)

internal

#

Sets the signatures threshold required to approve a multisignature operation.

Requirements:

_validateReachableThreshold(address account)

internal

#

Validates the current threshold is reachable with the number of signers.

Requirements:

_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 initData must 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.

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:

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:

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:

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:

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:

  1. The handle must exist in the authorized set
  2. The handle must match the corresponding plaintext signer (address cast to uint256 or keccak256 hash for ERC-7913 keys)
  3. The signature must be valid for the given hash
  4. 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:

_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 initData must 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).

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:

onUninstall(bytes data)

public

#

Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.

See ERC7579Multisig.onUninstall.

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 initData must 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:

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.

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

ERC7579MultisigConfidential
Nonces
IERC7579Module

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:

onUninstall(bytes data)

public

#

Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.

See ERC7579MultisigConfidential.onUninstall.

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:

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 initData must 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:

_setSignerWeights(address account, euint256[] signers, euint16[] weights)

internal

#

Sets encrypted weights for multiple signers at once. Internal version without access control.

Requirements:

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.

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:

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 qx and qy coordinates must not be zero.
  • The qx and qy coordinates 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 qx and qy coordinates must not be zero.
  • The qx and qy coordinates 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.

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:

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:

removeKeys(bytes[] oldKeys)

public

#

Removes session keys from the module for the calling account. Can only be called by the account itself.

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:

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:

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:

  • initData must be at least 192 bytes (6 × 32 bytes for the 3 ABI offsets and 3 array lengths).
  • All offsets must point within the initData bounds.

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.

_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

  1. Scheduling: Operations are scheduled via ERC7579DelayedExecutor._schedule with a specified delay period. The delay period is set during ERC7579ECDSA.onInstall and can be customized via ERC7579DelayedExecutor.setDelay. Each operation enters a Scheduled state and must wait for its delay period to elapse.

  2. Security Window: During the delay period, operations remain in Scheduled state but cannot be executed. Through this period, suspicious operations can be monitored and canceled via ERC7579DelayedExecutor._cancel if appropriate.

  3. Execution & Expiration: Once the delay period elapses, operations transition to Ready state. Operations can be executed via ERC7579DelayedExecutor._execute and have an expiration period after becoming executable. If an operation is not executed within the expiration period, it becomes Expired and can't be executed. Expired operations must be rescheduled with a different salt.

Delay Management

Accounts can set their own delay periods during installation or via 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.

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:

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 after ERC7579DelayedExecutor.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:

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

#

See ERC7579Executor._execute.

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 Scheduled or Ready.

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
            ^- Unknown

ERC7579ExecutorOperationScheduled(address indexed account, bytes32 indexed operationId, bytes32 salt, bytes32 mode, bytes executionCalldata, uint48 schedule)

event

#

Emitted when a new operation is scheduled.

ERC7579ExecutorOperationCanceled(address indexed account, bytes32 indexed operationId)

event

#

Emitted when a new operation is canceled.

ERC7579ExecutorDelayUpdated(address indexed account, uint32 newDelay, uint48 effectTime)

event

#

Emitted when the execution delay is updated.

ERC7579ExecutorExpirationUpdated(address indexed account, uint32 newExpiration)

event

#

Emitted when the expiration delay is updated.

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:

  1. Validator install (first): initData is abi.encode(bytes[] keys, uint48[] starts, uint48[] ends). Registers session keys with their validity ranges through ERC7579SessionKeys._addKeys. At least one key must be registered, otherwise the install reverts.
  2. Executor install (second): initData is abi.encode(bytes[] keys, address[] tokens, uint160[] limits, uint48[] renewPeriods). Writes a spending policy for each (keys[i], tokens[i]) row. Each row's keys[i] MUST already be registered. Keys MAY repeat across rows to configure multiple tokens for the same key. The keys array 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

ERC7579Executor
ERC7579SessionKeysValidator
ERC7579Validator
ERC7579SessionKeys
Nonces

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:

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:

Second call 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:

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:

  1. 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.
  2. EOA self-call: key.length == 20 and address(bytes20(key)) == msg.sender. The key's validity range is enforced manually.

Requirements:

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:

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:

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:

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:

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:

Emits ERC7579ERC20Spender.ERC7579ERC20SpendingConfigSet.

_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.

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 account must implement the IERC7579Execution-executeFromExecutor function.

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 callData is a single call into this module's ERC7579ERC20Spender.transferFrom or ERC7579ERC20Spender.batchTransferFrom. The key's validity range is packed into validationData so 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:

  1. Validator install (first): initData is abi.encode(bytes[] keys, uint48[] starts, uint48[] ends). This registers session keys with their validity ranges through ERC7579SessionKeys._addKeys. At least one key must be registered, otherwise the install reverts.
  2. Executor install (second): initData is abi.encode(bytes[] keys, uint160[] limits, uint48[] renewPeriods). This writes a spending policy for each already-registered key. The keys array 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

ERC7579Executor
ERC7579SessionKeysValidator
ERC7579Validator
ERC7579SessionKeys
Nonces

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:

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:

Second call 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:

  1. 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.
  2. EOA self-call: key.length == 20 and address(bytes20(key)) == msg.sender. The key's validity range is enforced manually.

Requirements:

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:

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:

  • mode is CALLTYPE_SINGLE (other call types are rejected).
  • executionCalldata decodes to (target=address(this), value=0, data).
  • data calls ERC7579ERC20Spender.transferFrom or ERC7579ERC20Spender.batchTransferFrom.
  • The key parameter inside data matches the key in userOp.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:

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:

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:

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:

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:

  • initData must be at least 192 bytes (6 × 32 bytes for the 3 ABI offsets and 3 array lengths).
  • All offsets must point within the initData bounds.

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

ERC7579MultisigWeighted
ERC7579Multisig
ERC7579DelayedExecutor
ERC7579Executor

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:

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:

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:

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 multisignature must be provided.
  • A multisignature only authorizes the cancellation while the ERC7579Multisig.threshold is nonzero.
  • See ERC7579DelayedExecutor._cancel for cancellation requirements.
  • The account must implement IERC5267.

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);

_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:

  1. Call ERC7579MultisigConfidential.requestDiscloseOperation to validate the multisig and make the encrypted result publicly decryptable.
  2. Submit the user operation to the entrypoint with the decryption proof in the signature; the entrypoint calls validateUserOp, which invokes _rawERC7579Validation to verify the proof (or that the operation was explicitly disclosed via ERC7579MultisigConfidential.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.

_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.

_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

ERC7579MultisigWeightedConfidential
ERC7579MultisigConfidentialValidator
ERC7579Validator
ERC7579MultisigConfidential
Nonces

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:

onUninstall(bytes data)

public

#

Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.

See ERC7579MultisigConfidential.onUninstall.

_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:

_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.

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:

onUninstall(bytes data)

public

#

Cleans up module's configuration when uninstalled from an account. Clears all signers, weights, and total weights.

See ERC7579Multisig.onUninstall.

_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);

_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);

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
}

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);

_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool

internal

#