OVERVIEW
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

Trade together. Settle together.
Collects exact-input flow into deterministic epochs for deterministic REFUND_ONLY research settlement; it does not claim to be a production batch auction.
beforeInitialize○ INACTIVEafterInitialize○ INACTIVEbeforeAddLiquidity○ INACTIVEafterAddLiquidity○ INACTIVEbeforeRemoveLiquidity○ INACTIVEafterRemoveLiquidity○ INACTIVEbeforeSwap● ACTIVEafterSwap○ INACTIVEbeforeDonate○ INACTIVEafterDonate○ INACTIVEbeforeSwapReturnDelta● ACTIVEafterSwapReturnDelta○ INACTIVEafterAddLiquidityReturnDelta○ INACTIVEafterRemoveLiquidityReturnDelta○ INACTIVE001// 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 {017using PoolIdLibrary for PoolKey;018using CurrencyLibrary for Currency;019enum SettlementMode {020REFUND_ONLY021}022error UnsupportedExactOutput();023error ExactInputTooLarge();024error InvalidEpochDuration();025error InvalidUser();026error EpochNotEnded();027error EpochAlreadySettled();028error EpochNotSettled();029error NothingToClaim();030031struct Epoch {032uint256 zeroForOneInput;033uint256 oneForZeroInput;034uint64 orderCount;035uint64 startedAt;036bool settled;037}038039struct UserOrder {040uint256 currency0Input;041uint256 currency1Input;042bool claimed;043}044uint64 public immutable epochDuration;045mapping(PoolId => mapping(uint64 => Epoch)) private _epochs;046mapping(PoolId => mapping(uint64 => mapping(address => UserOrder))) private _orders;047mapping(PoolId => mapping(uint64 => Currency[2])) private _currencies;048event OrderQueued(049PoolId indexed poolId, uint64 indexed epochId, address indexed user, bool zeroForOne, uint256 inputAmount050);051event EpochSettled(052PoolId indexed poolId, uint64 indexed epochId, SettlementMode mode, uint256 refundable0, uint256 refundable1053);054event Claimed(055PoolId indexed poolId,056uint64 indexed epochId,057address indexed user,058uint256 currency0Amount,059uint256 currency1Amount060);061062constructor(IPoolManager manager, uint64 duration) BaseHook(manager) {063if (duration == 0) revert InvalidEpochDuration();064epochDuration = duration;065}066067function getHookPermissions() public pure override returns (Hooks.Permissions memory) {068return Hooks.Permissions(069false, false, false, false, false, false, true, false, false, false, true, false, false, false070);071}072073function _beforeSwap(address, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)074internal075override076returns (bytes4, BeforeSwapDelta, uint24)077{078if (params.amountSpecified >= 0) revert UnsupportedExactOutput();079address user = _parseUser(hookData);080uint256 input = _abs(params.amountSpecified);081if (input > uint256(uint128(type(int128).max))) revert ExactInputTooLarge();082PoolId poolId = key.toId();083uint64 epochId = currentEpoch();084Epoch storage epoch = _epochs[poolId][epochId];085if (epoch.startedAt == 0) {086epoch.startedAt = uint64(uint256(epochId) * epochDuration);087_currencies[poolId][epochId] = [key.currency0, key.currency1];088}089UserOrder storage order = _orders[poolId][epochId][user];090Currency inputCurrency;091if (params.zeroForOne) {092epoch.zeroForOneInput += input;093order.currency0Input += input;094inputCurrency = key.currency0;095} else {096epoch.oneForZeroInput += input;097order.currency1Input += input;098inputCurrency = key.currency1;099}100epoch.orderCount += 1;101poolManager.mint(address(this), inputCurrency.toId(), input);102emit OrderQueued(poolId, epochId, user, params.zeroForOne, input);103return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(int128(int256(input)), 0), 0);104}105106function settleEpoch(PoolId poolId, uint64 epochId) external {107Epoch storage epoch = _epochs[poolId][epochId];108if (epoch.settled) revert EpochAlreadySettled();109if (block.timestamp < uint256(epoch.startedAt) + epochDuration) revert EpochNotEnded();110epoch.settled = true;111emit EpochSettled(poolId, epochId, SettlementMode.REFUND_ONLY, epoch.zeroForOneInput, epoch.oneForZeroInput);112}113114function claim(PoolId poolId, uint64 epochId) external {115Epoch storage epoch = _epochs[poolId][epochId];116if (!epoch.settled) revert EpochNotSettled();117UserOrder storage order = _orders[poolId][epochId][msg.sender];118if (order.claimed || order.currency0Input + order.currency1Input == 0) revert NothingToClaim();119order.claimed = true;120Currency[2] memory currency = _currencies[poolId][epochId];121if (order.currency0Input > 0) poolManager.transfer(msg.sender, currency[0].toId(), order.currency0Input);122if (order.currency1Input > 0) poolManager.transfer(msg.sender, currency[1].toId(), order.currency1Input);123emit Claimed(poolId, epochId, msg.sender, order.currency0Input, order.currency1Input);124}125126function currentEpoch() public view returns (uint64) {127return uint64(block.timestamp / epochDuration);128}129130function getEpoch(PoolId poolId, uint64 epochId) external view returns (Epoch memory) {131return _epochs[poolId][epochId];132}133134function getUserOrder(PoolId poolId, uint64 epochId, address user) external view returns (UserOrder memory) {135return _orders[poolId][epochId][user];136}137138function claimable(PoolId poolId, uint64 epochId, address user)139external140view141returns (uint256 currency0Amount, uint256 currency1Amount)142{143UserOrder memory order = _orders[poolId][epochId][user];144if (!_epochs[poolId][epochId].settled || order.claimed) return (0, 0);145return (order.currency0Input, order.currency1Input);146}147148function isSettled(PoolId poolId, uint64 epochId) external view returns (bool) {149return _epochs[poolId][epochId].settled;150}151152function settlementMode() external pure returns (SettlementMode) {153return SettlementMode.REFUND_ONLY;154}155156function _parseUser(bytes calldata data) internal pure returns (address user) {157if (data.length != 32) revert InvalidUser();158user = abi.decode(data, (address));159if (user == address(0)) revert InvalidUser();160}161162function _abs(int256 value) internal pure returns (uint256) {163if (value >= 0) return uint256(value);164unchecked {165return uint256(-(value + 1)) + 1;166}167}168}169
A compiled contract is not necessarily safe. Tests are not an audit. A verified source can still contain exploitable logic.