Most hook integration problems come from not understanding how Uniswap v4's singleton architecture processes hooks. Unlike v3 where each pool had its own contract, v4 runs everything through one PoolManager contract that calls your hook at specific lifecycle points. The official v4 documentation covers hook basics, but the actual implementation details tell the real story.
The Hook Permission System (Where Most People Fail)
Uniswap doesn't read your getHookPermissions()
function at runtime. That's just for development tooling. The real permissions come from your deployed address:
// This determines what PoolManager actually calls:
function hasPermission(IHooks self, uint160 flag) internal pure returns (bool) {
return uint160(address(self)) & flag != 0;
}
Why this breaks: You write a hook with beforeSwap: true
, deploy it at address 0x1234...0000
, and wonder why beforeSwap
never executes. The address ends in 0000
, so bit 0 (BEFORE_SWAP_FLAG) is not set. The Uniswap Foundation's hook deployment guide explains this requirement, and QuickNode's tutorial shows working examples.
I've debugged this exact issue at 2am more times than I want to count. The hook compiles fine, deploys fine, but silently does nothing because the address permissions don't match the code.
Flash Accounting and Delta Settlement
Uniswap v4 uses "flash accounting" - it tracks IOUs during a transaction and settles everything at the end. Your hook can modify these IOUs (deltas), but they must sum to zero. The MetaLamp architecture guide explains how this works technically, and QuillAudits' swap mechanics analysis covers the delta settlement process.
// This happens automatically:
function unlock(bytes calldata data) external override returns (bytes memory result) {
// Your hook executes here via callback
result = IUnlockCallback(msg.sender).unlockCallback(data);
// This check kills your transaction if deltas don't balance:
if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith();
}
Had this hook taking 0.1% fees. Worked perfectly in my tests - I was so proud of the clean math. Deployed to mainnet, first real trade comes through, instant revert. Spent 3 hours debugging before realizing I calculated the delta right but never actually moved the tokens. Classic.
Fix: Every delta your hook creates must be settled:
function afterSwap(...) external returns (bytes4, BalanceDelta hookDelta, uint24 lpFeeOverride) {
// Hook takes 0.1% fee
int128 fee = int128(swapAmount * 10 / 10000);
// Create the delta (negative = hook takes tokens)
hookDelta = BalanceDeltaLibrary.toBalanceDelta(-fee, 0);
// YOU MUST SETTLE THIS DELTA:
// Transfer fee tokens to PoolManager
IERC20(token0).transfer(address(poolManager), uint256(fee));
poolManager.settle(Currency.wrap(address(token0)));
return (BaseHook.afterSwap.selector, hookDelta, 0);
}
Hook Address Mining (The Annoying Part)
You need to mine for addresses with the right permission bits. This is slow and annoying, but there's no way around it. The HookMiner contract handles this, and SolidityDeveloper's integration guide shows practical deployment examples:
// Mine for address with beforeSwap + afterSwap permissions
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG);
(address hookAddress, bytes32 salt) = HookMiner.find(
CREATE2_DEPLOYER,
flags,
type(MyHook).creationCode,
abi.encode(poolManager) // constructor args
);
// Deploy with the mined salt
MyHook hook = new MyHook{salt: salt}(poolManager);
Time costs: Finding a simple permission combo takes 30 seconds. Complex combos can take 5+ minutes. Factor this into your deployment scripts.
Security Issues That Will Bite You
CertiK's security analysis and Hacken's hook audit guide identify patterns that consistently cause problems:
Missing access control: Most hooks need onlyPoolManager
modifiers on callback functions. Without this, anyone can call your hook directly:
// BAD - anyone can drain fees
function afterSwap(...) external returns (...) {
feeToken.transfer(msg.sender, collectedFees);
}
// GOOD - only PoolManager can call
function afterSwap(...) external onlyPoolManager returns (...) {
feeToken.transfer(owner, collectedFees);
}
Async hooks stealing funds: Async hooks completely replace Uniswap's swap logic. A malicious async hook can steal tokens instead of executing the swap:
// This hook steals your tokens instead of swapping
function beforeSwap(...) external returns (bytes4, BeforeSwapDelta, uint24) {
// Take all the user's tokens
int128 stolenAmount = -int128(params.amountSpecified);
// Transfer to attacker instead of swapping
IERC20(token).transfer(attacker, uint256(params.amountSpecified));
return (selector, BeforeSwapDelta.wrap(stolenAmount), 0);
}
Hooks attached to fake pools: Anyone can create a pool using your hook with fake tokens. If your hook doesn't validate the pool, attackers can trigger it with malicious tokens that reenter or manipulate state.
Real talk: I've seen every one of these vulnerabilities in production. The same flexibility that makes hooks powerful will burn you if you don't understand the security model.