Documentation

Lotus · One-Way Directional Liquidity

Lotus lets a holder provide liquidity as directional intent: deposit sell-side inventory across ascending price bins and let takers buy through it cheapest-first. Filled inventory becomes claimable quote and can never re-enter the market in the opposite direction. It is an inventory schedule, not a set of orders.

Concept

Overview#

Concentrated-liquidity range orders have a flaw for large holders: if you place a range to sell ETH into strength and price retraces, the position re-converts back into ETH. You never actually exit. Because of this round-trip risk, the biggest holders avoid providing liquidity at all.

Lotus removes the reverse path. A maker deposits inventory (e.g. WETH) across a ladderof ascending price bins — “sell 20% at 2×, 30% at 3×”. Takers buy through the bins cheapest-first. Whatever fills converts to claimable QUOTE (e.g. USDC) and stays there. State is monotonic: inventory only goes down, proceeds only go up.

The point
Liquidity becomes path-dependent on purpose. You express where you want to sell, the inventory works while it waits (see Productive yield), and a retrace can never un-sell what already filled.

Built at ETHGlobal NY. Tracks 1inch Aqua (the fill primitive) and LI.FI (cross-chain entry).

Mechanics

How a ladder works#

A ladder is a list of bins. Each bin holds a slice of sell-side inventory at a fixed price. The maker creates it once; from then on the bins are passive targets that takers consume.

Cheapest-first fill

A taker brings QUOTE and calls a buy. The fill walks bins from the lowest price upward, consuming each bin before moving to the next. This guarantees takers always get the best available price and that the maker’s lowest bins clear first.

The one-way invariant

Every bin carries strictly monotonic state. Inventory can only decrease; filled amount and accrued proceeds can only increase. There is no opcode, no path, no admin action that converts proceeds back into inventory.

invariant — enforced per bin
inventory   only ↓     // never refilled, never bought back
filled      only ↑     // cumulative sold, monotonic
proceeds    only ↑     // claimable QUOTE, monotonic
                       // no reverse path: QUOTE ⇏ inventory

Lifecycle

The core exposes a small, frozen surface. A maker calls createLadder, takers preview with quoteBuy and execute with fillBuy, and the maker pulls proceeds with claim. Unsold bins can be withdrawn with cancel.

LotusPositionManager — core (frozen ABI)
createLadder(
  address inventory,   // sell-side asset, e.g. WETH
  address quote,       // proceeds asset, e.g. USDC
  uint256[] prices,    // ascending bin prices
  uint256[] amounts    // inventory per bin
) returns (uint256 ladderId)

quoteBuy(ladderId, quoteIn) view returns (out, bins[])
fillBuy(ladderId, quoteIn, minOut)   // cheapest-first, atomic
claim(ladderId)    // pull accrued QUOTE
cancel(ladderId)   // withdraw unsold inventory
Capital efficiency

Productive yield#

Idle inventory should not sit dead in the ladder. While a bin waits to be filled, its inventory is supplied to a Morpho ERC-4626 vault — for example the Moonwell Flagship ETH vault (mwETH) on Base. The maker earns vault yield on inventory that has not sold yet.

Just-in-time recall

When a taker fills, the needed inventory is recalled from the vault inside the taker’s fill transaction. Nothing is pre-withdrawn and nothing is left exposed; the withdraw and the swap settle in the same atomic step.

The maxWithdraw guard

Before recalling, the yield adapter checks the vault’s maxWithdraw. If the vault cannot return enough liquidity right now, the fill reverts — the whole transaction rolls back. The maker never loses inventory to an illiquid vault; the worst case is a temporarily un-fillable bin.

fill path — JIT recall (atomic)
// inside fillBuy(), per touched bin:
need = binAmount(bin)
require(vault.maxWithdraw(adapter) >= need);  // else revert, no loss
vault.withdraw(need, to, adapter);            // recall just-in-time
// ... router swaps inventory -> QUOTE, proceeds booked to bin
Depth is safety
A deeper vault is a safer recall: the more liquidity in the vault, the larger the fill it can service without ever hitting the maxWithdraw limit. Yield and fill-reliability both improve with vault depth.
Entry

Cross-chain entry#

Inventory rarely starts where the ladder lives. Lotus uses LI.FI to bring inventory in from another chain, then create the ladder on the destination.

Sequenced, not atomic

Atomic destination contract-calls are not reliably available across every route, so Lotus sequences the flow rather than assuming a single atomic hop: first bridge the inventory, then call createLadder on the destination once funds land.

cross-chain flow
1. bridge  — LI.FI route: source chain ──▶ destination chain
2. wait    — inventory confirmed on destination
3. create  — createLadder(inventory, quote, prices, amounts)
Proven on testnet
USDC bridged from Arbitrum Sepolia → Base Sepolia via LI.FI, then createLadder executed on Base Sepolia end-to-end.

Supported source chains

Inventory can be brought from:

  • Ethereum
  • Base
  • Arbitrum
  • Optimism
  • BNB Chain
Under the hood

Architecture#

Lotus is three pieces: a frozen core, a fill adapter, and a yield adapter.

Core — LotusPositionManager (frozen ABI)

The position manager owns all ladder state and enforces the per-bin monotonic invariant. Its ABI is frozen — createLadder, quoteBuy, fillBuy, claim, cancel — so adapters and integrators can build against a stable surface.

Fill adapter — the ONEWAY_FILL Aqua opcode

A 1inch Aqua adapter exposes a custom SwapVM opcode, ONEWAY_FILL. Takers swap through 1inch’s router straight into a ladder: the router routes ordinary liquidity, and when it reaches the Lotus leg the ONEWAY_FILL opcode drives the cheapest-first fill against the core. This is proven end-to-end.

SwapVM leg
ONEWAY_FILL(ladderId, minOut)
  └─ LotusPositionManager.fillBuy(...)   // cheapest-first, monotonic
     proceeds booked to bins; taker receives inventory

Yield adapter — ERC-4626

A pluggable ERC-4626 adapter supplies idle inventory to a vault and performs the maxWithdraw-guarded just-in-time recall during a fill. Because it speaks the 4626 standard, the vault is swappable (Moonwell Flagship ETH today). See Productive yield.

Questions

FAQ#

What if the price drops?
Nothing liquidates. You simply keep the unsold inventory sitting in the bottom bins, still earning vault yield, waiting for price to come back. There is no margin, no liquidation, no forced round-trip. Only bins that actually traded are converted to proceeds.
What if the yield vault is illiquid at fill time?
The fill reverts and no loss occurs. The adapter checks maxWithdrawbefore recalling inventory; if the vault can’t return enough, the entire transaction rolls back atomically. The bin is temporarily un-fillable, never lost.
Can filled proceeds turn back into inventory?
No. That is the whole premise. The invariant is enforced per bin — inventory only decreases, proceeds only increase — and there is no reverse path in the core.
How is this different from a Uniswap V3 range order?
A V3 range re-converts if price retraces back through your range, so you can end up holding the asset you tried to sell. Lotus bins are one-way: once filled, they stay filled. It is an inventory schedule, not a reversible liquidity position.
How do takers reach a ladder?
Through the ONEWAY_FILL 1inch Aqua opcode — takers swap via the 1inch router and the Lotus leg fills the ladder cheapest-first. Or directly against fillBuy on the core.
Ready to build a ladder?Launch AppView Dashboard →