HOOKHOOD CONCEPT

Heartbeat Hook

A pool that can feel market stress.

2ACTIVE CALLBACKS

Observes tick movement and recent swap frequency to maintain a bounded market-activity score and recommend a dynamic LP fee.

dynamic feemarket state
HOOK DNA

Callback permissions

Lifecycle

beforeInitialize○ INACTIVE
afterInitialize○ INACTIVE
beforeAddLiquidity○ INACTIVE
afterAddLiquidity○ INACTIVE
beforeRemoveLiquidity○ INACTIVE
afterRemoveLiquidity○ INACTIVE
beforeSwap● ACTIVE
afterSwap● ACTIVE
beforeDonate○ INACTIVE
afterDonate○ INACTIVE

Advanced accounting

beforeSwapReturnDelta○ INACTIVE
afterSwapReturnDelta○ INACTIVE
afterAddLiquidityReturnDelta○ INACTIVE
afterRemoveLiquidityReturnDelta○ INACTIVE
MECHANISM

Read the control surface.

POOL ACTIONbeforeSwap + afterSwapSIGNATURE LAB POLICYPOOLMANAGER ACCOUNTING
SOLIDITY

HeartbeatHook.sol

REFERENCE IMPLEMENTATION. Requires a correctly mined permission address. Compilation and tests are separate status signals.
HeartbeatHook.solSOLIDITY 0.8.26
001// SPDX-License-Identifier: MIT002pragma solidity ^0.8.26;003004import {BaseHook} from "@openzeppelin/uniswap-hooks/src/base/BaseHook.sol";005import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";006import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol";007import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";008import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";009import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";010import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";011import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";012import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";013import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";014015/// @title HeartbeatHook016/// @notice Experimental activity score and dynamic LP fee controller. Not a volatility oracle.017contract HeartbeatHook is BaseHook {018    using PoolIdLibrary for PoolKey;019020    uint8 public constant MAX_SCORE = 100;021    uint24 public constant CALM_FEE = 1_500;022    uint24 public constant ACTIVE_FEE = 3_000;023    uint24 public constant HOT_FEE = 6_000;024    uint24 public constant STORM_FEE = 10_000;025026    struct Pulse {027        uint8 score;028        int24 lastTick;029        uint64 lastBlock;030        uint64 totalSwaps;031        bool initialized;032    }033    mapping(PoolId => Pulse) private _pulses;034035    event HeartbeatUpdated(PoolId indexed poolId, uint8 oldScore, uint8 newScore, int24 tick, uint64 totalSwaps);036037    constructor(IPoolManager manager) BaseHook(manager) {}038039    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {040        return Hooks.Permissions(041            false, false, false, false, false, false, true, true, false, false, false, false, false, false042        );043    }044045    function _beforeSwap(address, PoolKey calldata key, SwapParams calldata, bytes calldata)046        internal047        view048        override049        returns (bytes4, BeforeSwapDelta, uint24)050    {051        if (!LPFeeLibrary.isDynamicFee(key.fee)) {052            return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);053        }054        uint24 fee = recommendedFeeForScore(_pulses[key.toId()].score) | LPFeeLibrary.OVERRIDE_FEE_FLAG;055        return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee);056    }057058    function _afterSwap(address, PoolKey calldata key, SwapParams calldata, BalanceDelta, bytes calldata)059        internal060        override061        returns (bytes4, int128)062    {063        PoolId poolId = key.toId();064        (, int24 currentTick,,) = StateLibrary.getSlot0(poolManager, poolId);065        Pulse storage pulse = _pulses[poolId];066        uint8 oldScore = pulse.score;067        if (!pulse.initialized) {068            pulse.initialized = true;069            pulse.lastTick = currentTick;070            pulse.lastBlock = uint64(block.number);071            pulse.totalSwaps = 1;072        } else {073            pulse.score = computeNextScore(pulse.score, pulse.lastTick, currentTick, block.number - pulse.lastBlock);074            pulse.lastTick = currentTick;075            pulse.lastBlock = uint64(block.number);076            pulse.totalSwaps += 1;077        }078        emit HeartbeatUpdated(poolId, oldScore, pulse.score, currentTick, pulse.totalSwaps);079        return (BaseHook.afterSwap.selector, 0);080    }081082    function computeNextScore(uint8 previous, int24 previousTick, int24 currentTick, uint256 blockGap)083        public084        pure085        returns (uint8)086    {087        uint256 score = previous;088        uint256 decay = blockGap > 25 ? 25 : blockGap;089        score = score > decay ? score - decay : 0;090        int256 difference = int256(currentTick) - int256(previousTick);091        uint256 movement = uint256(difference >= 0 ? difference : -difference);092        score += movement > 40 ? 40 : movement;093        if (blockGap <= 2) score += 10;094        else if (blockGap <= 5) score += 5;095        return uint8(score > MAX_SCORE ? MAX_SCORE : score);096    }097098    function recommendedFeeForScore(uint8 score) public pure returns (uint24) {099        if (score <= 25) return CALM_FEE;100        if (score <= 50) return ACTIVE_FEE;101        if (score <= 75) return HOT_FEE;102        return STORM_FEE;103    }104105    function getPulse(PoolId poolId) external view returns (Pulse memory) {106        return _pulses[poolId];107    }108109    function heartbeatScore(PoolId poolId) external view returns (uint8) {110        return _pulses[poolId].score;111    }112113    function currentRecommendedFee(PoolId poolId) external view returns (uint24) {114        return recommendedFeeForScore(_pulses[poolId].score);115    }116117    function marketMode(PoolId poolId) external view returns (string memory) {118        uint8 score = _pulses[poolId].score;119        if (score <= 25) return "CALM";120        if (score <= 50) return "ACTIVE";121        if (score <= 75) return "HOT";122        return "STORM";123    }124}125
DEVELOPMENT STATUS

Evidence, not implication.

concept● PASS
reference Code● PASS
compile● PASS
unit Tests● PASS
fuzz Tests● PASS
invariant Tests● PASS
fork Tested○ NOT VERIFIED
external Audit○ NOT VERIFIED
testnet Deployment○ NOT VERIFIED
mainnet Deployment○ NOT VERIFIED
DEPLOYMENTS

Onchain records

NO MAINNET DEPLOYMENT. No testnet deployment metadata has been exported.
SECURITY

Trust no badge blindly.

A compiled contract is not necessarily safe. Tests are not an audit. A verified source can still contain exploitable logic.

  • Review permission bits and constructor configuration.
  • Model custom accounting and rounding independently.
  • Require fork, testnet, independent review, and audit evidence before real assets.
CHANGELOG

Reference history

2026-08-14 — Initial HookHood registry entry. No deployed revisions recorded.