Hooks

Step-by-step guide to implementing, testing, and deploying custom Lotus hooks with Solidity and TypeScript examples, including a MaxSupplyHook example.

Implement, test, and deploy a custom hook for Lotus markets.

Prerequisites

  • Familiarity with Solidity and the Lotus protocol concepts (see Learn → Hooks)

  • Understanding of the ILotusHooks interface (see Reference → Hooks)

  • Development environment with Forge

Step 1 — Implement the Interface

Inherit from BaseLotusHook, which provides default pass-through implementations for all five hook points. Override only the methods you need. Return the function selector on success; revert to deny the operation.

The following example implements a MaxSupplyHook that caps total supply per tranche:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {BaseLotusHook} from "lotus/hooks/base/BaseLotusHook.sol";
import {ILotusHooks} from "lotus/hooks/interfaces/ILotusHooks.sol";
import {ILotus} from "lotus/interfaces/ILotus.sol";
import {Id} from "lotus/interfaces/Types.sol";

contract MaxSupplyHook is BaseLotusHook {
    ILotus public immutable lotus;
    address public owner;

    // Maximum supply (in loan token assets) per market per tranche
    mapping(Id => mapping(uint256 => uint256)) public maxSupply;

    error NotOwner();
    error SupplyCapExceeded(uint256 currentSupply, uint256 cap);

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    constructor(address _owner, ILotus _lotus) {
        owner = _owner;
        lotus = _lotus;
    }

    function setMaxSupply(Id id, uint256 trancheIndex, uint256 cap) external onlyOwner {
        maxSupply[id][trancheIndex] = cap;
    }

    function afterSupply(
        ILotusHooks.SupplyParams calldata params
    ) external view override returns (bytes4) {
        uint256 cap = maxSupply[params.id][params.trancheIndex];
        if (cap == 0) return ILotusHooks.afterSupply.selector; // unconfigured

        // Read current tranche supply from Lotus
        ILotus.Tranche[] memory tranches = lotus.getMarketTranches(params.id);
        uint256 currentSupply = tranches[params.trancheIndex].trancheSupplyAssets;

        if (currentSupply > cap) {
            revert SupplyCapExceeded(currentSupply, cap);
        }

        return ILotusHooks.afterSupply.selector;
    }
}

Because BaseLotusHook provides pass-through implementations for the other four hooks, you do not need to implement them.

Step 2 — Set Permission Flags

Compute the permissions bitmask for your hook. Each hook point has a corresponding flag:

Only set flags for the hooks your contract actually overrides. Unnecessary flags add gas cost to operations that hit the default pass-through.

Step 3 — Deploy and Register

Deploy the hook contract and register it on a market via the admin's setMarketHook call.

In TypeScript with ethers.js:

Step 4 — Test the Hook

A Forge test verifying the hook allows normal supplies but blocks supplies that exceed the cap:

Design Tips

  • Keep hooks minimal. They execute on every hooked operation and add gas cost. Avoid complex storage reads or external calls when possible.

  • Hooks cannot modify Lotus state. They are invoked via staticcall and can only approve or reject by returning the selector or reverting.

  • Use BaseLotusHook for defaults. Override only the hooks you need; leave the rest as pass-through.

  • Test thoroughly. A buggy hook can lock users out of their funds if it reverts on every operation. Always test both the allow and deny paths.

  • Consider upgradability. Hooks are set per-market and can be replaced by the admin, but users with active positions are affected immediately. If the hook's rules need to change, design the hook with configurable parameters (like setFactor or setMaxSupply) rather than requiring a full redeployment.

See Also

  • Reference → Hooks for the full API

  • Learn → Hooks for conceptual background

  • Reference → Admin for setMarketHook documentation

Last updated