A Levee limit order sells one token for another once the price reaches a level you choose, and earns swap fees while it waits. Under the hood it is a narrow Uniswap v3 position placed just beyond the current price, holding only the token you want to sell; as the price moves through it, the pool converts it into the other token. You place one from the Limit mode of the ticket on the Trade page, and the RangeOrders contract holds it until it is filled or you cancel.
Levee DLMM pairs have a second kind of limit order that rests in a single price bin and fills at exactly that bin's price. Those are described in Levee pools; this chapter covers the Uniswap kind.
Setting the price
You choose a side, a limit price and an amount, and the app (limitBand in lib/trade/limit.ts) builds the band:
- Sell the asset above the market: the asset itself goes into the band.
- Buy the asset below the market: the other token goes into the band.
The near edge of the band is the usable tick closest to your price, moved one spacing outward if rounding left it touching the current price. A sell price at or below the market, or a buy price at or above it, is refused before anything is sent. The band is built from the pool's own slot0, and the button stays locked while that price and the API's differ by more than 100 bps. Only v3 pools are supported.
The order converts as the price walks across the band, so your average price lies between its two edges: about 0.1 % apart on a 0.05 % pool, 0.6 % on a 0.30 % pool and 2 % on a 1 % pool. The panel shows the near edge as "Fills at" and an estimate of the fees the order would earn per day if the price sat inside it.
What the contract checks
place re-checks everything against the pool at the moment the transaction lands.
- 01InputsA passed
deadlineisDeadlineand a zeroamountInisZeroAmount.token0must sort belowtoken1andtickLowermust be belowtickUpper, orBadRange. The pool comes fromfactory.getPool(token0, token1, fee); none isNoPool. - 02Side of the priceSelling token0 (
zeroForOne) needstickLower > tick. Selling token1 needstickUpper ≤ tick. A band that touches the current price would need both tokens and isBadRange. - 03FundingWith ETH attached, the sold token must be WETH and
msg.valuemust equalamountIn(ZeroAmountotherwise); the ETH is wrapped. Without ETH, the token is pulled withtransferFrom, which needs an exact-amount approval first. The Limit mode pays in ETH automatically when you sell WETH. - 04PositionThe contract mints the position to itself with the full amount on the sold side and zero on the other. Minimums are zero because the unused side is zero by construction. Whatever the mint could not use is returned to you at once, as an ERC-20: the contract never pays out ETH, because a fill pays the owner rather than the caller.
- 05RecordThe order is stored under the next id (ids start at 1) with you as
ownerand statusOpen, andPlacedis emitted.
The position NFT stays with the contract, so a fill needs no approval from you. Your claim on it is the owner field of the order, which cannot be changed.
When an order is done
A Uniswap position covers ticks from tickLower up to, but not including, tickUpper. That sets both the placing rule and the closing rule.
A sell-token0 order sits above the price. It must start strictly above the current tick, because at tickLower itself the position would already hold some token1. It is fully converted once the tick reaches tickUpper: fillable is true when tick ≥ tickUpper.
A sell-token1 order sits below the price. It may end exactly at the current tick, because a position whose upper tick equals the current tick is already all token1. It is fully converted only once the tick drops strictly below tickLower: fillable is true when tick < tickLower, since at tickLower the position is live again.
A partly crossed band holds both tokens and is not fillable. Orders have no clock: one the price never reaches stays open, earning fees, until you cancel it.
Closing an order
Fill is open to anyone once fillable is true. The contract marks the order Filled, then:
- Removes the position's live liquidity, collects and burns the NFT. It reads the liquidity from the position manager rather than the figure stored at placing, because anyone can add liquidity to a position the contract holds. Without that, a few wei of donated liquidity would stop the burn and lock your order; with it, the donation goes to you.
- Splits what came back into principal and fees: the decrease returns principal only, so the fee portion is what
collectpaid on top. - Sends 10 % of the fee portion of each token (
PROTOCOL_FEE_BPS= 1000) to theFeeRouter, booked to you so that your referrer earns 20 % of it. - Pays the caller 1 % (
KEEPER_BOUNTY_BPS= 100) of the converted principal, taken from your share of the bought token. - Sends everything else to you and emits
Filled.
Cancel is yours alone (NotOwner for anyone else) and works whenever the order is Open, inside the band or outside it. It runs the same unwind and the same 10 % share of earned fees, pays no bounty, and returns the position as it stands: all principal if the price never touched the band, a mix if it is part-way through, all the other token if it has crossed. Once an order is Filled or Cancelled it cannot reopen; a second close is NotOpen.
Who closes orders
In practice Levee's keeper bot does. Every minute by default, it reads fillable for every open order in one Multicall3 batch. For each crossed order it values the bounty at 1 % of the order's size in USD, converts that to wei at the ETH price, and sends fill only when the bounty is at least three times the estimated gas (GAS_BOUNTY_MULTIPLE). If the order cannot be priced, it skips rather than sending blind. After a success it leaves the order alone for 5 minutes; after a skip or a failure, for 15. A small order may therefore wait for cheaper gas, or for you or anyone else to call fill.
A filler may also push the price through the band and then close the order. That is allowed by design: it is the same trade any arbitrageur could make against the position, it pays the position fees on the way, and it can only happen at or beyond your price. The filler still cannot act before the cross, take part of an order, or redirect the proceeds: they always go to the owner.
Reference
Call parameters
| Name | Allowed | Default | Effect |
|---|---|---|---|
token0 | the lower-sorted pool token | - | Not reordered for you; the wrong order is BadRange. |
token1 | the higher-sorted pool token | - | With token0 and fee, finds the pool. |
fee | 100 · 500 · 3000 · 10000 hundredths of a bip | - | The pool fee tier. |
zeroForOne | true · false | - | true sells token0 into a band above the price; false sells token1 into a band at or below it. |
tickLower | above the current tick when selling token0 ticks | - | Lower edge of the band; one spacing below tickUpper in the app. |
tickUpper | at or below the current tick when selling token1 ticks | - | Upper edge of the band. |
amountIn | above 0 wei of the sold token | - | The principal. Equals msg.value when paying in ETH. |
deadline | unix seconds | now + 600 | Latest block time; also passed to the position manager. |
orders(orderId) returns the stored order, including owner, tokenId (the NFT the contract holds), pool, the ticks, amountIn (the principal the position actually took) and status. nextOrderId() is the id the next order will get.
Reverts
| Revert | Cause |
|---|---|
Deadline | place after deadline. |
ZeroAmount | amountIn is zero, ETH was sent for a token that is not WETH, or msg.value differs from amountIn. |
BadRange | Tokens out of order, tickLower not below tickUpper, or the band on the wrong side of the current tick. |
NoPool | No pool for the pair and fee. |
NotFillable | fill before the price has crossed the whole band. |
NotOwner | cancel by anyone but the order's owner. |
NotOpen | fill or cancel on an order already closed. |
Emitted events
| Event | Emitted by | Carries |
|---|---|---|
Placed | place | orderId, owner, tokenId, zeroForOne, the two ticks, amountIn after the leftover was returned |
Filled | fill | orderId, keeper (the caller), amountOut (converted principal before the bounty), feesEarned (bought token only), bounty |
Cancelled | cancel | orderId, amount0 and amount1 paid to the owner |
feesEarned counts only the bought token: the swaps that cross an order pay their fee in the token the order is buying, so the other side is dust. The 10 % protocol share still applies to both. The indexer turns these events into the rangeOrder table behind the Range orders lists on the Trade page and in Portfolio, and behind the bot's list of open orders.
contracts/src/RangeOrders.solplace, fillable, fill, cancel, the unwind and the fee splitcontracts/test/RangeOrders.t.solboth sides, the cross, the split, native ETH and the donated-liquidity caseapps/web/lib/trade/limit.tslimitBand and earnsPerDayUsdapps/web/app/(terminal)/trade/[asset]/LimitPanel.tsxthe Limit mode: ETH or approval, and the price check before placingapps/api/src/bots/keeper.tssweepRangeOrders: fillable in one batch and the bounty in weiapps/api/src/bots/chain.tsGAS_BOUNTY_MULTIPLE and the skip rule