Type/to search
Built-in Functions
Structures
Built-in Variables and Constants
Conventions
Exchanges
Capability Matrix
Centralized Exchanges
Decentralized Exchanges (DEX)
Prediction Markets
Securities and Futures
On-chain & Custom

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.

    javascript
    function 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.

    javascript
    var 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

TypeDescription

object / null value

{txHash, txHashes, swapTxHash, pool, protocol, symbol, positions}: txHash is the first add transaction's signature and txHashes all of them; swapTxHash is the signature of the swap before adding (null without one); each item of positions is {id, txHash, lowerBinId, upperBinId, priceLower, priceUpper, amountBase, amountQuote, rentSol, feeSol} (tickLower and tickUpper on Raydium and Orca). A null value is returned when funds are short or a simulation or transaction fails.

Arguments

NameTypeRequiredDescription

k

string

Yes

Set to lp.add to add liquidity.

plan

object

Yes

A plan returned by lp.plan, or plan parameters (the params of lp.plan).

See Also

Remarks

Steps
  1. Re-plans from the plan's params at the latest pool price and wallet balances; a shortfall is an error and nothing is sent.
  2. 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.
  3. Re-plans again after the swap; when slippage and fees leave the balances slightly below the plan, each position is scaled down in proportion.
  4. 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.
  5. After confirmation, amountBase and amountQuote are the amounts actually deposited, from the balances before and after; rentSol is the rent the transaction locked (new BinArrays, TickArrays and the position accounts included) and feeSol the 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 written meteora:position account address, raydium:position NFT mint or orca:position NFT mint and are passed to lp.increase, lp.decrease, lp.collect and lp.remove; positions created in this process are remembered, so lp.positions always 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.