exchange.IO("lp.plan", ...)
Builds a position plan (read-only, costs nothing): from a budget, a price range and a shape, it computes each position's range, liquidity and token amounts, whether the wallet covers them and what must be swapped first.
exchange.IO(k, params)Examples
-
Plan a ±5% range position in the chosen pool and see the funds needed and whether the wallet covers them.
javascriptfunction main() { var sel = exchange.IO("lp.select", "ETH", 100, {quote: "USDC"}) var params = {symbol: "ETH_USDC", budget: 100, pool: sel.id, range: {pct: [-5, 5]}} var plan = exchange.IO("lp.plan", params) if (!plan) { Log("Plan failed:", GetLastError()) return } var p = plan.positions[0] Log("Range:", p.priceLower, "~", p.priceUpper, "price:", plan.price) Log("Needs:", plan.needBase, "ETH +", plan.needQuote, "USDC") Log("Estimated daily fees:", plan.estDailyFeesUsd, "USD") if (plan.swap) { Log("Swap first:", plan.swap.side, plan.swap.amount, plan.swap.currency) } if (plan.shortfall) { Log("Wallet short:", plan.shortfall) } } -
A three-layer curve: the positions near the price are the thickest.
javascriptfunction main() { var plan = exchange.IO("lp.plan", {symbol: "ETH_USDC", budget: 100, range: {pct: [-5, 5]}, shape: "curve", layers: 3}) plan.positions.forEach(function (p) { Log(p.priceLower, "~", p.priceUpper, "share of budget:", p.share) }) if (plan.warning) { Log(plan.warning) } } -
Aerodrome and Velodrome: a plan without range is a basic (volatile) pool plan.
javascriptfunction main() { var plan = exchange.IO("lp.plan", {symbol: "ETH_USDC", budget: 100, stable: false}) Log(plan.protocol, plan.pool, "deposit:", plan.amountBase, "ETH +", plan.amountQuote, "USDC", "LP tokens:", plan.lpTokens) }
Returns
| Type | Description |
object / null value | The plan; see the remarks for its fields. Pass it unchanged to |
Arguments
| Name | Type | Required | Description |
k | string | Yes | Set to |
params | object | Yes | Plan parameters such as |
See Also
Uniswap / PancakeSwap Aerodrome / Velodrome exchange.IO exchange.IO("lp.select") exchange.IO("lp.simulate") exchange.IO("lp.add") exchange.IO("lp.pools")
Remarks
Keys of params:
| Key | Description |
|---|---|
symbol | Pair such as ETH_USDC; prices are quote per base |
budget | Budget in the quote token |
range | Price range, in one of three forms: {lower, upper} prices; {pct: [-5, 5]} percent around the current price; {ticks: [lo, hi]} ticks. Aligned to the pool's tick spacing |
shape | spot (default) one range; curve nested layers, thickest at the price; bidask segments on both sides, thicker further out |
layers | Number of layers for curve and bidask, default 3, at most 20 |
pool | Pool ID; when omitted the pool is picked by the lp.select rule (using fee and tickSpacing below) |
fee | Preferred fee tier when picking the pool (millionths) |
slippage | Price protection, default 0.01 (1%), at most 0.5 |
tickSpacing, protocol | Aerodrome and Velodrome: filters when picking a Slipstream pool; protocol defaults to ["slipstream", "slipstream2"] |
stable | Aerodrome and Velodrome basic-pool plan: true a stable pool, false a volatile pool; both when omitted |
stake | Aerodrome and Velodrome: true makes lp.add stake into the gauge right after adding |
Fields of the plan:
| Field | Description |
|---|---|
params | The parameters, echoed |
symbol, pool, protocol, fee, tickSpacing, tick, price | The chosen pool and the current price |
shape, layers, layersRequested | Shape, actual and requested layer count; warning explains a count below the request |
budget, budgetUsd | Budget in the quote token and in USD |
positions | Each position {tickLower, tickUpper, priceLower, priceUpper, liquidity, amountBase, amountQuote, share}, share being its part of the budget |
needBase, needQuote | Token amounts the positions need |
haveBase, haveQuote | Amounts available in the wallet |
swap | The swap needed first, {side, amount, currency} (sell sells base, buy buys base), or null |
payWith | With a native pair, the coin that pays the wrapped-token side (such as ETH or WETH); otherwise null |
shortfall | Why the wallet falls short, or null; lp.add fails while it is set. Its content is described under Funding below |
shareOfPool | Share of the pool's liquidity at the current price after adding |
volume24hUsd, estDailyFeesUsd | The pool's 24h volume and the daily fees estimated from the pool share (USD), an extrapolation of past volume |
slippageBps | Price protection in basis points |
Funding: a token the wallet lacks is bought with the other one (1% extra), swap gives the amount and lp.add places that market order first; when the two tokens together do not cover the budget, shortfall explains it: the two amounts the plan deposits (the same as needBase and needQuote), what the wallet holds (haveBase, haveQuote) and its value in the quote token at the pool price, and, when the missing token has to be bought with the other one, how much of that other token is needed in all (deposit plus swap cost, with 1% headroom) and how much is missing. For example, with a budget of 100 USDC, a plan depositing 0.02 ETH + 50 USDC and a wallet holding only 60 USDC, it says that buying the missing 0.02 ETH takes about 50.5 USDC, so the USDC side needs 100.5 USDC in all (50 to deposit + 50.5 for the swap) and is 40.5 USDC short. About 3 million gas at the current gas price is kept back from the native coin for fees. In V3 and Slipstream pools the native coin takes part as its wrapped token, so with a native pair (such as ETH_USDC) the wrapped-token side is paid either entirely in the native coin or entirely in the wrapped token, never a mix: the native coin when it covers the whole side, otherwise the wrapped token when that does, and when neither does the larger balance is used for the calculation; payWith is the coin actually used. In V4/Infinity CL native and wrapped pools are different pools, each using its own coin.
Pool choice: without pool the pool is picked by the lp.select rule every time, and lp.simulate and lp.add recompute a plan from its params (at the latest pool price), so they may pick another pool; to add to the pool you looked at, put pool in params. V4/Infinity CL pools on Uniswap and PancakeSwap always need pool (from lp.select with {protocol: "v4"} or {protocol: "infinity"}).
Range and shape: a range above the current price holds only the base token and one below it only the quote token; estDailyFeesUsd is then 0, and fees are earned once the price enters the range. curve layers all cross the current price, are distinct and aligned to the tick spacing, nesting inward first and filling with the range's other price-crossing combinations when nesting runs out; a two-sided range holds at most (legal lower bounds below the price) × (legal upper bounds above it) layers, and beyond that the plan gives as many as fit.
Aerodrome and Velodrome: a range means a Slipstream pool; without range (or with a basic pool) the plan is for a basic pool, depositing both tokens in the reserve ratio, and is {params, symbol, pool, protocol, stable, fee, price, budget, budgetUsd, needBase, needQuote, haveBase, haveQuote, amountBase, amountQuote, lpTokens, swap, shortfall, shareOfPool, volume24hUsd, estDailyFeesUsd, slippageBps}, lpTokens being the expected LP tokens. stake true stakes right after adding (see exchange.IO("lp.stake")).