ModuleKit
Build
Hooks

Hooks

Hooks are modules that are triggered either before or after execution and can be used to enforce certain behavior.

A module can be broken down into three different domains:

  1. Config: Handles configurations on the module, such as installation and uninstallation
  2. Business logic: Handles the core logic of the module, depending on the module type
  3. Metadata: Additional data that is used to identify and correctly use a module

This page only covers domain 2 in the specific case of a Hook Module. For more information on domains 1 and 3, read through the Module page.

Building a hook

To build a compliant validator you need to ensure that it:

  • Inherits from the ERC7579HookBase contract.
  • Implements the required functions of the interface.

An example of a compliant hook (without actual logic) looks like this:

contract HookExample is ERC7579HookBase {
    /*//////////////////////////////////////////////////////////////////////////
                                     CONFIG
    //////////////////////////////////////////////////////////////////////////*/
 
    ...
 
    /*//////////////////////////////////////////////////////////////////////////
                                     MODULE LOGIC
    //////////////////////////////////////////////////////////////////////////*/
 
     /**
     * Pre-checks an execution
     *
     * @param account The account address
     * @param msgSender The sender of the execution to the account
     * @param msgData The data of the execution
     *
     * @return hookData The data to be used in the post-check
     */
    function _preCheck(
        address account,
        address msgSender,
        uint256 msgValue,
        bytes calldata msgData
    )
        internal
        override
        returns (bytes memory hookData)
    {
        hookData = abi.encode(true);
    }
 
    /**
     * Post-checks an execution
     *
     * @param account The account address
     * @param hookData The data from the pre-check
     */
    function _postCheck(address account, bytes calldata hookData) internal override {
        (bool success) = abi.decode(hookData, (bool));
        if (!success) {
            revert("HookTemplate: execution failed");
        }
    }
 
    /*//////////////////////////////////////////////////////////////////////////
                                     METADATA
    //////////////////////////////////////////////////////////////////////////*/
 
    ...
}

_preCheck

This function is called before a single or batched execution and contains the address account, the address msgSender to the account, the uint256 msgValue sent to the account and the bytes msgData sent to the account. The hook can perform any arbitrary computations on this data. Optionally, the function can revert if the hook wants to disallow this execution. The function may also return data which is then passed on to the postCheck function.

_postCheck

This function is called after a single or batched execution and is given the return value of the preCheck function as well as the address account. The hook can perform any arbitrary computations on this data. Optionally, the function can revert if the hook wants to disallow this execution.

ERC7579HookDestruct

The ModuleKit also contains a contract called ERC7579HookDestruct, which is a more advanced base component for hooks. Specifically, it decodes the execution on the account and gives the Module developer some functions they can override to make it easier to build a hook that checks the calldata or only allows certain entrypoints into the account. Specifically, these functions are:

abstract contract ERC7579HookDestruct is ERC7579HookBase {
    /*//////////////////////////////////////////////////////////////////////////
                                CALLDATA DECODING
    //////////////////////////////////////////////////////////////////////////*/
    ...
 
    /*//////////////////////////////////////////////////////////////////////////
                                     EXECUTION
    //////////////////////////////////////////////////////////////////////////*/
 
    function onExecute(
        address account,
        address msgSender,
        address target,
        uint256 value,
        bytes calldata callData
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
    function onExecuteBatch(
        address account,
        address msgSender,
        Execution[] calldata
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
    function onExecuteFromExecutor(
        address account,
        address msgSender,
        address target,
        uint256 value,
        bytes calldata callData
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
    function onExecuteBatchFromExecutor(
        address account,
        address msgSender,
        Execution[] calldata
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
   /*//////////////////////////////////////////////////////////////////////////
                                     CONFIG
    //////////////////////////////////////////////////////////////////////////*/
 
    function onInstallModule(
        address account,
        address msgSender,
        uint256 moduleType,
        address module,
        bytes calldata initData
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
    function onUninstallModule(
        address account,
        address msgSender,
        uint256 moduleType,
        address module,
        bytes calldata deInitData
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
    /*//////////////////////////////////////////////////////////////////////////
                                UNKNOWN FUNCTION
    //////////////////////////////////////////////////////////////////////////*/
 
    function onUnknownFunction(
        address account,
        address msgSender,
        uint256 msgValue,
        bytes calldata msgData
    )
        internal
        virtual
        returns (bytes memory hookData)
    { }
 
    /*//////////////////////////////////////////////////////////////////////////
                                     POSTCHECK
    //////////////////////////////////////////////////////////////////////////*/
 
    function onPostCheck(address account, bytes calldata hookData) internal virtual { }
 
}

These functions may be overriden by the developer to implement the hook logic. If a developer chooses to not allow certain entrypoints into the account, they can revert the function call. If the developer chooses to allow the entrypoint, they can return some data that is then passed on to the postCheck function and then routed into the onPostCheck function which can be overriden to implement the post-check logic.

Hook examples