Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Fees

Two layers: pool fee (steward) and protocol fee (network). Both come out of the user's output. The pool owner always receives the full pool fee.

FeePolicy

Proxy: yes.

  • defaultFee in PPM, <= 1_000_000.
  • Optional per-pair override, direction-sensitive (A→BB→A).
  • Pair fee of 0 is treated as unset → falls back to default. You cannot set a literal zero pair fee; removePairFee instead.
getFee(tokenIn, tokenOut)
calculateFee(tokenIn, tokenOut, amount)  // amount * fee / PPM
setDefaultFee / setPairFee / removePairFee  // owner

isActive() always returns true (interface stub).

ProtocolFeeController

Proxy: yes. One per network, shared by pools.

initialize(owner, initialFee, initialRecipient)  // recipient ≠ 0, starts active
getProtocolFee()          // 0 if inactive
getProtocolFeeRecipient()
setProtocolFee / setProtocolFeeRecipient / setActive

Implementation: 0x302E6d52…23B4d.

How SwapPool combines them

totalFee     = quoted * feePpm / PPM
assumedFee   = quoted * 10_000 / PPM        // 1% floor
effectiveFee = max(totalFee, assumedFee)
protocolFee  = effectiveFee * protocolFeePpm / PPM
net          = quoted - totalFee - protocolFee

Skipped if controller unset, protocolFeePpm == 0, or recipient is zero.

Joint constraint: feePpm * (PPM + protocolFeePpm) < PPM² whenever feePpm >= 10_000. Else FeeTooHigh on quote and swap, including dust sizes.

Splitter

Proxy: yes. Pays ETH or ERC-20 to a fixed recipient set.

Allocations in PPM must sum to exactly 1_000_000. ≥2 recipients, no duplicates/zero/self. Only the hash of (accounts, percentAllocations) is stored; distribute calls must pass the same arrays (InvalidHash otherwise).

Fractional remainders accrue. Empty balance is a no-op.