Skip to main content

Customize the hook

Open src/Counter.sol. It inherits OpenZeppelin’s BaseHook:
BaseHook restricts external callbacks to the configured PoolManager and validates the hook address against getHookPermissions() during construction. The counter enables four callbacks: beforeSwap, afterSwap, beforeAddLiquidity, and beforeRemoveLiquidity. For example, its swap callback increments a counter for the current pool:
The return values acknowledge the callback, leave swap accounting unchanged, and request no fee override. If you enable or disable a callback, update both getHookPermissions() and the address flags in DeployHook.s.sol; changing the code or constructor arguments requires a new CREATE2 calculation. For a custom hook, also test unauthorized callback calls, state isolation across pools, both swap directions, exact-input and exact-output swaps, and failure paths. A callback’s sender is the caller of the manager, often Universal Router or PositionManager; do not assume it identifies the end user or trust arbitrary hookData as authentication.

Monad Testnet contract addresses

The tutorial uses the following deployments on chain ID 10143. The same addresses are listed in shared-infrastructure.json, at the root of the example repository. The PoolManager has no owner or protocol-fee controller. The PoolManager and periphery addresses differ from Uniswap’s mainnet deployments. Permit2 uses the same address on both networks.

Deploying on Monad Mainnet

Monad Mainnet uses chain ID 143 and RPC https://rpc.monad.xyz. Use its PoolManager, 0x188d586ddcf52439676ca21a244753fa19f9ea8e, from Uniswap’s deployment list. The example’s DeployHook.s.sol deliberately accepts only chain ID 10143. To adapt it for mainnet, change that check to 143 and supply the mainnet PoolManager through POOL_MANAGER. The script passes this address to the hook’s constructor and to NamespacedHookMiner.find, which searches for a salt with the required permission bits. The CREATE2 factory address is the same on both networks. The hook address also depends on the salt and initialization code, including constructor arguments. Because the PoolManager address changes, calculate the hook address again for mainnet. Test your custom hook and its pool interactions before deploying it with real liquidity.

Existing Monad hooks

The Uniswap hook registry includes Monad. Registry inclusion is not a security audit or a prerequisite for deploying a hook. The registry’s Doppler entry for UniswapV4ScheduledMulticurveInitializerHook restricts initialization and liquidity additions to an authorized initializer and blocks swaps until its configured start time. Its source is an example of launch controls implemented with hooks.

Publish and extend your hook

Publish and verify source code, document enabled permissions and administrator powers, and test the full pool lifecycle before using a custom hook with real liquidity. A hook revert can prevent the associated pool operation from completing. To make a deployed mainnet hook discoverable, follow the hook registry contribution instructions for hooks/monad. Registry metadata does not automatically make a pool available in every router or interface; check each integration’s support for your hook’s behavior. For further examples, explore OpenZeppelin Uniswap Hooks, Uniswap’s hook guides, and the Monad hook registry.