For the complete documentation index, see llms.txt. This page is also available as Markdown.

Managing positions: code examples

Code examples for the full concentrated liquidity position lifecycle: reading pool state, opening a position, monitoring it, claiming fees and rewards, and withdrawing.

This page walks through the full lifecycle of a concentrated liquidity position using the Stellar SDK. Make sure you're familiar with Prerequisites & basics — the RPC endpoints, contract addresses and data conversion helpers used below come from there.

All position management calls go directly to the pool contract. You can find the pool address on the pool page of the Aquarius website or programmatically — see Get pools info. Concentrated pools report pool_type: "concentrated" both on-chain (get_info) and in the backend API pool listing — filter the list by that field on your side.

The complete runnable scripts are at the bottom of this page.

Helper functions used in the snippets below (scval_map, simulate) are defined once in the complete code at the end of this page.

Step 1. Read the pool state

Everything starts with two read-only values: the current tick (from get_slot0) and the tick spacing. Read-only calls are executed through transaction simulation — no signature required.

# Current price state: {"sqrt_price_x96": U256, "tick": i32}
slot0 = scval_map(simulate("get_slot0"))
current_tick = slot0["tick"].i32.int32

# Tick spacing: 20 (0.1% pool), 60 (0.3%) or 200 (1.0%)
tick_spacing = simulate("get_tick_spacing").i32.int32

# Human-readable price of token0 in token1 units
current_price = 1.0001 ** current_tick
print(f"tick={current_tick} spacing={tick_spacing} price={current_price:.7f}")

The price is expressed as token1 per token0 in raw (stroop) units, where token0/token1 are the pool tokens sorted by contract ID (get_tokens returns them in order). Since Stellar classic assets have 7 decimals on both sides, the raw ratio matches the display price.

Step 2. Convert your price range to ticks

Position boundaries are ticks: price(tick) = 1.0001^tick, and both boundaries must be multiples of the tick spacing. The helper below picks the nearest valid ticks for a target price range — here, ±5% around the current price.

Step 3. Estimate and open the position

estimate_deposit_position computes how much of each token the deposit will actually consume and how much liquidity you'll receive. Use the estimated liquidity to set a min_liquidity slippage guard, then call deposit_position.

The desired_amounts you pass are maximums: the contract pulls what the current price requires and refunds the rest in the same transaction. If the position range lies entirely above or below the current price, only one of the two tokens is needed — pass 0 for the other.

Step 4. List your positions and check if they are in range

There are no position IDs — a position is identified by your address plus the tick range. get_user_position_snapshot lists all your ranges in the pool; a position keeps earning swap fees and AQUA rewards only while tick_lower <= current_tick < tick_upper. Keep in mind that the reward weight is checkpoint-based — it updates only when you deposit, withdraw or claim, so perform an action after the price crosses your range boundary to re-sync it.

The per-position calls used below (get_position, get_position_fees, claim_position_fees) fail with PositionNotFound if you pass a range you have no position in (for example, one you have already fully withdrawn) — take the ranges from the snapshot rather than hardcoding them.

Step 5. Check and claim swap fees

Fees accrue per position in both pool tokens. get_position_fees (read-only) shows the pending amounts; claim_position_fees transfers them to you. get_all_position_fees / claim_all_position_fees do the same across all your positions at once.

Step 6. Claim AQUA rewards

If the pool has active AMM rewards, in-range positions accrue AQUA. Check with get_user_reward and collect with claim.

Step 7. Withdraw the position

Read the position's liquidity with get_position, estimate the payout, then call withdraw_position. The payout includes the position's entire accrued swap fee balance on top of the principal — even when you withdraw only part of the liquidity; withdrawing the full liquidity closes the position. Use min_amounts as slippage guards (they are checked against the total payout, principal + fees).

To rebalance an out-of-range position, withdraw it and open a new one around the current price (Steps 1–3).

Complete code examples

The scripts below implement the shared simulate/invoke helpers used in the steps above and run through the whole lifecycle. Provide the secret key of a funded account and the address of a concentrated pool.

Copy the full code Python
Copy the full code JavaScript

Last updated