exchange.IO("lp.plan", ...)
Work out a position plan from a budget, a price range and a shape: which pool, how many positions, how much of each token, whether to swap first and how much rent to lock. Read-only, no transaction.
exchange.IO(k, params)Examples
-
Plan a position 5% around the SOL/USDC price and check whether a swap is needed first.
javascriptfunction main() { var plan = exchange.IO("lp.plan", {symbol: "SOL_USDC", budget: 100, range: {pct: [-5, 5]}}) if (!plan) { Log("Plan failed:", GetLastError()) return } Log(plan.protocol, plan.pool, "price", plan.price, "positions", plan.positions.length) plan.positions.forEach(function(p) { Log("range", p.priceLower.toFixed(2), "-", p.priceUpper.toFixed(2), "SOL", p.amountBase, "USDC", p.amountQuote) }) Log("needs", plan.needBase, "SOL", plan.needQuote, "USDC", "rent", plan.rentSol, "SOL", "refundable", plan.rentRefundableSol) if (plan.shortfall) { Log("Not enough funds:", plan.shortfall) } else if (plan.swap) { Log("Swap first:", plan.swap.side, plan.swap.amount, plan.swap.currency) } Log("Estimated daily fees", plan.estDailyFeesUsd, "USD") } -
Compare spot and curve on a Meteora pool: a wide spot range is split into several positions, while curve must fit in one.
javascriptfunction main() { var pool = exchange.IO("lp.select", "SOL", 100, {quote: "USDC", protocol: "meteora"}) if (!pool) { Log("Selection failed:", GetLastError()) return } // about 60 bins: fits in one position var pct = 30 * pool.binStep / 100 var curve = exchange.IO("lp.plan", {symbol: "SOL_USDC", budget: 100, pool: pool.id, range: {pct: [-pct, pct]}, shape: "curve"}) if (curve) { Log(curve.strategy, "bins", curve.bins, "positions", curve.positions.length) } var spot = exchange.IO("lp.plan", {symbol: "SOL_USDC", budget: 100, pool: pool.id, range: {pct: [-10, 10]}, shape: "spot"}) if (spot) { Log(spot.strategy, "bins", spot.bins, "positions", spot.positions.length, spot.warning) } } -
Plan a 4-layer curve on a Raydium pool.
javascriptfunction main() { var plan = exchange.IO("lp.plan", {symbol: "SOL_USDC", budget: 100, protocol: "raydium", range: {pct: [-5, 5]}, shape: "curve", layers: 4}) if (!plan) { Log("Plan failed:", GetLastError()) return } Log("tickSpacing", plan.tickSpacing, "layers", plan.layers, "/", plan.layersRequested) plan.positions.forEach(function(p) { Log("ticks", p.tickLower, p.tickUpper, "share", (p.share * 100).toFixed(1) + "%") }) }
Returns
| Type | Description |
object / null value | The plan object, fields in the remarks; it can be handed to |
Arguments
| Name | Type | Required | Description |
k | string | Yes | Set to |
params | object | Yes | The plan parameters |
See Also
Jupiter exchange.IO exchange.IO("lp.simulate") exchange.IO("lp.add") exchange.IO("lp.select") exchange.IO("lp.ticks") exchange.IO("lp.positions")
Remarks
Plan parameters
| Key | Required | Description |
|---|---|---|
symbol | yes | The pair, such as SOL_USDC: sets the price orientation (quote per base) and the currency of budget |
budget | yes | The budget in the quote token: the total value of both tokens |
range | yes | The price range, in one of four forms below |
shape | no | The shape: spot (default), curve or bidask, see below |
pool | no | The pool address or protocol:address; without it a pool is picked by the rule of lp.select |
protocol, fee, binStep, tickSpacing | no | Pool filters when pool is not given, as the options of lp.select |
slippage | no | Price protection ratio, default 0.01, at most 0.5, see below |
layers | no | Layers of curve and bidask on Raydium and Orca, default 3, at most 20 |
Forms of range:
{lower, upper}: prices (quote per base).{pct: [-5, 5]}: percent around the current price.{bins: [lowerBinId, upperBinId]}: DLMM bin IDs (both ends included).{ticks: [tickLower, tickUpper]}: Raydium and Orca ticks.binsandticksare synonyms, read as bin IDs or ticks by the pool's protocol.
Prices are rounded outward to bins, or aligned outward to the tick spacing.
Shapes
Meteora DLMM distributes by bin with DLMM's three strategies (strategy): spot is SpotImBalanced (equal value per bin), curve is CurveImBalanced (thicker towards the price on both sides) and bidask is BidAskImBalanced (thicker away from it). The active bin and those below hold the pool's Y, those above hold X. A position holds at most 70 bins: a wider spot range is split into several positions (one transaction and one rent deposit each, at most 10, explained in warning), while curve and bidask ranges wider than 70 bins are an error.
Raydium CLMM and Orca Whirlpools are planned the Uniswap way: spot is one range, curve nested ranges in layers (thickest at the price) and bidask segments on both sides of the price (thicker further out, the cell holding the price left empty). layers is the number of layers; in a narrow range the layers actually built (layers) can be fewer than layersRequested. Each position is one NFT and one transaction, at most 20 positions per plan. Orca's Splash pools (tick spacing of 32768 or more) take full-range positions only, so range and shape are ignored (explained in warning).
Price protection (slippage)
- DLMM: converted into the allowed drift of the active bin in bins,
maxActiveBinSlippage; the transaction fails when the active bin moves further at execution. - Raydium: caps on the token amounts: one token's amount is fixed (the SOL side first) and the other is capped at the most it can take while the price moves within this ratio.
- Orca: bounds the execution price;
priceBoundsare the resulting limits, beyond which the transaction fails.
Result fields
- Common:
params(the parameters as given),symbol,pool,protocol,fee,feeRate,price(quote per base),shape,budget,budgetUsd,slippageBps,volume24hUsd. positions: each position's{lowerBinId, upperBinId, priceLower, priceUpper, amountBase, amountQuote, share}(tickLowerandtickUpperon Raydium and Orca),sharebeing its share of the plan's total value.needBaseandneedQuoteare the amounts the plan deposits,haveBaseandhaveQuotewhat the wallet has available (the SOL side net of the reserve for rent and fees, wSOL in the wallet counted as SOL);swapis the swap needed before adding,{side, amount, currency}(1% extra for headroom), ornull; when both tokens together are not enough,swapisnullandshortfalldescribes the gap: the two amounts the plan deposits (the same asneedBaseandneedQuote), what the wallet holds 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; on the SOL side it also states what is kept back for rent and fees.rentSolis the rent the positions lock, of whichrentRefundableSolis that of the position accounts (plus the NFT mint and NFT account on Raydium and Orca, and two ticks on Orca), returned on closing; the rest is for new BinArrays and TickArrays and is not returned.shareOfPoolis the plan's share of the liquidity at the price (on DLMM, of the active bin), andestDailyFeesUsd=volume24hUsd×feeRate×shareOfPool(assuming the price stays in range); both are 0 for a plan with no liquidity at the price (such asbidask).- DLMM adds
strategy,binStep,activeId,lowerBinId,upperBinId,bins(number of bins),maxActiveBinSlippageandbinArraysToCreate(indexes of BinArrays to create); Raydium and Orca addtickSpacing,tick,tickLower,tickUpper(the whole range),layers,layersRequestedandtickArraysToCreate(start ticks of TickArrays to create), plusliquidities(each position's liquidity) on Raydium andpriceBoundson Orca;warningappears when something needs explaining.
A plan is a snapshot: lp.simulate and lp.add re-plan from its params at the latest pool price and balances. The fee estimate uses the past 24h volume and is no promise of future income.