Write a self-contained Uniswap v4 hook on the starter's BaseHook that records, per pool, the pool tick observed at the first swap of each block, and exposes those recorded snapshots through read-only view functions. Overwrite nothing on later swaps within the same block. The hook must take no fee, hold no privileged role, have no owner or upgrade path, and must not modify anything already present in the tree.

Ship tests using the existing launch-pool harness that cover: the first swap in a block records a tick; a second swap in the same block leaves the record unchanged; a swap in a later block records a new snapshot; and reads for a pool that has never swapped behave sanely rather than reverting ambiguously.

Work

  1. Posted11 minto the first attempt
  2. Build2 files changed

    Done. forge build is clean (no warnings) and forge test passes 27/27 — 19 new plus the 8 inherited starter tests, which still pass untouched.

    What was built

    src/FirstSwapTickRecorder.sol — a BaseHook with beforeSwap: true and every other permission false.

    Sampling happens in beforeSwap, so the tick written for a block is the one the previous block left behind. That choice is the substance of the hook: a tick sampled after the first swap would be a tick the first swapper picked, so whoever lands first in a block could set it to whatever their capital reaches. Sampling before puts the value out of reach of everyone in the block it belongs to.

    Storage is mapping(PoolId => TickSnapshot[]), append-only, in strictly increasing block order. A {uint48 blockNumber, int24 tick} struct fits one slot, so a recording swap costs one cold SSTORE. Reads:

    • snapshotCount — 0 for a pool never swapped
    • latestSnapshot → (bool recorded, uint256 blockNumber, int24 tick) — never reverts; the flag is what separates "no data" from a genuine tick 0
    • tickAtBlock — binary search, never reverts for any block or pool; a quiet block reports false rather than a neighbour's value
    • snapshots — the whole history
    • snapshotAt — the one revert, SnapshotIndexOutOfBounds(poolId, index, length), because an index read has a defined domain

    No fee (beforeSwapReturnDelta off, so v4 never reads a delta from it), no owner, no admin function, no delegatecall, no way to receive value.

    One judgment call worth flagging: uint48 block numbers require a decision at the boundary. It refuses to write past block 281,474,976,710,655 rather than truncating, since a truncated block number would file a snapshot under a block that never happened and collide with real history. Unreachable in practice; both sides of it are tested.

    On the tests

    Since there's no separate reviewer here, I checked the suite rather than trusting it: I compiled four mutated copies of the hook in test/scratch/ — no same-block guard, overwrite-instead-of-ignore, one global history instead of per-pool, and record-in-afterSwap — and re-ran the identical suite against each. All four were killed (3, 2, 1 and 11 failures respectively). The per-pool mutant is caught by exactly one test, test_HistoriesAreIndependentPerPool, which builds a second launch pool on the same hook.

    Beyond the four required cases, the suite covers: the recorded tick differing from the post-swap tick, quiet-block gaps, sells as well as buys, exactly one event per recording block, per-pool isolation, the swapper's delta being untouched and the hook holding neither balances nor ERC-6909 claims, the pool fee unchanged, beforeSwap rejected from a non-PoolManager caller, both ends of the block range, and a fuzz property over arbitrary block gaps and swaps-per-block.

    Nothing outside the two granted paths changed; test/scratch/ is removed.

    ran onclaude · claude-opus-5 · 38 turns · 10m 38s · 60 in · 50.4K out · 2.4M cached
    submission689cdc9aee13f43485761a0ae8273bac29ff162179304c87d3b8f07004fa9d0e
    device0edd2bbb66d2d014fbbda834d6ccbc278847c31414f601db126e7a1269baddd9
    started from851023a457f1267800b4c26ea6c72fd98bc87ec9
    bundlea421d5a11b6131bbca297fd3d7c2b471847c5e48f3b93e484ee8ff8d0bf496d2 · 11 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 2 files
    src/FirstSwapTickRecorder.soltest/FirstSwapTickRecorder.t.sol
    may write
    src/FirstSwapTickRecorder.soltest/FirstSwapTickRecorder.t.sol