Just wrapped up an incredible Uniswap v4 hooks workshop at LA Tech Week 2025, and I need to share what we learned. Hooks are the most significant DeFi innovation since concentrated liquidity.
If you’re building AMMs or DeFi protocols, you need to understand what Uniswap v4 hooks enable.
What Are Uniswap v4 Hooks?
Uniswap v4 launched in Q2 2024 with a revolutionary feature: hooks. Hooks are smart contracts that execute at specific points in the swap lifecycle, allowing developers to customize AMM logic.
Think of hooks as middleware for AMMs.
Without Hooks (Uniswap v3)
User → Swap → Pool → Update State → Done
Static behavior: Constant product formula, fixed fee tiers (0.05%, 0.3%, 1%), no customization.
With Hooks (Uniswap v4)
User → beforeSwap hook → Swap → afterSwap hook → Pool → Done
Dynamic behavior: Custom pricing curves, dynamic fees, limit orders, TWAP oracles, MEV protection, geofencing, and more.
Hook Lifecycle Points
Uniswap v4 provides 10 hook points where custom logic can execute:
Pool Lifecycle Hooks
- beforeInitialize: Before pool creation
- afterInitialize: After pool creation
Swap Hooks
- beforeSwap: Before swap execution (modify fee, check conditions)
- afterSwap: After swap execution (update oracles, execute limit orders)
Liquidity Hooks
- beforeAddLiquidity: Before LP adds liquidity
- afterAddLiquidity: After LP adds liquidity
- beforeRemoveLiquidity: Before LP removes liquidity
- afterRemoveLiquidity: After LP removes liquidity
Donation Hooks
- beforeDonate: Before donation (directly adding to fee accumulator)
- afterDonate: After donation
Key insight: Hooks are optional. A pool can use 0, 1, or all 10 hooks.
Real Hook Examples from LA Tech Week
At the workshop, Uniswap Labs showcased several production-ready hooks:
1. Dynamic Fee Hook
Problem: Static fees (0.3%) are suboptimal during volatility.
- High volatility → LPs need higher fees to compensate for IL
- Low volatility → Lower fees attract more volume
Solution: Dynamic fee hook adjusts fees based on realized volatility.
contract DynamicFeeHook is BaseHook {
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override returns (bytes4) {
// Calculate volatility from TWAP oracle
uint24 volatility = calculateVolatility(key.currency0, key.currency1);
// Adjust fee: higher volatility = higher fee
uint24 dynamicFee;
if (volatility > HIGH_VOLATILITY_THRESHOLD) {
dynamicFee = 10000; // 1%
} else if (volatility > MEDIUM_VOLATILITY_THRESHOLD) {
dynamicFee = 3000; // 0.3%
} else {
dynamicFee = 500; // 0.05%
}
// Update pool fee (Uniswap v4 allows dynamic fees)
poolManager.updateDynamicSwapFee(key, dynamicFee);
return BaseHook.beforeSwap.selector;
}
}
Impact: 30% higher LP returns in backtesting, 15% more volume vs static fees.
2. TWAP Oracle Hook
Problem: AMMs need reliable price oracles, but TWAP requires storage.
Solution: Hook updates TWAP every swap.
contract TWAPOracle is BaseHook {
struct TWAPObservation {
uint32 blockTimestamp;
uint256 tickCumulative;
}
mapping(PoolId => TWAPObservation[]) public observations;
function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external override returns (bytes4) {
// Record price observation
PoolId poolId = key.toId();
(uint160 sqrtPriceX96, int24 tick, , ) = poolManager.getSlot0(poolId);
observations[poolId].push(TWAPObservation({
blockTimestamp: uint32(block.timestamp),
tickCumulative: uint256(tick) * uint256(block.timestamp)
}));
// Keep only last 24 hours of observations (gas optimization)
if (observations[poolId].length > 7200) { // ~12 sec per block
delete observations[poolId][0];
}
return BaseHook.afterSwap.selector;
}
function getTWAP(PoolId poolId, uint32 secondsAgo) external view returns (uint256) {
// Calculate TWAP from observations
// ... implementation
}
}
Impact: Free TWAP oracle for any Uniswap v4 pool. No external oracle fees.
3. Limit Order Hook
Problem: AMMs only support market orders. Limit orders require separate protocols (1inch, CoW).
Solution: Hook checks price after each swap and executes pending limit orders.
contract LimitOrderHook is BaseHook {
struct LimitOrder {
address trader;
bool zeroForOne;
uint256 amount;
uint160 sqrtPriceLimitX96;
}
mapping(PoolId => LimitOrder[]) public limitOrders;
function placeLimitOrder(
PoolKey calldata key,
bool zeroForOne,
uint256 amount,
uint160 sqrtPriceLimitX96
) external {
PoolId poolId = key.toId();
limitOrders[poolId].push(LimitOrder({
trader: msg.sender,
zeroForOne: zeroForOne,
amount: amount,
sqrtPriceLimitX96: sqrtPriceLimitX96
}));
}
function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external override returns (bytes4) {
PoolId poolId = key.toId();
(uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(poolId);
// Check if any limit orders can execute at current price
LimitOrder[] storage orders = limitOrders[poolId];
for (uint i = 0; i < orders.length; i++) {
LimitOrder storage order = orders[i];
bool shouldExecute = order.zeroForOne
? sqrtPriceX96 <= order.sqrtPriceLimitX96
: sqrtPriceX96 >= order.sqrtPriceLimitX96;
if (shouldExecute) {
// Execute limit order via pool manager
poolManager.swap(key, IPoolManager.SwapParams({
zeroForOne: order.zeroForOne,
amountSpecified: int256(order.amount),
sqrtPriceLimitX96: order.sqrtPriceLimitX96
}), "");
// Remove executed order
delete orders[i];
}
}
return BaseHook.afterSwap.selector;
}
}
Impact: Native limit orders on Uniswap. No separate protocol needed.
4. MEV Protection Hook
Problem: Sandwich attacks extract value from traders.
Solution: Hook enforces maximum price impact per swap.
contract MEVProtectionHook is BaseHook {
uint256 constant MAX_PRICE_IMPACT_BPS = 50; // 0.5%
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override returns (bytes4) {
// Get current price
PoolId poolId = key.toId();
(uint160 sqrtPriceX96Before, , , ) = poolManager.getSlot0(poolId);
// Calculate maximum allowed price after swap
uint160 maxSqrtPriceX96After = sqrtPriceX96Before
+ (sqrtPriceX96Before * MAX_PRICE_IMPACT_BPS / 10000);
// Revert if price impact too high
require(
params.sqrtPriceLimitX96 <= maxSqrtPriceX96After,
"Price impact too high - possible sandwich attack"
);
return BaseHook.beforeSwap.selector;
}
}
Impact: Prevents sandwich attacks. Protects retail traders.
The Singleton Pattern: Gas Optimization
Uniswap v3: Each pool is a separate contract.
- Deploying ETH/USDC pool: ~1.2M gas (~$30 at 50 gwei)
- High deployment cost, inefficient cross-pool interactions
Uniswap v4: All pools in ONE singleton contract.
- Deploying new pool: ~5000 gas (~$0.12)
- 240x cheaper deployment
- Flash accounting across pools (atomic multi-pool swaps)
Flash Accounting
Before (Uniswap v3): Transfer tokens at each step.
User → Pool A (transfer in) → Pool B (transfer out) → User (transfer)
3 ERC20 transfers = expensive
After (Uniswap v4): Net settle at end.
User → Singleton → Calculate net delta → Single transfer
1 ERC20 transfer = cheap
Result: Multi-hop swaps 40% cheaper.
Hook Security: The Critical Concern
At LA Tech Week, the security panel emphasized: Hooks introduce smart contract risk.
Hook Vulnerabilities
1. Reentrancy: Hook calls external contract, which re-enters pool.
2. Oracle manipulation: Hook relies on manipulable price feed.
3. Access control: Hook allows unauthorized users to modify state.
4. Gas griefing: Hook consumes excessive gas, DoS attack.
5. Front-running: Hook introduces new MEV vectors.
Best Practices (from Uniswap security team)
// ✅ GOOD: Use ReentrancyGuard
contract SafeHook is BaseHook, ReentrancyGuard {
function beforeSwap(...) external override nonReentrant returns (bytes4) {
// Hook logic
}
}
// ✅ GOOD: Use Chainlink oracles (not pool price)
contract SafeOracleHook is BaseHook {
AggregatorV3Interface priceFeed;
function beforeSwap(...) external override returns (bytes4) {
(, int price, , , ) = priceFeed.latestRoundData();
// Use Chainlink price, not pool price
}
}
// ✅ GOOD: Gas limit on hook execution
contract GasLimitedHook is BaseHook {
function beforeSwap(...) external override returns (bytes4) {
uint256 gasStart = gasleft();
// Hook logic
require(gasStart - gasleft() < 100000, "Hook gas limit exceeded");
}
}
// ❌ BAD: Relying on pool price (manipulable)
contract UnsafeHook is BaseHook {
function beforeSwap(...) external override returns (bytes4) {
(uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(poolId);
// Using pool price for logic = DANGEROUS
}
}
Hook Deployment: Deterministic Addresses
Hooks use CREATE2 for deterministic addresses. The hook address determines permissions.
Hook address encodes permissions:
Address: 0x1234567890abcdef...
↑↑↑↑↑↑↑↑↑↑
Hook flags (beforeSwap, afterSwap, etc.)
Why? Gas optimization. No need to store hook permissions on-chain.
My Questions for the Community
-
What hooks would YOU build? Dynamic fees? Limit orders? Something else?
-
Security tradeoff: Are you comfortable with hook-enabled pools (custom logic) vs vanilla pools (battle-tested)?
-
Gas costs: Would you pay 20% higher gas for hooks (MEV protection, dynamic fees)?
-
Hook discovery: How should users find and trust hook-enabled pools?
Uniswap v4 hooks are a paradigm shift. The possibilities are endless.
Diana Martinez
DeFi Protocol Engineer @ Uniswap Labs
Resources:
- Uniswap v4 docs: Overview | Uniswap
- Hook examples repo: https://github.com/Uniswap/v4-periphery/tree/main/contracts/hooks
- LA Tech Week 2025 (October 13-19, Los Angeles)
- Uniswap v4 audit: https://blog.openzeppelin.com/uniswap-v4-audit