Fees
Base fees, max sell-off windows, and dynamic fees
A swap can have two pool-level charges: a base fee from the token you sell, and an optional dynamic fee within an enabled max sell-off window, deducted from the token you receive. LPs retain part of the base fee; the protocol receives the rest and the entire dynamic fee. Network fees, token transfer fees and account-creation costs are separate.
Base fee and protocol share
Without token transfer fees, selling 1 token with a 0.3% base fee leaves 0.997 token for curve pricing. If the protocol share is 20%, it takes 20% of the fee, not 20% of the trade. The remaining fee increases LP-owned reserves.
Rates vary by pool and can be updated. The 20% share is an example; use the pool's current settings when quoting.
Exact calculation
| Setting | Encoding | Range |
|---|---|---|
| Base fee | swap_fee_rate, scale 1_000_000 | 0–10%; 3_000 means 0.3% |
| Protocol share | protocol_fee_rate, scale 10_000 | 0–100%; 2_000 means 20% |
The base fee and its protocol share each round up to a raw input-token unit: 3.998 rounds to 4. For tiny fees, rounding can alter the effective protocol percentage and leave zero LP income. For quotes, use the exact fee pipeline and reserve accounting.
Max sell-off window
A token's max sell-off window caps how much all traders together can sell into one pool over time. The pool admin sets its duration and capacity. The counter uses input received after token transfer fees, before the pool's base fee. Older sales gradually fade from the counter. Exceeding the cap rejects the swap, regardless of accepted fees or slippage.
Window capacity
After bucket rotation and any required rescaling:
cap = floor(snapshotVirtualBalance × maxSelloffPct / 10,000)
effectiveBefore = floor(previousSales × (period − elapsed) / period) + currentSales
require effectiveBefore + receivedAmountIn <= capThe window counts received input before the pool's base fee. The previous bucket fades linearly while the current bucket accumulates sales. Reusing a virtual-balance snapshot prevents sales from increasing their own cap. Liquidity changes, range updates, and buys rescale the snapshot and counters together. Selling cannot inflate its own capacity. At rotation, carryover is rescaled when the snapshot basis changes.
max_selloff_pct = 0 disables both cap and dynamic fee. With a positive
cap, variable_fee_slope_high_pct = 0 disables only the fee. Updating
the policy does not reset accumulated sales; the next swap uses the new
settings. Permissions are described in
Pool Controls.
Dynamic fees
Dynamic fees are an optional part of the max sell-off window. The rate is zero through the threshold. Just above it, the rate starts at the configured low rate, then rises toward the high rate at full capacity. A kink can add a middle rate and change how steeply the fee rises. These settings apply to the token being sold into the pool.
The fee is deducted from the output token and reserved for the protocol. Only output produced by the above-threshold part of a sale is charged. The rate displayed at the current fill is a point on the curve; the swap's charge uses the rates traversed by that trade. See the annotated example.
Fee curve and integer calculation
| Parameter | Meaning |
|---|---|
| Threshold | Window fill through which the rate is zero |
| Low | Rate immediately above the threshold |
| Kink | Absolute window fill where the middle rate applies |
| Middle | Rate at the kink |
| High | Rate at 100% window fill |
The slope_* fields store rates. Rates and threshold use scale 10_000;
the kink uses whole percentages of total window capacity. For example,
threshold 8_000 and kink 90 mean 80% and 90% fill. The kink sits halfway
through the remaining 80–100% interval. Kink 0 gives one straight line
from low to high, ignoring the middle rate. A nonzero low rate produces
a jump immediately above the threshold.
Only output attributable to selling above the threshold is taxed. The implementation divides that span into four segments, prices each segment's output, and applies its average configured rate. The segment contributions are summed, then the total is rounded upward once and bounded by gross output.
vaultOutput = grossCurveOutput − dynamicFee
userOutput = vaultOutput − tokenTransferFee(vaultOutput)
minimum_amount_out is checked against userOutputSegmentation reduces the advantage of splitting a sale, but remains an approximation: partitioning, rounding, state, and elapsed time can change the total fee. Use a version-compatible full swap quote, not gross output multiplied by the final window rate.
The LP portion of the base fee stays in trading reserves. The protocol portion and dynamic fees accumulate in per-token protocol-fee balances; LPs cannot currently claim dynamic fees. See fee storage and the planned LP claim mechanism.