HOOKHOOD CONCEPT

FairFlow Hook

Trade together. Settle together.

2ACTIVE CALLBACKS

Collects exact-input flow into deterministic epochs for deterministic REFUND_ONLY research settlement; it does not claim to be a production batch auction.

asyncepochsrefund-onlyexperimental
HOOK DNA

Callback permissions

Lifecycle

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

Advanced accounting

beforeSwapReturnDelta● ACTIVE
afterSwapReturnDelta○ INACTIVE
afterAddLiquidityReturnDelta○ INACTIVE
afterRemoveLiquidityReturnDelta○ INACTIVE
MECHANISM

Read the control surface.

POOL ACTIONbeforeSwap + beforeSwapReturnDeltaSIGNATURE LAB POLICYPOOLMANAGER ACCOUNTING
SOLIDITY

FairFlowHook.sol

REFERENCE IMPLEMENTATION. Requires a correctly mined permission address. Compilation and tests are separate status signals.
FairFlowHook.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 {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";007import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";008import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";009import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";010import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";011import {BeforeSwapDelta, toBeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";012013/// @title FairFlowHook014/// @notice Experimental epoch collector with deterministic REFUND_ONLY research settlement.015/// @dev This deliberately does not fabricate a clearing price. Mainnet use is blocked by deployment scripts.016contract FairFlowHook is BaseHook {017    using PoolIdLibrary for PoolKey;018    using CurrencyLibrary for Currency;019    enum SettlementMode {020        REFUND_ONLY021    }022    error UnsupportedExactOutput();023    error ExactInputTooLarge();024    error InvalidEpochDuration();025    error InvalidUser();026    error EpochNotEnded();027    error EpochAlreadySettled();028    error EpochNotSettled();029    error NothingToClaim();030031    struct Epoch {032        uint256 zeroForOneInput;033        uint256 oneForZeroInput;034        uint64 orderCount;035        uint64 startedAt;036        bool settled;037    }038039    struct UserOrder {040        uint256 currency0Input;041        uint256 currency1Input;042        bool claimed;043    }044    uint64 public immutable epochDuration;045    mapping(PoolId => mapping(uint64 => Epoch)) private _epochs;046    mapping(PoolId => mapping(uint64 => mapping(address => UserOrder))) private _orders;047    mapping(PoolId => mapping(uint64 => Currency[2])) private _currencies;048    event OrderQueued(049        PoolId indexed poolId, uint64 indexed epochId, address indexed user, bool zeroForOne, uint256 inputAmount050    );051    event EpochSettled(052        PoolId indexed poolId, uint64 indexed epochId, SettlementMode mode, uint256 refundable0, uint256 refundable1053    );054    event Claimed(055        PoolId indexed poolId,056        uint64 indexed epochId,057        address indexed user,058        uint256 currency0Amount,059        uint256 currency1Amount060    );061062    constructor(IPoolManager manager, uint64 duration) BaseHook(manager) {063        if (duration == 0) revert InvalidEpochDuration();064        epochDuration = duration;065    }066067    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {068        return Hooks.Permissions(069            false, false, false, false, false, false, true, false, false, false, true, false, false, false070        );071    }072073    function _beforeSwap(address, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)074        internal075        override076        returns (bytes4, BeforeSwapDelta, uint24)077    {078        if (params.amountSpecified >= 0) revert UnsupportedExactOutput();079        address user = _parseUser(hookData);080        uint256 input = _abs(params.amountSpecified);081        if (input > uint256(uint128(type(int128).max))) revert ExactInputTooLarge();082        PoolId poolId = key.toId();083        uint64 epochId = currentEpoch();084        Epoch storage epoch = _epochs[poolId][epochId];085        if (epoch.startedAt == 0) {086            epoch.startedAt = uint64(uint256(epochId) * epochDuration);087            _currencies[poolId][epochId] = [key.currency0, key.currency1];088        }089        UserOrder storage order = _orders[poolId][epochId][user];090        Currency inputCurrency;091        if (params.zeroForOne) {092            epoch.zeroForOneInput += input;093            order.currency0Input += input;094            inputCurrency = key.currency0;095        } else {096            epoch.oneForZeroInput += input;097            order.currency1Input += input;098            inputCurrency = key.currency1;099        }100        epoch.orderCount += 1;101        poolManager.mint(address(this), inputCurrency.toId(), input);102        emit OrderQueued(poolId, epochId, user, params.zeroForOne, input);103        return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(int128(int256(input)), 0), 0);104    }105106    function settleEpoch(PoolId poolId, uint64 epochId) external {107        Epoch storage epoch = _epochs[poolId][epochId];108        if (epoch.settled) revert EpochAlreadySettled();109        if (block.timestamp < uint256(epoch.startedAt) + epochDuration) revert EpochNotEnded();110        epoch.settled = true;111        emit EpochSettled(poolId, epochId, SettlementMode.REFUND_ONLY, epoch.zeroForOneInput, epoch.oneForZeroInput);112    }113114    function claim(PoolId poolId, uint64 epochId) external {115        Epoch storage epoch = _epochs[poolId][epochId];116        if (!epoch.settled) revert EpochNotSettled();117        UserOrder storage order = _orders[poolId][epochId][msg.sender];118        if (order.claimed || order.currency0Input + order.currency1Input == 0) revert NothingToClaim();119        order.claimed = true;120        Currency[2] memory currency = _currencies[poolId][epochId];121        if (order.currency0Input > 0) poolManager.transfer(msg.sender, currency[0].toId(), order.currency0Input);122        if (order.currency1Input > 0) poolManager.transfer(msg.sender, currency[1].toId(), order.currency1Input);123        emit Claimed(poolId, epochId, msg.sender, order.currency0Input, order.currency1Input);124    }125126    function currentEpoch() public view returns (uint64) {127        return uint64(block.timestamp / epochDuration);128    }129130    function getEpoch(PoolId poolId, uint64 epochId) external view returns (Epoch memory) {131        return _epochs[poolId][epochId];132    }133134    function getUserOrder(PoolId poolId, uint64 epochId, address user) external view returns (UserOrder memory) {135        return _orders[poolId][epochId][user];136    }137138    function claimable(PoolId poolId, uint64 epochId, address user)139        external140        view141        returns (uint256 currency0Amount, uint256 currency1Amount)142    {143        UserOrder memory order = _orders[poolId][epochId][user];144        if (!_epochs[poolId][epochId].settled || order.claimed) return (0, 0);145        return (order.currency0Input, order.currency1Input);146    }147148    function isSettled(PoolId poolId, uint64 epochId) external view returns (bool) {149        return _epochs[poolId][epochId].settled;150    }151152    function settlementMode() external pure returns (SettlementMode) {153        return SettlementMode.REFUND_ONLY;154    }155156    function _parseUser(bytes calldata data) internal pure returns (address user) {157        if (data.length != 32) revert InvalidUser();158        user = abi.decode(data, (address));159        if (user == address(0)) revert InvalidUser();160    }161162    function _abs(int256 value) internal pure returns (uint256) {163        if (value >= 0) return uint256(value);164        unchecked {165            return uint256(-(value + 1)) + 1;166        }167    }168}169
DEVELOPMENT STATUS

Evidence, not implication.

concept● PASS
reference Code● PASS
compile● PASS
unit Tests● PASS
fuzz Tests○ NOT VERIFIED
invariant Tests○ NOT VERIFIED
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.