Pools and liquidity
Liquidity management
Status: live for Uniswap V3 and V4 pools on testnet. Aero Slipstream and Nest CL (Algebra) are built and tested on other chains; they switch on on Elysium as soon as their position managers are published and added to positionManagers (Nov 9).
How it works
Lane deploys no liquidity contract and never holds LP funds. For each action the API builds the call to the venue's own position manager, lists the approvals to send first, and simulates the call from the user's address. The user signs it; the position is an NFT in their wallet, visible in the venue's own UI too. Same model as /quote.
| Venue | Position manager | How tokens are pulled |
|---|---|---|
| Uniswap V3 | NonfungiblePositionManager | ERC20 approve to the manager; the WHYPE side can be paid in native HYPE (native=true) |
| Aero Slipstream | Slipstream NonfungiblePositionManager (pools by tick spacing) | ERC20 approve; WETH side payable natively |
| Nest CL (Algebra Integral), Algebra 1.9 | Algebra NonfungiblePositionManager (no fee tier; Integral adds deployer) | ERC20 approve; native via WNativeToken / refundNativeToken |
| Uniswap V4 | PositionManager | Permit2: approve the token to Permit2 once, then a Permit2 allowance for the manager (30 days); native HYPE pools take HYPE directly |
Only the managers listed in positionManagers in the network file are used (to read positions and to build transactions). Discovery (the owner of V3 Mint events, the sender of v4 ModifyLiquidity events, checked with factory() / poolManager()) only suggests candidates, listed with trusted: false and logged for the operator: anyone can call a pool's mint and answer factory() with the real factory, so trusting discovery would let a fake manager collect token approvals. GET /v1/liquidity/managers lists both. On testnet: V3 0x28580f9b… (factory B) and V4 0xbE6E9E09… (PoolManager 0x0088BEA…).
The screens
| Figma | Route | Notes |
|---|---|---|
| Choose a range, see the other amount | GET /v1/liquidity/quote | Ticks aligned to the pool's spacing; give amount0 or amount1, the other is computed from the price; also returns the share of the last 30 days the price spent in that range |
| Add liquidity (new position) | GET /v1/liquidity/add | Returns approvals (send in order), then tx |
| Add to an existing position | GET /v1/liquidity/add?tokenId= | Same range as the position |
| My positions | GET /v1/liquidity/positions?owner= | Value, uncollected fees, in range, time in range, APR estimate |
| Remove (25/50/75/100%) | GET /v1/liquidity/remove?percent= | Collects the removed tokens and the fees; 100% also burns the NFT |
| Collect fees | GET /v1/liquidity/collect | Fees only, liquidity untouched |
Ranges
Give either tickLower + tickUpper, or priceLower + priceUpper (human units: token1 per token0, e.g. WHYPE per USDC when USDC is token0), or fullRange=true. Without any, the range is ±10% around the current price. Ticks are rounded outward to the pool's tick spacing. A range entirely above the price takes only token0; entirely below, only token1.
Position fields
| Field | Meaning |
|---|---|
amounts | Tokens the position holds now (at the current price) |
valueUsd | Their USD value |
fees | Uncollected fees, per token and in USD. V3: what collect() returns when simulated from the owner (exact). V4: computed from the PoolManager's fee growth, which matched real collects to the wei on live testnet positions |
inRange | The price is inside the range now (the position earns fees) |
inRangePct30d | "In range 94% of the time": share of the last 30 days' hourly price samples inside the range |
aprEstimatePct | Pool's 24h fees × the position's share of in-range liquidity × 365 ÷ position value. 0 when out of range. An estimate, not a promise |
Example: add then remove
API=https://api.lane.markets/v1; POOL=0x591Aa2eC4275135C640D07F9eAa35CDad52ab05c; ME=0xYou
# 1 HYPE on the WHYPE side, paid natively, ±10% around the price
curl "$API/liquidity/add?pool=$POOL&amount1=1000000000000000000&native=true&owner=$ME"
# -> approvals: [{ to: USDC, data: approve(...) , description }] send them, then call again
# -> tx: { to: NonfungiblePositionManager, data: multicall(mint, refundETH), value }, simulation: { ok: true }
curl "$API/liquidity/positions?owner=$ME"
curl "$API/liquidity/remove?manager=0x28580f9b…&tokenId=42&percent=50&owner=$ME"With the SDK: addLiquidity, positions, removeLiquidity, collectFees (SDK).
Limits (phase 1)
- On Elysium: Uniswap V3 and V4 until Aero's and Nest's position managers are published; the code for them is in place. The manager's dialect (Uniswap, Slipstream, Algebra 1.9, Algebra Integral) is detected from its bytecode.
- A wallet's positions: the newest 50 per manager.
- V4 positions are found by scanning
ownerOfover the newest 200,000 ids of each manager (the V4 NFT isn't enumerable); the scan is cached a minute and shared by concurrent requests. openPositionsin/v1/pools/statsstaysnull: counting every position on the chain needs position indexing.- V4 pools whose hook restricts who can add liquidity will fail in simulation: the API shows the revert.
How it was verified
- V4 fees: the collect call the API builds was run, by a probe placed at each owner's address in a simulation, on 4 live testnet positions: what it received equals the fees the API computed, to the wei (
liquidity-live-check.ts). - Remove, collect, V4 add: simulated from real owners' addresses on live testnet.
- Aero Slipstream (Base) and Algebra 1.9 (Camelot, Arbitrum): mint calldata from the same encoders, replayed on forks (
LiquidityL2.fork.t.sol): exact range and liquidity, native ETH with refund. Algebra Integral (Nest's version) isn't exercised on-chain yet: no test deployment known. - V3 add: the exact calldata the API produced was replayed on a fork (
LiquidityV3.fork.t.sol): the NFT is minted with exactly the liquidity and range the API computed, and unused native HYPE is refunded.
Next: Live prices