Hooks are lightweight, permissionless contracts which perform a specific action (bridge, swap, lend, etc.). They are designed to be composable and can be chained together to create complex transaction flows which can achieve any multi-chain strategy. Hook chains can be fulfilled by any ERC-7579 smart account with the SuperValidator and SuperExecutor modules installed.Documentation Index
Fetch the complete documentation index at: https://docs.superform.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Key Concepts
Hooks implement transient storage during the execution of a transaction to temporarily hold state changes. This allows for efficient inter-hook communication that avoids the high gas costs associated with permanent storage writes. If any hook in a chain fails, the entire transaction is reverted, ensuring atomicity.Hook Lifecycle
The execution of hooks is a three-step sequence bundled in the hook’sbuild() function:
preExecute → execution(s) → postExecute
- Build - Frontend selects the addresses and calldata of the hook(s) in a chain.
- Bundle - SuperBundler splits hooks per chain and wraps each into an ERC-4337 UserOp, storing a Merkle tree.
- Validate - Smart Account delegates to SuperValidator onchain to perform Merkle proof verification.
- Execute - SuperExecutor iterates through hooks, performs each action and updates SuperLedger.
Hook Classifications
Hooks are classified based onHookType and HookSubType.
HookType must be defined as NONACCOUNTING, INFLOW, or OUTFLOW depending on the hook’s expected interaction with the accounting system.
HookSubTypes designate which action category the hook implements.
- NONACCOUNTING - Utility hooks that do not update the accounting system. These hooks include
HookSubTypeslikeSWAP,STAKE,BRIDGE. - INFLOW - Hooks that process additions to positions and typically encode some sort of
depositaction. They include aHookSubTypecorresponding to the underlying vault being deposited into likeERC4626,ERC7540, etc. - OUTFLOW - Hooks that process reductions to positions and typically encode some sort of
redeemaction. They include similarHookSubTypetoINFLOWhooks.
Quick Start
RequirementsFoundry ≥ v1.3.0Solidity 0.8.30pnpmyarn
Anatomy of a Hook
- Every hook must inherit BaseHook to obtain core hook functionality, along with any specific interfaces your hook may require from ISuperHook.
- Each hook contract begins with natspec detailing the layout of the hook data, this ensures that SuperBundler is able to encode the data required for hook execution.
- The natspec of the data structure must be placed after the line saying
@dev data has the following structureand the natspec must follow the format of@notice DataType Name Offset. - An example of a hook with a simple encoding structure can be found here. This is a more complex example of encoding natspec.
- The natspec of the data structure must be placed after the line saying
- The constructor sets the
HookTypeandHookSubtypeas well as any immutable state variables such as the target contract address to call during execution. For a breakdown of differentHookTypeoptions see Hook Classifications, and theHookSubTypesare found in this library. If your hook will perform an action type that is not yet in the library, add it there asbytes32 public constant CATEGORY_NAME. - It is necessary to decode and validate the encoded hook data used by the hook. It is recommended to create some internal decoder and validation helpers. The decoding must align with the data locations denoted in the natspec as that is the order in which data will be encoded. There are two libraries you can use during the decoding process:
BytesLib- This necessary library can be found in thesrc/vendordirectory, you need only import it to gain access to its functionality.HookDataDecoder- This library lives in thesrc/librariesdirectory. It must be imported and the statementusing HookDataDecoder for bytesmust be placed at the top of the hook contract.
- The
_buildHookExecutions()function takes decoded hook data and returns an array of Executions.- There are 3 parameters for this function:
prevHook- The address of the prior hook in a chain,address(0)if this hook is unchained or the first in a chain. Note: If you intend for this hook to be after another hook in a chainbool usePrevHookAmountmust be added as a parameter to be encoded in the hook data structure natspec, andISuperHookResultis required.account- The smart account address if needed for the Execution payload.data- The bytes hook data (encoded in the structure defined by the natspec)
- The payload of an Execution is as follows:
Target- The address to call (contract or another module)Value- Native ETH amount to forward asmsg.valueCalldata- The encoded function selector to be called and any arguments
- There are 3 parameters for this function:
Hook Contract Outline
BaseHook:
| Variable | Purpose |
|---|---|
hookType | Enum for accounting engine |
subType | 32-byte tag for analytics / routing |
usedShares, spToken, asset | Transient storage outputs consumed by later hooks |
setOutAmount(uint256,address) | Records how many units this hook produced |
Writing your First Hook: Approve-and-Deposit ERC-4626
We’ll re-create the example atsrc/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol (GitHub).
Approve the vault, deposit amount into it, and emit the number of shares received so that downstream hooks can compose on it.
Hook Data Layout
| Offset | Bytes | Field |
|---|---|---|
| 0 | 32 | yieldSourceOracleId (optional) |
| 32 | 20 | yieldSource (IERC4626 vault) |
| 52 | 20 | token (underlying) |
| 72 | 32 | amount (uint256) |
| 104 | 1 | usePrevHookAmount (bool) |