HMMDynamicHookV2
RobinhoodHMMDynamicHookV2 is a Uniswap v4 hook on Robinhood Chain. Runs code on every swap. Can take tokens from swaps. Can block LP withdrawals. Gates who can add liquidity. Runs at pool creation. Handles donations to LPs.
- Contract name
- HMMDynamicHookV2
How this hook works
Trading taxA trading tax hook. It takes a cut of every trade, burns tokens and keeps a price record.
Takes a cut of every trade
On top of the normal pool fee, it keeps a share of every trade for itself.
The code that does this (accrue):function _accrue(Currency currency, uint256 amount, uint8 denominator) private nonReentrant { address asset = Currency.unwrap(currency); FeeAccounting storage accounting = _accounting(asset); uint256 claims = accounting.pendingFees - accounting.settledFees; if (amount > MAX_MANAGER_DELTA - claims) _settleClaims(currency, accounting, claims); // Preserve upstream policy exactly, including division order. This is not an author fee: // denominator 4 means (amount / 4) * 125 / 100, NOT amount / 4 or an author recipient. uint256 treasury = denominator == 0 ? 0 : (amount / denominator) * 125 / 100; uint256 retained = _retainFees(asset, amount, treasury); accounting.pendingFees += retained; accounting.pendingTreasurySweeps += treasury; accounting.aggregateManagerClaims += retained; uint256 beforeClaims = poolManager.balanceOf(address(this), uint160(asset)); poolManager.mint(address(this), uint160(asset), amount); uint256 afterClaims = poolManager.balanceOf(address(this), uint160(asset)); // The first comparison makes the subsequent difference non-underflowing. unchecked { if ( afterClaims < beforeClaims || afterClaims - beforeClaims != amount || afterClaims < accounting.aggregateManagerClaims ) revert ClaimMismatch(); } emit FeeAccrued(boundPoolId, asset, amount, treasury); }Burns tokens
It destroys some tokens (or sends them to an address nobody controls), so there are fewer of them.
The code that does this (burnExact):function _burnExact(address asset, FeeAccounting storage accounting, uint256 amount) private { uint256 beforeClaims = poolManager.balanceOf(address(this), uint160(asset)); poolManager.burn(address(this), uint160(asset), amount); uint256 afterClaims = poolManager.balanceOf(address(this), uint160(asset)); unchecked { if ( afterClaims > beforeClaims || beforeClaims - afterClaims != amount || afterClaims < accounting.aggregateManagerClaims ) revert ClaimMismatch(); } }Keeps a price record
It records the pool’s price over time, so other apps can read a fair average price.
The code that does this (onPoolRegistered):function _onPoolRegistered(PoolConfig calldata config) internal override(PoolBoundTruncatedOracleV4, HMMLiquidityHookV2) { PoolBoundTruncatedOracleV4._onPoolRegistered(config); HMMLiquidityHookV2._onPoolRegistered(config); }Rules for adding or removing liquidity
It decides who can add or remove liquidity, or when.
The code that does this (afterSwap):function afterSwap( address sender, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata ) external override onlyPoolManager nonReadReentrant nonFeeReentrant returns (bytes4, int128) { _initializedPool(key); uint24 rate = _consumeRate(_pendingSwapFee, boundPoolId, sender, params); _checkpointLockerFees(key, 0, 0); PoolConfig storage config = _config; Currency specified = _specifiedCurrency(key, params); Currency unspecified = Currency.unwrap(specified) == Currency.unwrap(key.currency0) ? key.currency1 : key.currency0; if (rate != 0 && _isFeeCurrency(specified, key, params, config)) { uint256 precharged = _fee(_abs(params.amountSpecified), rate); if (precharged != 0) { int128 specifiedAmount = Currency.unwrap(specified) == Currency.unwrap(key.currency0) ? delta.amount0() : delta.amount1(); if (specifiedAmount != params.amountSpecified + int256(precharged)) { revert IncompleteFill(); } } } uint256 fee; if (rate != 0 && _isFeeCurrency(unspecified, key, params, config)) { int128 amount = Currency.unwrap(unspecified) == Currency.unwrap(key.currency0) // …Anyone can trigger payouts
Anyone can claim what they’re owed and collect the fees it has built up, so nobody has to wait on the hook developer.
The code that does this (claimStrategyFees):function claimStrategyFees() external override nonReentrant nonFeeReentrant returns (uint256 tokenAmount, uint256 quoteAmount, int24 referencePoolTick) { address manager = liquidityManager(); if (msg.sender != manager) revert Unauthorized(); // Read internally: the claim guard is active, so an externally guarded oracle read // would fail. afterSwap and maintain() both use the latest persisted truncated tick. referencePoolTick = _strategyReferencePoolTick(); tokenAmount = pendingStrategyTokenFees; quoteAmount = pendingStrategyQuoteFees; pendingStrategyTokenFees = 0; pendingStrategyQuoteFees = 0; // ERC6909 transfers neither create currency deltas nor invoke token/receiver callbacks. // Only initialized swap accrual can make either reservation nonzero. if (tokenAmount != 0) poolManager.transfer(manager, uint160(token), tokenAmount); if (quoteAmount != 0) poolManager.transfer(manager, uint160(_strategyQuote), quoteAmount); }
Who controls it: No admin functions: nobody can change its rules after deployment.
Based on the hook’s published code.
What it can do
Its code executes inside each trade in pools that use it.
It can take or add tokens during a swap. Fees, burns and custom curves work this way, and so do scams.
It runs before liquidity is removed, so it can delay or refuse LP exits.
It checks every deposit before it goes in.
It runs when a pool is created, often to lock in that pool’s settings.
It runs when someone donates tokens to the pool’s LPs.
Hook history
- Lp fees checkpointed 146 times
Latest transactions
- 0x4ba1730e…2a34bb block 110,168,127
- 0x4ba1730e…2a34bb block 110,168,127
- 0x53e88f8b…f921b6 block 110,151,062
- 0x53e88f8b…f921b6 block 110,151,062
- 0x7aca127e…674e83 block 110,150,904
- 0x7aca127e…674e83 block 110,150,904
- 0xc6a601ad…a95579 block 110,150,894
- 0xc6a601ad…a95579 block 110,150,894
- 0x6bf15821…f462c2 block 110,150,884
- 0x6bf15821…f462c2 block 110,150,884
- Strategy fees allocated 73 times
Latest transactions
- 0x4ba1730e…2a34bb block 110,168,127 · 0.0000148 WETH
- 0x53e88f8b…f921b6 block 110,151,062 · 2.05e-7 WETH
- 0x7aca127e…674e83 block 110,150,904 · 2.20e-7 WETH
- 0xc6a601ad…a95579 block 110,150,894 · 2.38e-7 WETH
- 0x6bf15821…f462c2 block 110,150,884 · 2.59e-7 WETH
- 0x68bcd6bc…825784 block 110,150,874 · 2.82e-7 WETH
- 0x4ab67f7a…3b39ac block 110,150,864 · 3.07e-7 WETH
- 0x125628c7…5a141a block 110,150,854 · 3.36e-7 WETH
- 0x77c1851a…706b11 block 110,150,844 · 3.69e-7 WETH
- 0xbcae68aa…8e14f1 block 110,150,834 · 4.09e-7 WETH
- Fee accrued 73 times
Latest transactions
- 0x4ba1730e…2a34bb block 110,168,127
- 0x53e88f8b…f921b6 block 110,151,062
- 0x7aca127e…674e83 block 110,150,904
- 0xc6a601ad…a95579 block 110,150,894
- 0x6bf15821…f462c2 block 110,150,884
- 0x68bcd6bc…825784 block 110,150,874
- 0x4ab67f7a…3b39ac block 110,150,864
- 0x125628c7…5a141a block 110,150,854
- 0x77c1851a…706b11 block 110,150,844
- 0xbcae68aa…8e14f1 block 110,150,834
- Opening completed 1 time
- Registered pool 1 time
Settings changes: set up pool (1 time).
Counted from what the hook recorded on-chain across all its pools, for every action in its published code, plus token transfers in and out of it. Still reading older history, so totals will grow.