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:- Each tick spacing has a default initial fee set in the factory’s
tickSpacingInitialFeemapping - When a pool is created, it inherits the initial fee for its tick spacing
- Governance can adjust any pool’s fee at any time via
RamsesV3Factory.setFee(pool, fee) - 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:1e18 represents 100%. Expressed in basis points:
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 < tickUpperearn 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)
ThefeeProtocol 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 usesfeeProtocol = 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 usesfeeProtocol = 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
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 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:
- Pulls the protocol’s share from the pool
- If the pool has no gauge (or gauge is not alive): sends everything directly to the treasury
- If the pool has a live gauge: splits fees between the FeeDistributor (for voters) and the treasury based on the
treasuryFeesratio
FeeCollector Interface
Distribution Cycle
- Accumulation: Fees accumulate in pools continuously
- Collection: Anyone can call
collectProtocolFees(pool)to trigger collection and distribution (permissionless) - Voter Claims: On gauged pools, xRAM holders claim from FeeDistributor proportional to their voting weight
- CL LP Claims: On non-gauged CL pools, LPs collect their retained share via
collect() - 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.amountInLeft != 0, because the pool cannot completely fill the input.
Slippage protection
Since fees and reserves can change before execution, set appropriate slippage tolerance:amountIn is not an output quote.
Events
Additional Resources
- Concentrated Liquidity
- DLMM & Liquidity Bins
- DLMM Contract Architecture
- Token Contracts & Tokenomics
- RamsesV3Pool Reference
- RamsesV3Factory Reference
