exchange.IO("lp.add", ...)
Open concentrated-liquidity positions by a plan: the missing token is swapped through Jupiter first, and each position is one on-chain transaction.
exchange.IO(k, plan)Examples
-
Add liquidity 5% around the SOL/USDC price.
javascriptfunction main() { var r = exchange.IO("lp.add", {symbol: "SOL_USDC", budget: 100, range: {pct: [-5, 5]}}) if (!r) { Log("Add failed:", GetLastError()) return } if (r.swapTxHash) { Log("Swapped first:", r.swapTxHash) } r.positions.forEach(function(p) { Log(p.id, p.priceLower.toFixed(2), "-", p.priceUpper.toFixed(2), "deposited", p.amountBase, "SOL", p.amountQuote, "USDC", "rent", p.rentSol, "SOL") }) } -
Full workflow: pick a pool → plan → simulate → add → monitor and collect → withdraw.
javascriptvar BUDGET = 100 // budget (USDC) var MIN_FEES = 1 // collect once uncollected fees exceed 1 USDC function main() { // 1. List the pools and pick one var pools = exchange.IO("lp.pools", "SOL", "USDC") if (!pools || pools.length == 0) { Log("No pools:", GetLastError()) return } var pool = exchange.IO("lp.select", "SOL", BUDGET, {quote: "USDC"}) if (!pool) { Log("Selection failed:", GetLastError()) return } Log(pools.length, "candidates, picked", pool.dex, pool.id, pool.reason) // 2. Plan: 5% around the price var plan = exchange.IO("lp.plan", {symbol: "SOL_USDC", budget: BUDGET, pool: pool.id, range: {pct: [-5, 5]}, shape: "spot"}) if (!plan || plan.shortfall) { Log("Plan failed or funds short:", plan ? plan.shortfall : GetLastError()) return } Log("positions", plan.positions.length, "swap", JSON.stringify(plan.swap), "rent", plan.rentSol) // 3. Simulate var sim = exchange.IO("lp.simulate", plan) if (!sim || !sim.ok) { Log("Simulation did not pass:", sim ? JSON.stringify(sim.positions) : GetLastError()) return } // 4. Add liquidity var r = exchange.IO("lp.add", plan) if (!r) { Log("Add failed:", GetLastError()) return } var ids = r.positions.map(function(p) { return p.id }) Log("Created", ids.join(", "), "transactions", r.txHashes.join(", ")) // 5. Monitor: leave the loop when out of range, collect when fees add up while (true) { Sleep(60 * 1000) var list = exchange.IO("lp.positions", "SOL_USDC") if (!list) { continue } var mine = list.filter(function(p) { return ids.indexOf(p.id) >= 0 }) var out = mine.some(function(p) { return !p.inRange }) var fees = mine.reduce(function(s, p) { return s + p.feesValue }, 0) LogStatus(_D(), "positions", mine.length, "out of range", out, "fees", fees.toFixed(4)) if (out) { break } if (fees > MIN_FEES) { var c = exchange.IO("lp.collect", ids) Log("Collected:", c ? c.txHashes.join(", ") : GetLastError()) } } // 6. Withdraw everything, close the positions for their rent, sell the SOL back to USDC var rm = exchange.IO("lp.remove", ids, 1, true) if (!rm) { Log("Remove failed:", GetLastError()) return } rm.positions.forEach(function(p) { Log(p.id, "received", p.receivedBase, "SOL", p.receivedQuote, "USDC", "fees", p.feesBase, p.feesQuote, "rent back", p.rentRefundSol) }) }
Returns
| Type | Description |
object / null value |
|
Arguments
| Name | Type | Required | Description |
k | string | Yes | Set to |
plan | object | Yes | A plan returned by |
See Also
Jupiter exchange.IO exchange.IO("lp.plan") exchange.IO("lp.simulate") exchange.IO("lp.positions") exchange.IO("lp.collect") exchange.IO("lp.remove") exchange.IO("lp.increase")
Remarks
Steps
- Re-plans from the plan's
paramsat the latest pool price and wallet balances; ashortfallis an error and nothing is sent. - When the plan needs a swap, a Jupiter market order of this exchange object swaps first (slippage per
exchange.IO("slippage")) and is waited for. - Re-plans again after the swap; when slippage and fees leave the balances slightly below the plan, each position is scaled down in proportion.
- One transaction per position: the DLMM position account, or the Raydium and Orca position NFT mint, is created with a freshly generated key that co-signs.
- After confirmation,
amountBaseandamountQuoteare the amounts actually deposited, from the balances before and after;rentSolis the rent the transaction locked (new BinArrays, TickArrays and the position accounts included) andfeeSolthe network fee.
Execution
Every transaction is simulated by the node before sending and is not sent when the simulation fails, with the reason reported (DLMM, Raydium CLMM and Orca Whirlpool error codes with their names); the compute-unit limit is the simulated usage plus headroom, and the priority fee is the 75th percentile of recent fees paid by transactions writing the same accounts, capped by exchange.IO("priorityFee"). The call returns after confirmation, with actual amounts from the balances before and after. When within 120 seconds the transaction is neither confirmed nor provably expired, the error says outcome is unknown with the signature: it may have landed, so check it with exchange.IO("receipt") before retrying.
Position IDs and failures
- Position
ids are writtenmeteora:position account address,raydium:position NFT mintororca:position NFT mintand are passed tolp.increase,lp.decrease,lp.collectandlp.remove; positions created in this process are remembered, solp.positionsalways finds them. - When a step after the swap fails, the error carries the swap's signature (the swapped tokens stay in the wallet); when one of several positions fails, the error lists the positions already created.
Risks
- Impermanent loss: the further the price moves from the entry, the less the position is worth compared with simply holding.
- Once the price leaves the range, the position holds only one token and earns no fees.
- Rent for new BinArrays and TickArrays is not returned; split positions each lock their own position rent.
- Every add, rebalance, collect and withdrawal is an on-chain transaction; frequent rebalancing can cost more than the fees earned.