Skip to content

Modules

filmakarov edited this page Aug 19, 2024 · 11 revisions

Nexus is following the ERC-7579 specification. ERC-7579 is the standard for minimal modular smart accounts. Thus, modules are crucial component that allows to add features and extend the functionality of Nexus.

There are currently 4 module types specified in ERC-7579 and all of them are supported by Nexus:

  • Validators: validate userOperations as per ERC-4337.
  • Executors: can initiate executions on behalf of the Smart Account they are installed on.
  • Fallback handlers: extend the native functionality of the Smart Account. Calls to functions which are not implemented in the Smart Account are forwarded to fallback handlers.
  • Hooks: hook executions by executing code before and after execution.

Installation

Install module

Nexus implements ERC-7579 installModule interface for the installation of the modules. It allows specifying which module to install and accepts data to perform the initial configuration for a module being installed. Another important parameter that has to be provided at module installation is the moduleTypeId.

Module Type Id

It is an incremental identifier, which must be used by accounts, modules and other entities to identify the module type.

  • Validator modules have type id: 1
  • Executors have type id: 2
  • Fallback Handlers have type id: 3
  • and Hooks have type id: 4 Every module self-reports its type via isModuleType(uint256 moduleTypeId) method. Module can implements several types, i.e. be a validator and an executor at the same time.

There is Module type id: 0 which means the Multitype installation. It allows installing the module as several types in course of the same installModule call.

Module enable mode

There is another way to install the module. Sometimes, there can be a situation, when the user wants to use the modules, which is not enabled yet. In this case, we'd want to avoid sending a separate userOperation that installs the module. If the module we want to install before the usage is executor, this can be simply solved by batching installModule(executor) call before the call that goes through this executor. However, that would not work for validators, as the validation phase in 4337 goes before execution, thus the new validator should be enabled before it is used for the validation.

Enable Mode allows to enable the module at the beginning of the validation phase. To achieve this, a user signs the data object that describes which module is going to be installed and how it should be configured during the installation. Any module type can be installed via Module Enable Mode, however, we believe it makes the most sense for validators and hooks.

To install a module via Enable mode, a user with appropriate rights (owner, etc) should sign the Enable Mode Data hash. This hash is generated in the following way:

_hashTypedData(
            keccak256(
                abi.encode(
                    MODULE_ENABLE_MODE_TYPE_HASH,
                    module,
                    moduleType,
                    userOpHash,
                    keccak256(initData)
                )
            )

So, it is an EIP-712 hash, based on:

  • module: Module address
  • moduleType: so the signature can not be exploited to install the module as a different module type compared to what was expected
  • userOpHash: introduces all uniqueness provided by the fields of the UserOperation, such as nonce, and others
  • keccak256(initData): Data used to init the module that is about to be installed

The data (ModuleType moduleType, bytes moduleInitData, bytes enableModeSignature, bytes userOpSignature), that is required to activate the enable mode, is provided in the userOp.signature field. The encoding must follow a specially packed encoding as follows:

  • First 20 bytes: Address of a module that is about to be installed
  • Next 32 bytes: ModuleType moduleType as a uint256.
  • Next 4 bytes: Length of moduleInitData
  • Next bytes: moduleInitData
  • Next 4 bytes: Length of enableModeSignature
  • Next bytes: enableModeSignature
  • Rest of the bytes: userOpSignature.

In Solidity, it would be encoded as follows:

bytes memory extendedUserOpSignature = abi.encodePacked(
    module,
    uint256(moduleType),
    bytes4(uint32(moduleInitData.length)),
    moduleInitData,
    bytes4(uint32(enableModeSignature.length)),
    enableModeSignature,
    userOpSignature
);

The parseEnableModeData method is used to decode this data out of a signature.

Usage of Modules

Modules are used according to their types.

Usage of validators

Validators validate userOperations. The validator's address should be encoded into the userOp.nonce field. Nexus decodes this address and forwards the validation flow to the appropriate validator.

Usage of executors

Executors can be called directly or via Nexus.execute. The most important ability of executors is to perform actions on behalf of Nexus they are installed on by calling ERC-7579' executeFromExecutor method. For example, Executors can implement some complex logic of conditionally batching actions together.

Usage of fallback handlers

Sometimes, developers may want to add native functions to Nexus. To avoid dealing with upgrades, a Fallback can be installed on Nexus, and Nexus will forward the calls to non-existent methods to such modules that implement them.

Usage of hooks

Hooks are used to actually 'hook' executions. For example, they can store (ideally, temporarily) some data that reflects the state before the execution, and then compare it to what the state is after the execution. Hooks are the most convenient way to implement spending limits for Nexus for example.

Querying installed modules

There are two methods of querying this. One can check if a given module is installed as a given type on a given Nexus, by calling Nexus.isModuleInstalled method. If one just needs to learn what modules are installed, there are dedicated methods for it:

  • getValidatorsPaginated for validators
  • getExecutorsPaginated for executors
  • getActiveHook for the hook. As you may see, only one hook can be enabled at a given time (this can be a hook multiplexer though).
  • getFallbackHandlerBySelector to check where the implementation for a method with a given selector lives

Uninstalling the module

When uninstalling the module it is very important to provide the appropriate data that will deinitialize the module (by clearing its state or in any other way). Leaving the module initialized (configured for a given Smart Account) after its uninstallation may lead to unexpected behaviors when the module is installed on the same Smart Account next time.