Hooks
Dynamic fee
A built-in hook that raises a pool's fee after its price moves and lets it decay back to the base fee while trading is calm.
LPs lose the most to traders right after the price moves, when stale quotes get picked off. A fixed fee charges the same in calm and in turmoil. The dynamic-fee hook measures how far each swap moves the pool's price and charges more while recent swaps have been moving it, then lets the extra decay away second by second.
It measures only this pool's own swaps. It reads no external price and needs no keeper.
How the fee is set#
The hook keeps one number, an exponentially weighted moving average (EWMA) of how many ticks each swap moved the price.
After each swap it takes the swap's movement, |tick after − tick before|, and folds it in:
ewma = ewma × (1 − weight) + movement × weightBefore each swap it first decays the average for the seconds that passed since the last call, then turns it into a fee:
ewma = ewma × (1 − min(elapsed × decay, 1))
extra = min(ewma × sensitivity, max_extra)
fee = min(base_fee + extra, pool max fee)All values are integers in parts per million and every division rounds down, exactly as on chain. Elapsed time comes from the cluster's Clock.
Settings#
| Setting | Field | Documented default | Meaning |
|---|---|---|---|
| Weight | ewma_weight_ppm | 250,000 | Share of the newest swap's movement in the average (25%). |
| Sensitivity | sensitivity_ppm | 100 | Extra fee per tick of average movement, in ppm (0.01%). |
| Max extra | max_extra_fee_ppm | 10,000 | Ceiling on the extra part (1%). |
| Decay | decay_per_second_ppm | 10,000 | Share of the average forgotten per second (1%); fully gone after 100 s. |
The pool's own base fee and max fee bound the result. The app's Create page starts the max extra at 7,000 ppm with a 1% max fee.
Permissions#
The hook needs before swap (1), after swap (2) and fee override (16): mask 19. The pool's max fee should leave room above the base fee, or the hook has nothing to raise.
What the fee is in a quote#
The Socket API computes the before-swap fee at the server's current second, using the same arithmetic as the hook. Because the hook charges at the second the transaction executes, a quote and the executed fee can differ by a second or two of decay. Slippage limits absorb it.
State#
The hook's state lives in the pool's hook-state account (["hook-state", pool] under the built-in hooks program): the configuration, the current EWMA in ppm-ticks, the last tick and the last timestamp. The app's pool page reads it to drive the fee gauge, which ticks down locally between swaps.