Levee pools are the venue Levee runs itself on Robinhood Chain. DAMM pools and stock pools are Uniswap v4 pools behind a Levee hook; DLMM pairs keep liquidity in fixed-price bins. Anyone can open a pool or provide to one, and how a swap is routed across them is in Swaps and routing.
Choosing a pool type
| Pool | Price moves | Fee per swap | Good for |
|---|---|---|---|
| DAMM | along v4 ticks | base fee, optional anti-snipe schedule, volatility surcharge | new tokens, volatile pairs |
| DLMM | bin to bin, fixed inside a bin | bin-step preset plus a volatility term | shaped liquidity, limit orders |
| Stock | v4 ticks inside an oracle band | set by the market session | tokenized stocks |
Each is opened in one permissionless transaction from the create form on the Pools page: DammHook.createPool, DlmmFactory.createPair or StockHook.createPool.
Where each swap fee goes
A hook works out the total fee, gives the LP part to the pool as that swap's LP fee, and charges the protocol part on the input token: in beforeSwap for exact-input swaps, in afterSwap for exact-output ones. It is paid straight to the FeeCollector, or held as an ERC-6909 claim that anyone can sweep there when the PoolManager is short of that token mid-swap. A DLMM pair charges its fee in each bin it crosses, sends the protocol share to the FeeCollector at the end of the swap and leaves the rest in the bins. The share is read on every swap. What the FeeCollector does with it is in Buyback and burn.
DAMM pools
The fee in pips (100 pips = 1 bp) has three parts:
- Schedule.
baseFeeBps, or during an anti-snipe window a fee that falls in a straight line fromsnipeStartFeeBpsto the base oversnipeSeconds. - Surcharge.
variableFeeControl × vol² / 100 000, wherevoladds up the ticks recent swaps moved the price and decays to zero over 600 s. At a control of 10 000, 100 ticks of movement adds 10 bps. - Cap. 500 bps, or the schedule itself while it is higher.
currentFee(id) returns the LP part and the total right now. createPool(key, sqrtPriceX96, params) needs the dynamic-fee flag and the DammHook in the key; the form offers tick spacings 10, 60 and 200. Launched tokens use the same pools, see Launch a token.
| Name | Allowed | Default | Effect |
|---|---|---|---|
baseFeeBps | 5 - 100 bps | 30 in the form | The fee outside any anti-snipe window. |
snipeStartFeeBps | base - 5 000 bps | 2 000 in the form | Where the window starts. Ignored when snipeSeconds is 0. |
snipeSeconds | 0 - 3 600 s | off, or 600 | Length of the window. 0 means none. |
variableFeeControl | uint32 | 10 000 in the form | Scales the surcharge. 0 switches it off. |
DLMM pairs
Bin id trades at (1 + binStep / 10 000)^(id − 2^23) of token Y per token X, in raw units. Inside a bin the price is constant, so a swap fills at exactly that price until the bin is empty, then moves on to the next bin with liquidity.
createPair(tokenX, tokenY, binStep, activeId) makes one pair per token pair and bin step, ERC-20 only (use WETH for ETH). A pair copies its bin step's fee preset when it is created:
| Bin step (bps) | 1 | 5 | 10 | 25 | 50 | 100 |
|---|---|---|---|---|---|---|
| Base fee (bps) | 2 | 5 | 10 | 20 | 40 | 80 |
A variable fee grows with the square of how far, in bins, the price has recently travelled. With the default presets it tops out near 1 %, and the pair caps the total at 10 %.
Providing to a pair
DlmmPositionNFT.mint gives you one ERC-721 over a range of bins, with a weight per bin. The pool page builds the weights from a shape: Spot spreads evenly, Curve concentrates near the active bin, Bid-Ask weights the edges to buy dips and sell rallies. The deposit is guarded: it reverts if the active bin has moved more than ceil(50 / binStep) bins (at least 1), if less than 99.5 % of what you typed lands, or after the deadline. A deposit into the active bin with a different X/Y mix pays the swap fee on the part that amounts to a swap.
Fees accrue into the bins. decrease withdraws a share of every bin and burn all of it; both take minimum amounts and never check the pause.
Stock pools
A stock pool pairs an AssetRegistry asset with USDG (priced at $1), native ETH or WETH (both priced by Chainlink ETH/USD). createPool(key, 0) starts it at the oracle price. Before every swap the hook reads the session's fee and band; these are the deploy values, tunable per asset by the owner up to a 1 000 bps fee:
| Session | When | Fee | Band |
|---|---|---|---|
| Open | Monday to Friday, 13:30-20:00 UTC, except holidays | 30 bps | ± 200 bps |
| Closed | any other time | 150 bps | ± 50 bps |
| Stale | the asset or quote feed is over 25 h old | 500 bps | ± 50 bps |
The owner shifts the hours for daylight saving and marks holidays. After a swap the price must sit inside the band, or be strictly closer to the oracle than before, so anyone can pull a drifted pool back. While the feed is stale only such toward-oracle swaps pass (PriceOutOfBand, StaleAwayFromOracle). oracleState(id) returns what the Stocks page shows.
Limit orders in a bin
DlmmLimitOrders.place(pair, id, sellX, amount, to, deadline) rests your tokens in one bin past the active one: X above it to sell, Y below it to buy, WrongSide otherwise. Orders in the same bin and side share a batch. As the price moves through the bin the order fills at that bin's exact price and earns swap fees.
Once the price has crossed the whole bin, anyone may execute the batch. Levee's limit-order bot executes every batch readyBooks() lists, and claim executes a filled batch itself. Before execution, cancel returns whatever the bin holds for you. The contract has no owner and no fee, takes and returns ETH for a WETH side, and only place needs the venue unpaused. Uniswap range orders are a separate product, see Limit orders.
Risks
- Liquidity is exposed to the pair's price, and bins or ranges the price has left earn nothing.
- Outside the session a stock's oracle stops moving, so a pool can sit far from where the stock reopens.
- The
ProtocolConfigowner can pause swaps and deposits, raise the protocol share, change presets for new pairs and change a stock's feed, fees, band and hours. It cannot move liquidity; see Security model.
Reference
| Error | When |
|---|---|
OnlyViaCreatePool, NotDynamicFee, WrongHook | a hooked pool initialised outside createPool, or a key with the wrong fee or hook |
BadParams | a DAMM parameter out of range |
Paused, DlmmPair__Paused | a swap or deposit while the venue is paused |
NotRegisteredPair | a stock pool for an asset or quote the registry does not list |
DlmmFactory__BinStepNotAllowed, DlmmFactory__PairExists | no preset for the bin step, or the pair exists |
DlmmPair__OutOfLiquidity | a swap that runs out of bins |
DlmmPair__ZeroShares | a bin in the deposit range would get nothing |
DlmmPositionNFT__ActiveIdSlippage, DlmmPositionNFT__AmountSlippage | the deposit guard |
DlmmLimitOrders__NotFilled, DlmmLimitOrders__AlreadyExecuted | an execute before the bin is crossed, a cancel after execution |
contracts/src/venue/hooks/LeveeHookBase.solthe fee split, the protocol charge, sweepcontracts/src/venue/hooks/DammHook.solcreatePool, the schedule, the surcharge and the capcontracts/src/venue/hooks/StockHook.solthe oracle start price, the band and the toward-oracle rulecontracts/src/venue/core/AssetRegistry.solassets, quotes, sessions and riskParamscontracts/src/venue/core/ProtocolConfig.solthe protocol share, the fee collector, the pausecontracts/src/venue/dlmm/DlmmFactory.solcreatePair and the presetscontracts/src/venue/dlmm/DlmmPair.solbins, swap, mint, burn, _feeRatecontracts/src/venue/dlmm/DlmmPositionNFT.solthe position NFT and its deposit guardcontracts/src/venue/periphery/DlmmLimitOrders.solplace, execute, claim, cancelcontracts/script/venue/VenueWiring.soldeploy-time stock fees and bandspackages/core/src/dlmm.tsbin prices, shapes and their weights