Modular accounts with ERC-7579

ERC-7579 defines an interface for modular smart accounts

Introduction

ERC-7579 outlines interfaces and behavior for modular smart accounts to ensure interoperability. It was developed by Rhinestone, ZeroDev, Biconomy, and OKX from their experiences developing with ERC-6900.

Read the standard at ethereum.org β†—

ERC-7579 accounts can only be used with the v0.7 EntryPoint, which is not yet supported by all providers.

Reference Implementation

The reference implementation of ERC-7579 is the best place to start developing a custom smart account. It includes two accounts:

  • MSABasic, which contains the minimal interface
  • MSAAdvanced, which has an optional hook extension

In most cases you will want to use the MSABasic account.

Account interface

ERC-7579 smart accounts are ERC-4337 compliant smart contract accounts with a modular architecture. An account has three functions: accountId, supportsAccountMode, and supportsModule.

The accountId can be used to identify an account, which is useful for determining the account type and version.

supportsAccountMode returns if the account supports an execution mode.

supportsModule returns if the account supports a module type.

Execution

Smart accounts using this standard have two execution functions: execute and executeFromExecutor. These allow the user to define an execution mode. It can optionally have an executeUserOp function.

execute

This function is intended to be called by the EntryPoint.

function execute(bytes32 mode, bytes calldata executionCalldata) external payable;

executeFromExecutor

This function is intended to be called only by executor modules.

function executeFromExecutor(bytes32 mode, bytes calldata executionCalldata)
    external
    payable
    returns (bytes[] memory returnData);

executeUserOp

This optional function is like execute, but allows an entire packed user operation to be passed.

function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external payable;

Execution Modes

The execution mode is a bytes32 value that is structured as follows:

  • callType (1 byte): 0x00 for a single call, 0x01 for a batch call and 0xff for delegatecall
  • execType (1 byte): 0x00 for executions that revert on failure, 0x01 for executions that do not revert on failure but implement some form of error handling
  • unused (4 bytes): this range is reserved for future standardization
  • modeSelector (4 bytes): an additional mode selector that can be used to create further execution modes
  • modePayload (22 bytes): additional data to be passed

The account has a supportsAccountMode function that checks if the mode is supported.

Modules

There are four types of modules: validators, executors, fallback handlers, and hooks.

  • Validators: A module used during the ERC-4337 validation flow to determine if a PackedUserOperation is valid.
  • Executors: A module that can execute transactions on behalf of the smart account via a callback.
  • Fallback Handlers: A module that can extend the fallback functionality of a smart account.
  • Hooks: An optional module that can execute custom logic before and after execution.

Smart accounts using this standard have three functions and two events related to modules.

  • installModule is a function that installs a module by calling the onInstall function on a module. It emits a ModuleInstalled event.
  • uninstallModule is a function that uninstalls a module by calling the onUninstall function on the module. It emits a ModuleUninstalled event.
  • isModuleInstalled returns if a given module is installed.

These functions require the type of module to be specified: 1 for validators, 2 for executors, 3 for fallback handlers, and 4 for hooks.

Module contracts

Each module smart contract will have four functions:

  • onInstall is a function that executes call data to install the module
  • onUninstall is a function that executes call data to uninstall the module
  • isModuleType is a function that returns true of a module is of the given type
  • getModuleType is a function that returns the different TypeIds of the module.

Validator Modules

In addition to the standard module interface, validators have a validateUserOp function and an isValidSignatureWithSender function.

interface IValidator is IModule {
    /**
     * @dev Validates a UserOperation
     * @param userOp the ERC-4337 PackedUserOperation
     * @param userOpHash the hash of the ERC-4337 PackedUserOperation
     *
     * MUST validate that the signature is a valid signature of the userOpHash
     * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch
     */
    function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256);

    /**
     * @dev Validates a signature using ERC-1271
     * @param sender the address that sent the ERC-1271 request to the smart account
     * @param hash the hash of the ERC-1271 request
     * @param signature the signature of the ERC-1271 request
     *
     * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid
     * MUST NOT modify state
     */
    function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) external view returns (bytes4);
}

Executors

Executors are not required to have any interface other than the standard module interface.

Fallback Handlers

Fallback Handlers also do not require any interface other than the standard module interface.

Hooks

In addition to the standard module interface, hooks have a preCheck and postCheck function.

interface IHook is IModule {
    /**
     * @dev Called by the smart account before execution
     * @param msgSender the address that called the smart account
     * @param msgData the data that was sent to the smart account
     *
     * MAY return arbitrary data in the `hookData` return value
     */
    function preCheck(address msgSender, bytes calldata msgData) external returns (bytes memory hookData);

    /**
     * @dev Called by the smart account after execution
     * @param hookData the data that was returned by the `preCheck` function
     *
     * MAY validate the `hookData` to validate transaction context of the `preCheck` function
     */
    function postCheck(bytes calldata hookData) external returns (bool success);
}