> For the complete documentation index, see [llms.txt](https://docs.aqua.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aqua.network/developers/concentrated-liquidity/managing-positions-code-examples.md).

# 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 with the [official SDKs](/developers/integrating-with-aquarius.md) (0.3.0 or later). Amounts and liquidity always come from the contract's own estimators — the SDK converts prices to ticks with exact integer math and derives every slippage guard from a quote, never from client-side arithmetic.

{% hint style="info" %}
The [contract-level scripts](#contract-level-scripts-any-language) at the bottom cover other languages; per-function signatures live in the [contract interface reference](/developers/concentrated-liquidity/contract-interface-reference.md). The concentrated surface is beta: the contracts are under an external audit.
{% endhint %}

### Step 1. Set up and find a concentrated pool

{% tabs %}
{% tab title="Python" %}

```python
from stellar_sdk import Keypair
from aquarius import AquariusClient, Asset, XLM

AQUA = Asset.classic("AQUA", "GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA")

aqua = AquariusClient(network="mainnet", signer=Keypair.from_secret("S..."))

pool = next(p for p in aqua.pools_for_pair(XLM, AQUA) if p.type == "concentrated")

print(pool.slot0())          # Slot0(sqrt_price_x96=..., tick=...)
print(pool.tick_spacing())   # 20 (0.1% pool), 60 (0.3%) or 200 (1.0%)
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
import { Keypair } from "@stellar/stellar-sdk";
import { AquariusClient, Asset, XLM } from "@aquariusdefi/sdk";

const AQUA = Asset.classic("AQUA", "GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA");

const aqua = new AquariusClient({ network: "mainnet", signer: Keypair.fromSecret("S...") });

const pool = (await aqua.pools.forPair(XLM, AQUA)).find(p => p.type === "concentrated");

console.log(await pool.slot0());         // { sqrtPriceX96: ..., tick: ... }
console.log(await pool.tickSpacing());   // 20 (0.1% pool), 60 (0.3%) or 200 (1.0%)
```

{% endtab %}
{% endtabs %}

### Step 2. Convert your price range to ticks

Prices are token1 per token0 in base units. `tick_from_price` is integer-exact and identical across both SDKs; `snap_tick` aligns a tick to the pool's spacing. Passing `price_range` to the estimator in the next step performs both conversions for you:

{% tabs %}
{% tab title="Python" %}

```python
from aquarius import price_at_tick, snap_tick, tick_from_price

spacing = pool.tick_spacing()
tick = pool.current_tick()

# Range: 5% below to 5% above the current price
lower = snap_tick(tick_from_price(price_at_tick(tick) * 0.95), spacing)
upper = snap_tick(tick_from_price(price_at_tick(tick) * 1.05), spacing, up=True)
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
import { priceAtTick, snapTick, tickFromPrice } from "@aquariusdefi/sdk";

const spacing = await pool.tickSpacing();
const tick = await pool.currentTick();

// Range: 5% below to 5% above the current price
const lower = snapTick(tickFromPrice(priceAtTick(tick) * 0.95), spacing);
const upper = snapTick(tickFromPrice(priceAtTick(tick) * 1.05), spacing, { up: true });
```

{% endtab %}
{% endtabs %}

### Step 3. Estimate and open the position

Deposits are two-phase: `estimate_position_deposit` quotes through the contract's own estimator, then `execute` runs the deposit with a minimum-liquidity guard derived from that quote. The contract pulls the desired amounts and refunds whatever the range does not need:

{% tabs %}
{% tab title="Python" %}

```python
est = pool.estimate_position_deposit(
    {XLM: 10_0000000, AQUA: 5000_0000000},
    tick_range=(lower, upper),          # or price_range=(low, high)
)
print(f"will use: {est.amounts}, minting ~{est.liquidity} liquidity")

position = est.execute(slippage=0.01)   # min-liquidity guard from the estimate
print(f"opened with {position.liquidity} liquidity  {position.tx_hash}")
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const est = await pool.estimatePositionDeposit({
  amounts: [[XLM, 10_0000000n], [AQUA, 5000_0000000n]],
  tickRange: [lower, upper],            // or priceRange: [low, high]
});
console.log(`will use: ${est.amounts}, minting ~${est.liquidity} liquidity`);

const position = await est.execute({ slippage: 0.01 });   // min-liquidity guard from the estimate
console.log(`opened with ${position.liquidity} liquidity  ${position.txHash}`);
```

{% endtab %}
{% endtabs %}

A position is the key `(owner, tick_lower, tick_upper)` — no NFTs, merged on re-deposit, at most 20 ranges per account.

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

`position_range_status` matches the contract's active-liquidity rule exactly: the range is half-open `[tick_lower, tick_upper)`, so a position sitting at `tick_upper` is already out and earning nothing. "Below" means the price is under the range and the position sits entirely in the first sorted token; "above" — entirely in the second:

{% tabs %}
{% tab title="Python" %}

```python
for tick_lower, tick_upper in pool.position_ranges():
    status = pool.position_range_status(tick_lower, tick_upper)
    print(f"[{tick_lower}, {tick_upper}): {status.status}")   # in_range | below | above

value = pool.position_value(lower, upper)
print(f"principal: {value.principal}, fees: {value.fees}, total: {value.total}")
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
for (const [tickLower, tickUpper] of await pool.positionRanges()) {
  const status = await pool.positionRangeStatus(tickLower, tickUpper);
  console.log(`[${tickLower}, ${tickUpper}): ${status.status}`);   // in_range | below | above
}

const value = await pool.positionValue(lower, upper);
console.log(`principal: ${value.principal}, fees: ${value.fees}, total: ${value.total}`);
```

{% endtab %}
{% endtabs %}

### Step 5. Check and claim swap fees

Fees accrue per position while it is in range. Withdrawals auto-claim the position's fees, so explicit claims are for harvesting without touching the principal:

{% tabs %}
{% tab title="Python" %}

```python
print(pool.position_fees(lower, upper))     # accrued, unclaimed, in sorted-token order

claimed = pool.claim_position_fees(lower, upper)
print(f"claimed: {claimed.amounts}  {claimed.tx_hash}")

# Or across all of the signer's positions in this pool:
pool.claim_all_position_fees()
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
console.log(await pool.positionFees(lower, upper));   // accrued, unclaimed, in sorted-token order

const claimed = await pool.claimPositionFees(lower, upper);
console.log(`claimed: ${claimed.amounts}  ${claimed.txHash}`);

// Or across all of the signer's positions in this pool:
await pool.claimAllPositionFees();
```

{% endtab %}
{% endtabs %}

### Step 6. Claim AQUA rewards

Reward accrual and claims work the same as for every other pool type:

{% tabs %}
{% tab title="Python" %}

```python
print(pool.pending_rewards())        # accrued AQUA, stroops

claim = pool.claim_rewards()
print(f"claimed {claim.amount} AQUA  {claim.tx_hash}")
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
console.log(await pool.pendingRewards());   // accrued AQUA, stroops

const claim = await pool.claimRewards();
console.log(`claimed ${claim.amount} AQUA  ${claim.txHash}`);
```

{% endtab %}
{% endtabs %}

### Step 7. Withdraw the position

Partial or full — pass the liquidity to remove, or nothing for the whole position. Per-token minimums come from a simulation of this exact withdrawal, reduced by `slippage`; the payout includes the position's accrued fees:

{% tabs %}
{% tab title="Python" %}

```python
half = pool.withdraw_position(lower, upper, position.liquidity // 2)

closed = pool.withdraw_position(lower, upper)     # full close
print(f"received: {closed.amounts}  {closed.tx_hash}")
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const half = await pool.withdrawPosition({ tickLower: lower, tickUpper: upper, liquidity: position.liquidity / 2n });

const closed = await pool.withdrawPosition({ tickLower: lower, tickUpper: upper });   // full close
console.log(`received: ${closed.amounts}  ${closed.txHash}`);
```

{% endtab %}
{% endtabs %}

## Contract-level scripts (any language)

The scripts below run the same lifecycle against the pool contract directly, with the `simulate`/`invoke` helpers spelled out — the reference for languages without an official SDK. Provide the secret key of a funded account and the address of a concentrated pool.

<details>

<summary>Copy the full code Python</summary>

```python
# concentrated_position.py

import math
import time

from stellar_sdk import Address, Keypair, Network, scval, SorobanServer, TransactionBuilder, xdr
from stellar_sdk.soroban_rpc import GetTransactionStatus
from stellar_sdk.xdr import UInt128Parts

# ==========================
# Configuration Variables
# ==========================

SOROBAN_SERVER_RPC = "https://mainnet.sorobanrpc.com"
NETWORK_PASSPHRASE = Network.PUBLIC_NETWORK_PASSPHRASE

# User's secret key (ensure this is kept secure)
USER_SECRET_KEY = "S..."
# Address of the concentrated liquidity pool contract
POOL_ADDRESS = "C..."

server = SorobanServer(SOROBAN_SERVER_RPC)
keypair = Keypair.from_secret(USER_SECRET_KEY)


# ==========================
# Utility Functions
# ==========================

def u128_to_int(value: UInt128Parts) -> int:
   """Converts UInt128Parts from Stellar's XDR to a Python integer."""
   return (value.hi.uint64 << 64) + value.lo.uint64


def scval_map(scv: xdr.SCVal) -> dict:
   """Converts an ScVal map (contract struct) to a Python dict of ScVals."""
   return {entry.key.sym.sc_symbol.decode(): entry.val for entry in scv.map.sc_map}


def price_to_tick(price: float, tick_spacing: int) -> int:
   """Converts a price to the nearest valid (spacing-aligned) tick below it."""
   tick = math.floor(math.log(price) / math.log(1.0001))
   return tick - tick % tick_spacing


def build_tx(function_name: str, parameters: list):
   return (
       TransactionBuilder(
           source_account=server.load_account(keypair.public_key),
           network_passphrase=NETWORK_PASSPHRASE,
           base_fee=1000000,
       )
       .set_timeout(3600)
       .append_invoke_contract_function_op(
           contract_id=POOL_ADDRESS,
           function_name=function_name,
           parameters=parameters,
       )
       .build()
   )


def simulate(function_name: str, parameters: list = None) -> xdr.SCVal:
   """Read-only contract call executed through simulation. No signature needed."""
   simulation = server.simulate_transaction(build_tx(function_name, parameters or []))
   if simulation.error:
       raise RuntimeError(f"{function_name} simulation failed: {simulation.error}")
   return xdr.SCVal.from_xdr(simulation.results[0].xdr)


def invoke(function_name: str, parameters: list) -> xdr.SCVal:
   """State-changing contract call: prepare, sign, send and wait for the result."""
   prepared_tx = server.prepare_transaction(build_tx(function_name, parameters))
   prepared_tx.sign(keypair)
   send_response = server.send_transaction(prepared_tx)

   # Poll until the transaction is included in a ledger
   while True:
       tx_response = server.get_transaction(send_response.hash)
       if tx_response.status != GetTransactionStatus.NOT_FOUND:
           break
       time.sleep(2)

   if tx_response.status != GetTransactionStatus.SUCCESS:
       raise RuntimeError(f"{function_name} failed: {tx_response.result_xdr}")

   transaction_meta = xdr.TransactionMeta.from_xdr(tx_response.result_meta_xdr)
   return (transaction_meta.v4 if transaction_meta.v == 4 else transaction_meta.v3).soroban_meta.return_value


# ==========================
# Position Lifecycle
# ==========================

def run():
   # Step 1. Read the pool state
   slot0 = scval_map(simulate("get_slot0"))
   current_tick = slot0["tick"].i32.int32
   tick_spacing = simulate("get_tick_spacing").i32.int32
   current_price = 1.0001 ** current_tick
   print(f"tick={current_tick} spacing={tick_spacing} price={current_price:.7f}")

   # Step 2. Convert the target price range (±5%) to ticks
   tick_lower = price_to_tick(current_price * 0.95, tick_spacing)
   tick_upper = price_to_tick(current_price * 1.05, tick_spacing) + tick_spacing
   print(f"range: [{tick_lower}, {tick_upper}]")

   # Step 3. Estimate and open the position
   desired_amounts = [100_0000000, 100_0000000]  # raw amounts, sorted token order
   est = simulate("estimate_deposit_position", [
       scval.to_int32(tick_lower),
       scval.to_int32(tick_upper),
       scval.to_vec([scval.to_uint128(a) for a in desired_amounts]),
   ])
   est_liquidity = u128_to_int(est.vec.sc_vec[1].u128)

   result = invoke("deposit_position", [
       scval.to_address(keypair.public_key),
       scval.to_int32(tick_lower),
       scval.to_int32(tick_upper),
       scval.to_vec([scval.to_uint128(a) for a in desired_amounts]),
       scval.to_uint128(est_liquidity * 99 // 100),  # 1% slippage guard
   ])
   deposited = [u128_to_int(v.u128) for v in result.vec.sc_vec[0].vec.sc_vec]
   liquidity = u128_to_int(result.vec.sc_vec[1].u128)
   print(f"deposited {deposited}, received liquidity {liquidity}")

   # Step 4. List positions and check range status
   snapshot = scval_map(simulate("get_user_position_snapshot",
                                 [scval.to_address(keypair.public_key)]))
   for entry in snapshot["ranges"].vec.sc_vec:
       rng = scval_map(entry)
       lower = rng["tick_lower"].i32.int32
       upper = rng["tick_upper"].i32.int32
       status = "IN RANGE" if lower <= current_tick < upper else "OUT OF RANGE"
       print(f"position [{lower}, {upper}]: {status}")

   # Step 5. Check pending swap fees (claim with "claim_position_fees" when non-zero)
   fees = simulate("get_position_fees", [
       scval.to_address(keypair.public_key),
       scval.to_int32(tick_lower),
       scval.to_int32(tick_upper),
   ])
   print("pending fees:", [u128_to_int(v.u128) for v in fees.vec.sc_vec])

   # Step 6. Check pending AQUA rewards (claim with "claim" when non-zero)
   reward = u128_to_int(
       simulate("get_user_reward", [scval.to_address(keypair.public_key)]).u128
   )
   print(f"pending AQUA reward: {reward / 1e7}")

   # Step 7. Withdraw the whole position (principal + accrued fees)
   est = simulate("estimate_withdraw_position", [
       scval.to_address(keypair.public_key),
       scval.to_int32(tick_lower),
       scval.to_int32(tick_upper),
       scval.to_uint128(liquidity),
   ])
   min_amounts = [u128_to_int(v.u128) * 99 // 100 for v in est.vec.sc_vec]

   withdrawn = invoke("withdraw_position", [
       scval.to_address(keypair.public_key),
       scval.to_int32(tick_lower),
       scval.to_int32(tick_upper),
       scval.to_uint128(liquidity),
       scval.to_vec([scval.to_uint128(a) for a in min_amounts]),
   ])
   print("withdrawn:", [u128_to_int(v.u128) for v in withdrawn.vec.sc_vec])


if __name__ == "__main__":
   run()
```

</details>

<details>

<summary>Copy the full code JavaScript</summary>

```javascript
// concentrated_position.js
const StellarSdk = require('@stellar/stellar-sdk');
const {
    Address,
    Contract,
    Keypair,
    Networks,
    TransactionBuilder,
    TimeoutInfinite,
    BASE_FEE,
    nativeToScVal,
    scValToNative,
    rpc,
    xdr,
} = StellarSdk;

// ==========================
// Configuration Variables
// ==========================

const sorobanServerUrl = 'https://mainnet.sorobanrpc.com';

// User's secret key (ensure this is kept secure)
const userSecretKey = 'S...';
// Address of the concentrated liquidity pool contract
const poolAddress = 'C...';

const server = new rpc.Server(sorobanServerUrl);
const keypair = Keypair.fromSecret(userSecretKey);

// ==========================
// Utility Functions
// ==========================

function priceToTick(price, tickSpacing) {
    /** Converts a price to the nearest valid (spacing-aligned) tick below it. */
    const tick = Math.floor(Math.log(price) / Math.log(1.0001));
    return tick - ((tick % tickSpacing) + tickSpacing) % tickSpacing;
}

async function buildTx(functionName, parameters) {
    const account = await server.getAccount(keypair.publicKey());
    const contract = new Contract(poolAddress);
    return new TransactionBuilder(account, {
        fee: BASE_FEE,
        networkPassphrase: Networks.PUBLIC,
    })
        .addOperation(contract.call(functionName, ...parameters))
        .setTimeout(TimeoutInfinite)
        .build();
}

async function simulate(functionName, parameters = []) {
    /** Read-only contract call executed through simulation. No signature needed. */
    const tx = await buildTx(functionName, parameters);
    const result = await server.simulateTransaction(tx);
    if (!result.result) {
        throw new Error(`${functionName} simulation failed`);
    }
    return scValToNative(result.result.retval);
}

async function invoke(functionName, parameters) {
    /** State-changing contract call: prepare, sign, send and wait for the result. */
    const tx = await buildTx(functionName, parameters);
    const preparedTx = await server.prepareTransaction(tx);
    preparedTx.sign(keypair);

    const sendResponse = await server.sendTransaction(preparedTx);

    // Poll until the transaction is included in a ledger
    let txResponse = await server.getTransaction(sendResponse.hash);
    while (txResponse.status === 'NOT_FOUND') {
        await new Promise(resolve => setTimeout(resolve, 2000));
        txResponse = await server.getTransaction(sendResponse.hash);
    }

    if (txResponse.status !== 'SUCCESS') {
        throw new Error(`${functionName} failed: ${txResponse.status}`);
    }
    return scValToNative(txResponse.returnValue);
}

// ==========================
// Position Lifecycle
// ==========================

async function run() {
    // Step 1. Read the pool state
    const slot0 = await simulate('get_slot0');
    const currentTick = slot0.tick;
    const tickSpacing = await simulate('get_tick_spacing');
    const currentPrice = Math.pow(1.0001, currentTick);
    console.log(`tick=${currentTick} spacing=${tickSpacing} price=${currentPrice}`);

    // Step 2. Convert the target price range (±5%) to ticks
    const tickLower = priceToTick(currentPrice * 0.95, tickSpacing);
    const tickUpper = priceToTick(currentPrice * 1.05, tickSpacing) + tickSpacing;
    console.log(`range: [${tickLower}, ${tickUpper}]`);

    const user = new Address(keypair.publicKey()).toScVal();
    const i32 = value => nativeToScVal(value, { type: 'i32' });
    const u128 = value => nativeToScVal(value, { type: 'u128' });
    const u128vec = values => xdr.ScVal.scvVec(values.map(u128));

    // Step 3. Estimate and open the position
    const desiredAmounts = [1000000000n, 1000000000n]; // raw amounts, sorted token order
    const [, estLiquidity] = await simulate('estimate_deposit_position',
        [i32(tickLower), i32(tickUpper), u128vec(desiredAmounts)]);

    const [deposited, liquidity] = await invoke('deposit_position', [
        user,
        i32(tickLower),
        i32(tickUpper),
        u128vec(desiredAmounts),
        u128(estLiquidity * 99n / 100n), // 1% slippage guard
    ]);
    console.log(`deposited ${deposited}, received liquidity ${liquidity}`);

    // Step 4. List positions and check range status
    const snapshot = await simulate('get_user_position_snapshot', [user]);
    for (const { tick_lower, tick_upper } of snapshot.ranges) {
        const inRange = tick_lower <= currentTick && currentTick < tick_upper;
        console.log(`position [${tick_lower}, ${tick_upper}]: ${inRange ? 'IN RANGE' : 'OUT OF RANGE'}`);
    }

    // Step 5. Check pending swap fees (claim with "claim_position_fees" when non-zero)
    const pendingFees = await simulate('get_position_fees',
        [user, i32(tickLower), i32(tickUpper)]);
    console.log(`pending fees: ${pendingFees}`);

    // Step 6. Check pending AQUA rewards (claim with "claim" when non-zero)
    const reward = await simulate('get_user_reward', [user]);
    console.log(`pending AQUA reward: ${Number(reward) / 1e7}`);

    // Step 7. Withdraw the whole position (principal + accrued fees)
    const estAmounts = await simulate('estimate_withdraw_position',
        [user, i32(tickLower), i32(tickUpper), u128(liquidity)]);
    const minAmounts = estAmounts.map(amount => amount * 99n / 100n);

    const withdrawn = await invoke('withdraw_position',
        [user, i32(tickLower), i32(tickUpper), u128(liquidity), u128vec(minAmounts)]);
    console.log(`withdrawn: ${withdrawn}`);
}

run();
```

</details>
