Skip to main content

Fee Architecture

Ramses uses two distinct fee models:
  • Concentrated-liquidity pools have a pool-level fee set by governance
  • DLMM pools combine a base fee with a variable fee driven by on-chain bin movement
For concentrated liquidity, Ramses decouples tick spacing from fees. Each tick spacing has a default initial fee, but governance can adjust an individual pool through factory.setFee(pool, newFee). DLMM uses a separate parameter set described below.

Concentrated-Liquidity Fee Model

How Fees Are Set

Concentrated-liquidity fees are not determined by an on-chain algorithm during swaps. Instead:
  1. Each tick spacing has a default initial fee set in the factory’s tickSpacingInitialFee mapping
  2. When a pool is created, it inherits the initial fee for its tick spacing
  3. Governance can adjust any pool’s fee at any time via RamsesV3Factory.setFee(pool, fee)
  4. The pool stores its current fee and applies it to all swaps

Default Tick Spacing / Fee Mapping

Fees are denominated in parts per million (1/1,000,000). For example, 100 = 0.01%, 3000 = 0.30%.

DLMM Fee Model

DLMM charges:
The base fee is:
where 1e18 represents 100%. Expressed in basis points:
The variable fee rises quadratically with volatilityAccumulator × binStep. The accumulator increases when swaps move across bins and decays according to: The fee observes on-chain DLMM activity only. It does not read CEX prices or an external volatility oracle. Total trader fees are capped at 10%. Governance can update presets for future pools and static parameters on an existing pool. Integrators should quote against live pool state rather than reproduce the fee algorithm from stale configuration.

Concentrated-Liquidity Fee Distribution

LP Fee Collection

Swap fees are distributed pro-rata to all in-range liquidity at the time of the swap:
  • In-Range Positions: Positions where tickLower ≤ currentTick < tickUpper earn fees
  • Out-of-Range Positions: Positions outside the active price earn 0 fees
  • Fee Accrual: Fees accumulate continuously as long as a position remains in-range
  • Manual Collection: LPs must call collect() to claim accumulated fees (not auto-compounded)

Protocol Fee Split (feeProtocol)

The feeProtocol setting determines what percentage of swap fees are routed to the protocol (FeeCollector) vs staying with LPs. It is also denominated in parts per million (1/1,000,000). Ramses uses these target configurations:

Gauged policy (0% to LPs, 100% to voters)

The gauged policy uses feeProtocol = 1,000,000 so all swap fees enter FeeCollector, plus treasuryFees = 0 so the collector forwards that entire amount to the pool’s FeeDistributor for xRAM voters. LPs receive emissions from the gauge instead of swap fees. Gauge association alone is not proof that both values are set. Voter and governance synchronization paths can update them independently, so integrations must read the pool and collector state.

Fee-only policy (95% to LPs, 5% to treasury)

The default fee-only policy uses feeProtocol = 50,000 (5%). This means:
  • 95% of swap fees go to LPs (collected via pool.collect())
  • 5% goes to the FeeCollector, which sends it to the treasury (since there’s no gauge/FeeDistributor)

Fee Flow

If treasuryFees is non-zero, the collector diverts that configured share before notifying the FeeDistributor.

DLMM Fee Distribution

For DLMM, protocolShare is measured out of 10,000 and applies to the swap fee:
The remainder stays in bin reserves. It therefore compounds into the redemption value of per-bin LP shares rather than becoming a separately claimable balance. Ramses’ Robinhood product policy is: The optional gauged path replaces LP swap-fee retention with RAM emissions distributed by DLMMRewarder to eligible bins. Reaching the stated 100%-to-voters policy also requires DLMMFeeCollector.treasuryFees = 0; that collector value is independently mutable.
At block 22,674,244, the Robinhood factory was not connected to Voter, sampled pools had no rewarder hook, and its open presets and sampled pools used protocolShare = 500, or 5%.
protocolShare is mutable. Read it from DLMMPool.getStaticFeeParameters(); do not infer it from a pool label or from the factory’s generic default constant.

Protocol Fee Distribution

Contracts: FeeCollector.sol for concentrated liquidity and DLMMFeeCollector.sol for DLMM. When collectProtocolFees(pool) is called, the relevant collector:
  1. Pulls the protocol’s share from the pool
  2. If the pool has no gauge (or gauge is not alive): sends everything directly to the treasury
  3. If the pool has a live gauge: splits fees between the FeeDistributor (for voters) and the treasury based on the treasuryFees ratio

FeeCollector Interface

Distribution Cycle

  1. Accumulation: Fees accumulate in pools continuously
  2. Collection: Anyone can call collectProtocolFees(pool) to trigger collection and distribution (permissionless)
  3. Voter Claims: On gauged pools, xRAM holders claim from FeeDistributor proportional to their voting weight
  4. CL LP Claims: On non-gauged CL pools, LPs collect their retained share via collect()
  5. DLMM LP Accounting: On fee-only DLMM pools, the retained share stays in bin reserves and increases LP share value

Integrator Considerations

Querying Current Fees

Always query or quote the current fee before estimating swap outputs.
For DLMM, reject a quote when amountInLeft != 0, because the pool cannot completely fill the input.

Slippage protection

Since fees and reserves can change before execution, set appropriate slippage tolerance:
The route quote must include token prices, decimals, fee configuration, available tick/bin liquidity, and price impact. Subtracting a fee from amountIn is not an output quote.

Events

Additional Resources