AngstromL2
AngstromL2.sol is the Uniswap v4 hook that enforces Angstrom’s MEV tax, fee splits, and LP compensation on rollups. The contract implements the IBeforeSwap, IAfterSwap, IAfterAddLiquidity, and IAfterRemoveLiquidity hooks (plus the IBeforeInitialize guard).
Immutables & State
FACTORY— the deploying factory address. Only it may override protocol fees or read the globalwithdrawOnlyflag.UNI_V4— provided byUniConsumer; the sole pool manager the hook trusts.- Tax constants:
SWAP_TAXED_GAS = 100_000,SWAP_MEV_TAX_FACTOR = 49,
JIT_TAXED_GAS = 100_000,JIT_MEV_TAX_FACTOR = 73(1.5× swap factor). PoolFeeConfiguration(perPoolId): stores creator/protocol swap & tax fees in E6 units plus anisInitializedguard.PoolRewards(perPoolId): aggregates maker compensation using the same growth pattern as Uniswap v3/v4 fee accounting.- Transient storage (
liquidityBeforeSwap,slot0BeforeSwapStore) caches pool state betweenbeforeSwapandafterSwap. _cachedWithdrawOnlymirrors the factory’s emergency switch so hot paths avoid external calls.
The contract exposes receive() to hold native token while swap deltas are being settled.
Pool Setup & Fee Configuration
initializeNewPool(PoolKey key, uint160 sqrtPriceX96, uint24 creatorSwapFeeE6, uint24 creatorTaxFeeE6)
may be called by the pool owner or the factory. It enforces:
key.currency0must equal the rollup’s native token (zero-address currency).- Dynamic LP fees are disallowed (
LPFeeLibrary.isDynamicFeemust be false). - Creator swap/tax fees stay below
MAX_CREATOR_SWAP_FEE_E6 = 20%andMAX_CREATOR_TAX_FEE_E6 = 50%. - Each pool is initialised only once.
On success the hook:
- Calls
UNI_V4.initialize. - Records creator swap/tax fees.
- Asks the factory for starting protocol shares via
recordPoolCreationAndGetStartingProtocolFee. - Checks that creator+protocol percentages never exceed 100% (
_checkFeeConfiguration).
Additional configuration entry points:
setProtocolSwapFee/setProtocolTaxFee— factory-only overrides for a pool’s protocol shares. Both re-run the 100% guard.getPoolFeeConfiguration— read the current creator/protocol split.withdrawCreatorRevenue(Currency, address to, uint256 amount)— owner-only sweep of native or ERC20 balances collected by the hook.pullWidthrawOnly()— refresh the cached emergency flag from the factory; whenwithdrawOnlyis true, swaps & liquidity adds revert.
Swap Lifecycle
beforeSwap
uint256 tax = SWAP_MEV_TAX_FACTOR * SWAP_TAXED_GAS * (tx.gasprice - block.basefee);
- Snapshots pool liquidity and tick using transient storage.
- Returns a
BeforeSwapDeltathat debitstaxunits of the native token (currency0) from the caller. No funds move yet—the router must make the balance good when it later callsIPoolManager.settle. - Reverts with
WithdrawOnlyModeif the factory has paused swaps.
getSwapTaxAmount(priorityFee) exposes the same calculation off-chain for routers quoting trades.
afterSwap
- Calculates the taker swap fee and splits it between creator and protocol using the pool-specific configuration.
- Splits the recorded tax into creator, protocol, and LP compensation shares, minting the LP portion into the pool with
UNI_V4.mint. - Updates reward accounting (
PoolRewardsLib.updateAfterTickMove) and runs the compensation solver (_zeroForOneDistributeTax/_oneForZeroDistributeTax) so LPs accrue deterministic payouts. - Returns the hook’s net delta (creator/protocol shares of fee + tax) in the unspecified currency. Routers settle these balances, together with the MEV tax recorded in step 1, during the
settlestep on the pool manager.
Liquidity Callbacks & JIT Tax
afterAddLiquidityandafterRemoveLiquiditycharge the JIT tax
JIT_MEV_TAX_FACTOR * JIT_TAXED_GAS * priorityFee,
diverting the full amount to the protocol treasury to deter JIT cex-dex attacks.- Both callbacks update
PoolRewardsso new or exiting positions inherit the correct growth baseline. afterRemoveLiquidityadditionally burns (UNI_V4.burn) pending rewards to push the native-token compensation into the withdrawal delta.
getJitTaxAmount(priorityFee) mirrors the on-chain JIT calculation for routers.
Reward Accounting Helpers
getPendingPositionRewards(PoolKey, owner, lowerTick, upperTick, bytes32 salt)returns the native-token rewards awaiting a Uniswap position (using the same key derivation as v4).- Internal state keeps:
globalGrowthX128— rewards-per-liquidity accumulator,
rewardGrowthOutsideX128[tick]— boundary growth mirroring Uniswap’s fee growth.
Guards & Access Control
beforeInitializealways reverts; pools must go throughinitializeNewPool.setProtocolSwapFee,setProtocolTaxFeerequiremsg.sender == FACTORY.withdrawCreatorRevenue,initializeNewPool(manual path) are owner-only.UniConsumer._onlyUniV4()protects all hook callbacks so only the configured pool manager can call them.- Errors such as
CreatorFeeExceedsMaximum,TotalFeeAboveOneHundredPercent, andUnauthorizedgive explicit feedback on misconfiguration.
With these primitives the hook stays minimal: deploy via AngstromL2Factory, initialise the pool, and then route trades through standard Uniswap v4 flows. The hook enforces MEV capture, fee sharing, and LP compensation automatically while routers simply budget the extra native-token delta at settlement.