FeeStyleHook
BSCFeeStyleHook is a Uniswap v4 hook on BSC. Runs code on every swap. Sets its own swap fee. Gates who can add liquidity. Runs at pool creation.
- Contract name
- FeeStyleHook
How this hook works
Smart feesA smart fees hook. It changes its fee over time, limits some actions to approved addresses and limits trading to stop bots.
Fee changes over time
Instead of a fixed fee, it sets the fee itself and changes it over time, for example starting high at launch and coming down. The fee goes to liquidity providers; the hook keeps nothing for itself.
The code that does this (beforeSwap):function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata) external onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { if (!LPFeeLibrary.isDynamicFee(key.fee)) return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); PoolId id = key.toId(); PoolState memory st = stateOf[id]; Style memory s = styleOf[id]; (uint160 sqrtP, int24 tick,,) = poolManager.getSlot0(id); uint256 vol; if (s.volFeePerTick != 0) { bool dirty; if (st.exclPending) { // the previous swap was an insider's: nothing else has moved the price since, so this is its move int256 sh = int256(st.shift) + int256(tick) - int256(st.exclFrom); // saturate: only an absurd back-and-forth in one block gets here, and a swap must never revert on it st.shift = int32(sh > type(int32).max ? type(int32).max : (sh < type(int32).min ? type(int32).min : sh)); st.exclPending = false; dirty = true; } if (block.number != st.anchorBlock) { // a new block: fold the move since the last block's anchor, less the insiders' part, into the decayed // record, re-anchor uint256 recorded = _decayed(st.volTicks, st.volAt, s.volDecaySecs); uint256 move = _absDiffWide(int256(tick) - st.shift, st.anchorTick); if (move > recorded) recorded = move; if (recorded > type(uint24).max) recorded = type(uint24).max; st.volTicks = uint24(recorded); // …Approved addresses only
Some actions are limited to addresses on an approved list.
The code that does this (allow):function allow(address account) external onlyOwner { if (frozen) revert AllowlistFrozen(); if (account == address(0)) revert ZeroAddress(); allowed[account] = true; emit Allowed(account); }Limits on trading
It limits trading, for example a max buy or wallet size or a wait between trades, to stop bots grabbing the supply before real traders.
The code that does this (validate):function validate(Style calldata s) public pure { if (s.baseFee < MIN_BASE_FEE || s.baseFee > MAX_BASE_FEE) revert BadStyle(); if (s.sniperStartFee == 0) { if (s.sniperSecs != 0) revert BadStyle(); } else if (s.sniperStartFee <= s.baseFee || s.sniperStartFee > MAX_FEE || s.sniperSecs == 0 || s.sniperSecs > 1 days) { revert BadStyle(); } if (s.volFeePerTick == 0) { if (s.volMaxFee != 0 || s.volDecaySecs != 0) revert BadStyle(); } else if ( s.volFeePerTick > 1_000 || s.volMaxFee == 0 || s.volMaxFee > MAX_SURCHARGE || s.volDecaySecs < 60 || s.volDecaySecs > 1 days ) revert BadStyle(); if (s.sizeMaxFee == 0) { if (s.sizeFullBps != 0) revert BadStyle(); } else if (s.sizeMaxFee > MAX_SURCHARGE || s.sizeFullBps == 0 || s.sizeFullBps > 1_000) { revert BadStyle(); } }Rules for adding liquidity
It decides who can add liquidity, or when.
The code that does this (beforeAddLiquidity):function beforeAddLiquidity(address sender, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) external view onlyPoolManager returns (bytes4) { // through the PositionManager, the wallet behind it must be allowed (our factories); a contract calling the manager itself // must be allowed directly (Longevity's AlignmentVault placing its own standing ask and bid, Sky 11/9) bool ok = sender == positionManager ? allowed[IMsgSender(positionManager).msgSender()] : allowed[sender]; if (!ok) revert LiquidityGated(); return IHooks.beforeAddLiquidity.selector; }Only approved callers create pools
New pools with this hook can only be created by approved callers, for example its own factory or launcher.
The code that does this (beforeInitialize):function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external onlyPoolManager returns (bytes4) { if (!allowed[sender]) revert InitGated(); if (LPFeeLibrary.isDynamicFee(key.fee)) { PoolState storage st = stateOf[key.toId()]; if (!st.registered || st.initialized) revert NoStyle(); st.initialized = true; st.openedAt = uint40(block.timestamp); st.coinIs1 = true; // both factories sort the quote first; recorded for the size / buy direction st.anchorTick = TickMath.getTickAtSqrtPrice(sqrtPriceX96); st.anchorBlock = uint64(block.number); st.volAt = uint40(block.timestamp); } return IHooks.beforeInitialize.selector; }
Who controls it: The hook developer can change who else can run it and who is on the approved list. The hook developer can also call: allow, disallow. The code cannot be changed.
What do these labels mean? Based on the hook’s published code.
What it can do
Its code executes inside each trade in pools that use it.
Pools using it let the hook choose the swap fee instead of a fixed tier.
It checks every deposit before it goes in.
It runs when a pool is created, often to lock in that pool’s settings.
Hook history
- Registered a style 2 times
Latest transactions
- 0x3f6ac693…0abb1b block 133,199,463
- 0xc3d05906…bf24c2 block 133,179,687
Settings changes: set the reference (67 times), set the gap (2 times).
Counted from what the hook recorded on-chain across all its pools, for every action in its published code, plus token transfers in and out of it.