Skip to main content

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 global withdrawOnly flag.
  • UNI_V4 — provided by UniConsumer; 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 (per PoolId): stores creator/protocol swap & tax fees in E6 units plus an isInitialized guard.
  • PoolRewards (per PoolId): aggregates maker compensation using the same growth pattern as Uniswap v3/v4 fee accounting.
  • Transient storage (liquidityBeforeSwap, slot0BeforeSwapStore) caches pool state between beforeSwap and afterSwap.
  • _cachedWithdrawOnly mirrors 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.currency0 must equal the rollup’s native token (zero-address currency).
  • Dynamic LP fees are disallowed (LPFeeLibrary.isDynamicFee must be false).
  • Creator swap/tax fees stay below MAX_CREATOR_SWAP_FEE_E6 = 20% and MAX_CREATOR_TAX_FEE_E6 = 50%.
  • Each pool is initialised only once.

On success the hook:

  1. Calls UNI_V4.initialize.
  2. Records creator swap/tax fees.
  3. Asks the factory for starting protocol shares via recordPoolCreationAndGetStartingProtocolFee.
  4. 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; when withdrawOnly is 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 BeforeSwapDelta that debits tax units of the native token (currency0) from the caller. No funds move yet—the router must make the balance good when it later calls IPoolManager.settle.
  • Reverts with WithdrawOnlyMode if the factory has paused swaps.

getSwapTaxAmount(priorityFee) exposes the same calculation off-chain for routers quoting trades.

afterSwap

  1. Calculates the taker swap fee and splits it between creator and protocol using the pool-specific configuration.
  2. Splits the recorded tax into creator, protocol, and LP compensation shares, minting the LP portion into the pool with UNI_V4.mint.
  3. Updates reward accounting (PoolRewardsLib.updateAfterTickMove) and runs the compensation solver (_zeroForOneDistributeTax / _oneForZeroDistributeTax) so LPs accrue deterministic payouts.
  4. 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 settle step on the pool manager.

Liquidity Callbacks & JIT Tax

  • afterAddLiquidity and afterRemoveLiquidity charge 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 PoolRewards so new or exiting positions inherit the correct growth baseline.
  • afterRemoveLiquidity additionally 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

  • beforeInitialize always reverts; pools must go through initializeNewPool.
  • setProtocolSwapFee, setProtocolTaxFee require msg.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, and Unauthorized give 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.