The hook and anti-snipe
A Uniswap v4 hook is a contract the PoolManager calls at precise moments in a swap.
StockFunHook's current implementation uses six permissions: beforeInitialize,
beforeAddLiquidity, beforeSwap, afterSwap, and the two delta permissions that let it
take a cut. Since 2026-10-02 its address carries all 14 v4 permissions; the callbacks
it does not use pass through.
The address encodes the permissions
v4 reads a hook's low address bits to know when to call it. The address is therefore not chosen: it is mined via CREATE2 until a value turns up whose bits match the declared permissions.
Since 2026-10-02 the address mined is a proxy's, with all 14 permission bits set. An upgrade replaces the implementation behind that address without moving it, so it never needs a new mine, and a later implementation can use any callback. The current one lets the callbacks it does not use pass through: they return their selector and, where a delta is expected, zero. The proxy's initialization checks that its address carries every bit.
Practical consequence: the mined address depends on the proxy's bytecode and its
constructor arguments, which carry the implementation's address. A new deployment must be
re-mined. This is also why foundry.toml sets bytecode_hash = "none" — without it the
mined address no longer matches the deployed contract.
The beforeAddLiquidity permission, added on 2026-10-01, changed the bits themselves: the
address was re-mined, and any deployment made before that date is stale. The proxy of
2026-10-02 changed them again: any deployment made before that date is stale too.
Taking the fee
On a buy, the fee is taken in beforeSwap, from the incoming ETH, before the swap
happens. The hook withdraws its share from the PoolManager and returns a delta that
charges it to the buyer. The StockFun router and the liquidity lock pay the buyer's ETH
into the PoolManager before the swap, so their buys never draw on the ETH the
PoolManager already holds.
Any other v4 router pays the same tax rate. But one that settles the buyer's ETH after the
swap, as v4-periphery's V4Router does with its default encoding, has the tax taken from
the ETH the PoolManager already holds, and its buy reverts when the tax is larger. That
can happen on a PoolManager holding little ETH, a testnet's for instance. An integrator
should pay the buyer's ETH in before the swap, as the official router does. Sells are not
affected. The security pipeline of 2026-10-01 documented this limit. Taking the tax as
PoolManager claims instead would lift it for every router, but would move the treasury's
2 % from paid during the trade to credited and paid later: on 2026-10-05 the owner decided
to keep the tax as it is taken today.
On a sell, it is taken in afterSwap, from the outgoing ETH, once the amount is known.
In both cases the hook splits immediately: treasury, creator, team, buyback. Only the treasury line leaves during the trade, sent to the market's vault. The other three lines are credited on the hook and paid outside any trade.
Since 2026-10-05 a vault that refuses the treasury line no longer makes the trade fail: the
hook keeps the amount as owed to that vault (treasuryOwed, event TreasuryOwed), and
every buy and sell goes on. Anyone pays the debt to that vault with payTreasury, which
fails and keeps the debt while the vault still refuses; the keeper tries every cycle. The
anti-snipe surplus follows the same rule. Until then the send failed loudly: a vault that
could not receive, after a faulty upgrade for instance, made every trade of its market fail,
sells included.
The creator share is pull-based: it accrues on the hook, market by market, and the
creator claims it when they want, one transaction per market (claimCreatorFees). Since
2026-10-05 a creator that cannot take ETH itself, a contract without a way to receive it,
claims to another address (claimCreatorFeesTo); only the creator can.
Since 2026-10-01 the team and buyback shares are credited the same way, one balance each,
and paid by claimTeamFees() and claimBuybackFees(). Anyone can call them; they pay only
the team and buyback wallets the factory names at the time of the claim. The
BuybackBurner pulls its balance itself at the start of each burn. A wallet that refuses
ETH only delays its own payout: its claim fails and the balance stays on the hook.
Until then, those two shares were sent during the trade, with an escrow fallback when the
send failed. The security audit of 2026-09-29 showed that a wallet which accepted the send
and then called the PoolManager could halt trading on every pool. The escrow is gone.
What the hook holds between trades is owed to someone: the creators' balances, the team's
and the buyback's, and the vaults' debts. ETH sent to it by anyone but the PoolManager is
counted apart, as strays (strayEth). Since 2026-10-05 StockFun's owner can move strays out
(rescue): at most that amount of ETH, and any token, since the hook never holds one.
Nothing the hook owes can leave that way. ETH forced in without a call is not counted, and
waits for an upgrade.
Only the lock adds liquidity
Since 2026-10-01, beforeAddLiquidity refuses every liquidity addition to a StockFun pool
except the liquidity lock's.
A position placed just beside the current price acts as a limit order: a trader's swap crosses it and converts it, and the trader pays the tax, while the position's owner adds and removes it without ever paying the 5 %, nor, in the first ten blocks, the anti-snipe tax. The security audit of 2026-09-29 reproduced it. The pools charge no LP fee by default, so no legitimate use needs a third-party position.
The lock's own fee collections, with a liquidity delta of zero, go through the remove-liquidity path, which the current implementation lets through: they are unaffected. So is the end mode's recovery, which takes the positions out through the same path: see The two-position launch.
Decaying anti-snipe
A v4 pool is live the moment it is initialised. Without protection, the first blocks after creation would be taken by bots. Since 2026-09-28, those blocks pay a heavier tax, which falls at each block, on buys and sells alike. With the default settings:
| Block since launch | Tax |
|---|---|
| 1 | 80 % |
| 2 to 10 | 72, 64, 56, 48, 40, 32, 24, 16, 8 % |
| 11+ | Normal, 5 % |
A bot that buys in the opening block pays 80 % at once: sniping loses money.
Three things deserve explanation.
The creator's launch buy needs no identity check. The only swap the LiquidityLock
ever performs is the creator's optional buy, inside its own creation callback. So "the
caller is the lock" implies "we are inside the creation transaction", and that buy pays the
normal 5 %.
The surplus has its own destination. The normal 5 % keeps its usual split. The part
above it goes to the market's treasury, and so to the holders at the next airdrop. On the
$STOCKFUN market it goes to the team's balance on the hook, paid by claimTeamFees().
The whitelist needs identity, and that is the subtle part. The hook sees the router,
not the buyer. The StockFun router therefore carries its caller's address in the hook data,
and the hook trusts that field only if the caller is the official router, the one
recorded in the factory, which the owner can change at any time (accepted on 2026-09-28).
No tx.origin is used anywhere in this protocol.
Documented limitation: a whitelisted address routing through a third-party aggregator during the first ten blocks is not exempt.
The whitelist
Set by the market's creator. The addresses on it pay the normal 5 % during the anti-snipe blocks. Until 2026-09-28 the list was held by the owner and shared by every market, precisely so it could not become an insider advantage; a creator can now exempt their own wallets. Validated on 2026-09-28: the list is fixed in the creation transaction, public, immutable, and capped at 20 addresses by default.
The $STOCKFUN market
The decaying tax applies too. $STOCKFUN has no external creator: its surplus goes to the
team's claimable fees, and its whitelist is set by the owner at launch.
The settings
Since 2026-10-05 every number on this page is a setting of StockFun's owner, changed on
the hook with setTaxSettings: the tax, 5 % by default; its lines, 2 / 2 / 0.5 on a
launched market and 2 / 2.5 on $STOCKFUN, the buyback taking the rest; the anti-snipe's
opening tax, 80 %; its decrease per block, 8 points; the blocks it lasts, the creation
block included, 10; and the largest whitelist, 20 addresses.
A change applies from the next swap, on every pool, a pool still inside its anti-snipe blocks included: the decay is computed with the settings in force, from the pool's launch block. Whatever the settings, the anti-snipe never charges less than the tax. The setter refuses a rate above 100 % and a schedule whose lines exceed the tax. The whitelist cap is checked when a market is created: a list already set stays as it is.
Upgrades
Since 2026-10-02 StockFun's owner can upgrade the hook, with immediate effect. The tax, its
split, the anti-snipe and the whitelists described on this page are those of the current
implementation. An upgrade keeps the hook's address and must keep the same PoolManager;
the factory, which the hook reads and which decides who may upgrade it, is fixed in the
implementation.