Building a Treasury Management Hook for Uniswap V4 — Part 2: Hook Lifecycle and Fee Collection Mechanics
Pool Initialization and Registration
When new pools are created in Uniswap V4, our hook automatically registers them for treasury management through the afterInitialize callback:
function _afterInitialize(
address,
PoolKey calldata key,
uint160,
int24
) internal override returns (bytes4) {
PoolId poolId = key.toId();
isPoolManaged[poolId] = true;
return IHooks.afterInitialize.selector;
}
Registration Strategy
Universal Coverage: The hook automatically registers all newly initialized pools, ensuring fee collection across the entire protocol.
Efficient Identification: Uses PoolKey.toId() to generate consistent, unique identifiers for each pool, leveraging Uniswap V4’s built-in hashing mechanism.
Single Storage Write: Each pool registration requires only one storage operation, minimizing gas costs during pool creation.
Proper Return Value: Returns the correct selector to confirm successful hook processing, maintaining compatibility with the hook chain.
Pre-Swap Validation and Optimization
The beforeSwap hook implements a crucial optimization pattern that determines whether to process swaps for fee collection:
function _beforeSwap(
address,
PoolKey calldata key,
IPoolManager.SwapParams calldata,
bytes calldata
) internal view override returns (bytes4, BeforeSwapDelta, uint24) {
PoolId poolId = key.toId();
if (!isPoolManaged[poolId]) {
return (bytes4(0), BeforeSwapDelta.wrap(0), 0);
}
return (IHooks.beforeSwap.selector, BeforeSwapDelta.wrap(0), 0);
}
Optimization Logic
Early Exit Strategy: For unmanaged pools, returning bytes4(0) signals the hook system to skip the afterSwap callback entirely, saving significant gas.
Managed Pool Processing: Managed pools return the proper selector, ensuring the fee collection logic in afterSwap will execute.
Gas Efficiency: This pattern prevents unnecessary processing for pools that shouldn’t participate in fee collection.
View Function Optimization: The function is marked as view, enabling the EVM to optimize storage reads during execution.
Core Fee Collection Implementation
The afterSwap hook contains the primary fee collection logic:
function _afterSwap(
address,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata
) internal override returns (bytes4, int128) {
PoolId poolId = key.toId();
if (treasuryFeeRate == 0) {
return (IHooks.afterSwap.selector, 0);
}
(Currency tokenIn, uint256 amountIn) = _getSwapInputDetails(key, params, delta);
if (amountIn > 0) {
uint256 feeAmount = (amountIn * treasuryFeeRate) / BASIS_POINTS;
if (feeAmount > 0) {
accumulatedFees[tokenIn] += feeAmount;
poolManager.take(tokenIn, address(this), feeAmount);
emit TreasuryFeeCollected(poolId, tokenIn, feeAmount);
return (IHooks.afterSwap.selector, int128(int256(feeAmount)));
}
}
return (IHooks.afterSwap.selector, 0);
}
Fee Collection Flow Analysis
Validation Phase: Verifies that treasuryFeeRate is non-zero to avoid unnecessary processing
Input Extraction: Calls _getSwapInputDetails to determine which token and amount to charge fees on
Fee Calculation: Applies the basis points calculation: (amountIn * treasuryFeeRate) / BASIS_POINTS
State Updates:
Accumulates fees in the token-specific mappingUses poolManager.take() to transfer tokens from the pool to the hook contract
Event Emission: Logs the fee collection for transparency and off-chain monitoring
Return Value: Reports the collected fee amount back to the pool manager
Swap Input Analysis
The _getSwapInputDetails function determines which token and amount to charge fees on:
function _getSwapInputDetails(
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta
) private pure returns (Currency tokenIn, uint256 amountIn) {
if (params.zeroForOne) {
tokenIn = key.currency0;
amountIn = delta.amount0() < 0 ? uint256(uint128(-delta.amount0())) : 0;
} else {
tokenIn = key.currency1;
amountIn = delta.amount1() < 0 ? uint256(uint128(-delta.amount1())) : 0;
}
}
Swap Direction Logic
Direction Detection: params.zeroForOne indicates whether the swap is from currency0 to currency1 or vice versa
Token Mapping: Maps the swap direction to the correct input currency from the pool key
Amount Extraction: Negative delta values represent tokens being input to the pool (sold by the trader)
Safe Type Conversion: Converts signed int128 delta values to unsigned uint256 amounts, handling the sign flip properly
Zero Amount Handling: Returns 0 for cases where the delta is positive or zero (no input)
Fee Calculation Mathematics
The fee calculation system uses basis points for precise control:
uint256 feeAmount = (amountIn * treasuryFeeRate) / BASIS_POINTS;
Mathematical Properties
Precision Control: 10,000 basis points provide 0.01% precision, allowing fine-tuned fee adjustments
Range Coverage: Fee rates from 0 to 1000 basis points cover 0% to 10% in 0.01% increments
Overflow Protection: Uses 256-bit arithmetic to prevent overflow even with maximum possible values
Trader-Favorable Rounding: Integer division rounds down, ensuring traders aren’t overcharged due to rounding
Example Calculations
Assume amountIn is 1000;
1% fee (100 basis points): (1000 * 100) / 10000 = 10 tokens0.5% fee (50 basis points): (1000 * 50) / 10000 = 5 tokens0.25% fee (25 basis points): (1000 * 25) / 10000 = 2 tokens10% fee (1000 basis points): (1000 * 1000) / 10000 = 100 tokens
Token Transfer Integration
Fee collection leverages Uniswap V4’s optimized transfer system:
poolManager.take(tokenIn, address(this), feeAmount);
Transfer Mechanism Benefits
Automatic Deduction: Fees are automatically deducted from the swap proceeds before tokens reach the trader
Gas Optimization: Leverages the pool manager’s already-optimized transfer mechanisms rather than implementing custom logic
Seamless Integration: Works with Uniswap V4’s internal accounting system without requiring additional approvals
Multi-Token Support: Handles any ERC-20 token or ETH through the Currency abstraction
Event System for Transparency
The contract emits comprehensive events for all treasury operations:
event TreasuryFeeCollected(PoolId indexed poolId, Currency indexed token, uint256 amount);
event TreasuryAddressChanged(address indexed oldTreasury, address indexed newTreasury);
event TreasuryFeeRateChanged(uint24 oldRate, uint24 newRate);
event FeesWithdrawn(Currency indexed token, uint256 amount);
Event Design Benefits
Indexed Parameters: Enable efficient filtering and searching in event logs
Complete Coverage: All significant treasury operations emit events for full transparency
Gas Optimization: Events include only essential data to minimize gas costs
Frontend Integration: Support real-time monitoring dashboards and analytics tools
Audit Trail: Create permanent, immutable records of all treasury activities
Gas Optimization Strategies
The implementation employs several techniques to minimize gas consumption:
Efficient Storage Access
Single Reads: Frequently accessed variables like treasuryFeeRate are read once and cached in memory
Conditional Processing: Early exits prevent expensive operations when they’re unnecessary
Optimized Mappings: Separate mappings for different data types avoid complex struct operations
Computational Efficiency
Integer Arithmetic: All calculations use integer math to avoid expensive floating-point operations
Minimal Type Conversions: Carefully designed to minimize casting between different numeric types
Basis Points System: Avoids decimal calculations while maintaining precision
Hook-Specific Optimizations
Selective Processing: The beforeSwap optimization prevents unnecessary afterSwap execution
View Function Usage: Marking functions as view where possible enables EVM optimizations
Minimal Return Data: Returns only necessary data to reduce transaction costs
Pool Management Features
The contract provides both automatic and manual pool management capabilities:
function getPoolManagedStatus(PoolKey calldata key) external view returns (bool) {
return isPoolManaged[key.toId()];
}
function setPoolManaged(PoolKey calldata key, bool managed) external {
isPoolManaged[key.toId()] = managed;
}
Management Capabilities
Status Queries: External contracts and frontends can check which pools participate in fee collection
Manual Override: Allows disabling fee collection for specific pools when needed
Testing Support: Enables controlled testing scenarios with custom pool configurations
Flexible Configuration: Supports different treasury strategies for different pool types
The fee collection mechanics demonstrated here provide a foundation for automated treasury management. The system efficiently processes high-frequency swap operations while maintaining precise fee collection and monitoring capabilities. In Part 3, we’ll explore the treasury operations and withdrawal mechanisms that complete the system.
Building a Treasury Management Hook for Uniswap V4 — Part 2: Hook Lifecycle and Fee Collection… was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.