Docs

How Hook Prog works

The root hook, the fee arithmetic exactly as the contract computes it, and what is and is not live.

Overview

Hook Prog is one Uniswap v4 hook that serves many pools. When a pool is opened through it, the pool's rules are written to storage once; the hook reads that record on every swap. There is no function that edits the record, so nothing about a live pool can change afterwards, the owner included.

The hook's address carries the permission bits 0x28cc: beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap and the two swap-delta bits. Removing liquidity is not hooked: LPs can always leave.

Two lanes

New token. launch deploys a fixed-supply ERC-20 of 1,000,000,000 units, opens its pool, and places the whole supply in one token-only band held by the hook. The contract has no function that removes that band. The creator may buy in the same transaction, before anyone else can, up to 5% of supply; that buy is a call from the hook to the pool, which v4 runs without hook callbacks, so it pays no rule.

Existing token. openExisting initializes a fresh hooked pool for a token that already exists. It adds no liquidity, holds no position, and records only who opened it. Launch Guard is refused on this lane, because a guard only means something when the hook holds the only liquidity.

Pools on the hook can only be opened through those two functions. Anyone calling the PoolManager directly with this hook is refused in beforeInitialize.

Fee model

The base LP fee goes to in-range liquidity in full. The protocol takes a share s from inside each opted-in stream, 20% by default and never above 50%, frozen per pool at opening. A pool that opts into nothing but a base fee pays the protocol nothing.

StreamCharged on1 − s goes to
Base LP feeevery swapLPs, 100 %
Dynamic fee above baseevery swap, by sizeLPs
Launch Guard taxbuys while the guard is opencreator
Directional feebuys and sellscreator
LP Rewardsevery swapLPs, by donation
Nth-buy Potbuysthe pot
Founding position feescollected on demandcreator

In integer pips (1,000,000 = 100 %), with surge the dynamic component and tax the directional fee plus the guard tax:

pSurge   = surge * s / 10000
lpFee    = base + surge - pSurge              // the v4 LP fee for this swap
protocol = pSurge + tax*s/10000 + lp*s/10000 + pot*s/10000
creator  = tax - tax*s/10000
lpReward = lp  - lp*s/10000
potAdd   = pot - pot*s/10000                  // buys only
cut      = quoteAmount * (protocol + creator + lpReward + potAdd) / 1e6

previewRates returns these five numbers for any swap before it is sent. The protocol's part accrues per currency in protocolAccrued; sweep, callable by anyone, sends all of it to the treasury address. There is no burn, no buyback and no staking path in the contract. Creators and pot winners withdraw with claim.

The dynamic component is span × min(1, size × sens / depth), where span is ceiling minus base, size the swap's specified amount and depth the in-range virtual reserve of that same currency. The guard tax at time t is snipeTax × (guardEnd − t) / (guardEnd − guardStart).

The four swap directions

Every stream is taken in the quote currency, whichever way the swap is specified, so neither the creator nor the protocol is ever paid in the token being traded.

SwapWhere the cut is takenTrader sees
Exact-input buybeforeSwap, from the quote inputless quote reaches the pool
Exact-output buyafterSwap, on top of the quote inputpays the swap plus the cut
Exact-input sellafterSwap, from the quote outputreceives the output minus the cut
Exact-output sellbeforeSwap, added to the quote outputreceives the exact amount, sells more token

Rule fields and limits

FieldBound
baseFeePips ≤ maxFeePipsat most 200,000 (20 %)
surgeSens0 when the fee is flat, otherwise 1 to 10
snipeTax + max(buyTax, sellTax) + lp + potat most 300,000 (30 %)
guardSecondsnew-token lane only, at most 7 days
maxBuyPerBlockneeds a guard; zero for no cap
potEveryN2 to 100,000 when the pot is on
potMinBuyabove zero when the pot is on
protocolShareBpsat most 5,000, frozen per pool

A rule set outside these bounds reverts with InvalidRules. The builder runs the same checks before you sign.

What the owner can do

  • Move the treasury address. Revenue already accrued follows the new address at the next sweep.
  • Change the default protocol share for pools opened afterwards, within the cap.
  • Pause and resume the opening of new pools.
  • Hand ownership over, in two steps.

The owner cannot change a live pool's rules or share, cannot move a founding position, cannot touch a creator's or a winner's claimable balance, and cannot stop swaps.

Integrating

  • The pool key's fee is the v4 dynamic-fee flag 0x800000; the hook sets the LP fee per swap.
  • Every block works with empty hook data through any router. To name a pot recipient, pass abi.encode(address) as hook data; otherwise the transaction's sender is used.
  • On an exact-input swap the cut is computed on the specified amount. Use the full-fill price limit so a partial fill is not charged on the unfilled part.
  • config(poolId) returns the frozen record; pools(i) and poolCount() enumerate every pool on the hook.
  • Events: PoolOpened, Cut, PotWon, Claimed, Swept, FoundingFeesCollected.

Contract status

The root hook is not deployed on Robinhood Chain yet. Until it is, the builder is a simulator and nothing on this site opens a real market.

HookProg.sol compiles with solc 0.8.26 (via-IR, optimizer 200, cancun) to 23,254 bytes of runtime code. Its end-to-end suite runs against Uniswap's own PoolManager on a local chain: 44 checks covering the launch, the on-chain logo and bio, the creator buy, the guard, all four swap directions, the pot, the payouts, the existing-asset lane and the owner functions, with the hook's balance checked against everything it owes after each step.

The contract has not been audited. A passing test suite is not an audit.

Addresses

Robinhood Chain, chain id 4663. The Uniswap addresses are Uniswap's own deployments.

Known limitations

  • Unaudited.
  • The per-block buy cap and the pot counter use the chain's block.number, which on Robinhood Chain follows the Ethereum L1 block, about twelve seconds, not the L2 block.
  • A per-block cap is shared by everyone buying in that block: one buyer can fill it.
  • When no recipient is named, the pot falls back to the transaction's sender. A smart-account user whose transaction is sent by a bundler should name the recipient in the hook data.
  • A creator who buys through their own guard pays the tax to themselves, less the protocol share.
  • The Discover index is rebuilt from logs and can trail the chain; every page that uses it says to which block it has read.