Contract interface reference
Full reference of the concentrated liquidity pool smart contract interface: position management, state getters, fees, swaps and rewards.
This page is the canonical public reference for the concentrated liquidity pool contract interface. Functions are grouped by where they live: the router (shared Aquarius entry point) and the pool contract (one per concentrated pool). Signatures are given in Rust/Soroban notation; the e: Env argument is implicit in every Soroban contract call and is not passed by the client.
Read-only getters and estimate_* functions don't change state — call them through transaction simulation, no signature or fees required. Estimates enforce the same validation as the real calls: on invalid input the simulation fails with the corresponding error code (for example, PositionNotFound, AllCoinsRequired, OutMinNotSatisfied when amounts round to zero liquidity) instead of returning a value — handle simulation errors accordingly.
Router functions
The router address is listed in Prerequisites & basics. Token vectors must always be sorted by contract ID.
init_concentrated_pool
Creates a concentrated pool for a token pair, or returns the existing one for that fee tier.
fn init_concentrated_pool(
e: Env,
user: Address,
tokens: Vec<Address>,
fee: u32,
) -> (BytesN<32>, Address);user— the account creating the pool (pays the pool creation fee in AQUA if one is configured).tokens— the two token addresses, sorted.fee— one of10(0.1%),30(0.3%),100(1.0%). Tick spacing is derived automatically:20,60or200respectively.
Returns the pool index hash and the pool contract address.
Pool discovery
get_pools returns all pools for a pair (all types). Concentrated pools report pool_type: "concentrated" in get_info, along with fee and tick_spacing. See the Get pools info example.
Router-level deposit, withdraw, swaps, rewards
The generic router functions documented in Router & pool contracts — deposit, withdraw, swap_chained, claim — all work with concentrated pools. A router deposit into a concentrated pool opens a full-range position (see position model). For ranged positions, call the pool contract directly.
Pool contract: position management
deposit_position
Opens a new position or adds liquidity to an existing one with the same range.
sender— position owner; authorizes the token transfers.tick_lower,tick_upper— range boundaries; must be multiples of the pool's tick spacing,tick_lower < tick_upper, within±887272.desired_amounts—[amount0, amount1]in raw units (token order = sorted order). These are maximums you authorize, not exact spends: the contract computes the maximum liquidity purchasable at the current price, pulls what it needs and refunds the excess within the same transaction.min_liquidity— slippage guard on liquidity units (not token amounts): the transaction fails withOutMinNotSatisfiedif the minted liquidity is below this value.
Returns (actual_amounts, minted_liquidity) — the amounts actually spent and the liquidity minted.
Special cases:
If the current price is outside the range, only one token is required — pass
0for the other. Which one depends on the side: a range entirely above the current price takes onlytoken0, entirely below — onlytoken1. Passing only the wrong-side token yields zero liquidity and fails withOutMinNotSatisfied.The first deposit into an empty pool initializes the pool price from
amount1 / amount0. It must include both tokens (AllCoinsRequiredotherwise), and the tick derived from that ratio must fall inside the deposit range (TickOutOfBoundsotherwise).
estimate_deposit_position
Simulation counterpart of deposit_position — same math, no transfers.
withdraw_position
Burns liquidity from a position and transfers principal plus all accrued swap fees to the owner. This applies to partial withdrawals too: any withdraw_position call, regardless of amount, always sweeps the position's entire accrued fee balance along with the withdrawn principal — there is no way to withdraw principal while leaving fees unclaimed. Withdrawing the full liquidity closes the position.
amount— liquidity to burn (up to the position's total; seeget_position).min_amounts—[min0, min1]slippage guards checked against the total payout (principal + fees).
Returns the paid-out token amounts. Withdrawals are always available — this function has no kill-switch.
estimate_withdraw_position
Like withdraw_position, the returned amounts include the position's accrued fees on top of the principal.
Pool contract: swap fees
All return [amount0, amount1]. Claiming emits a claim_fees event with the owner and token addresses as topics.
Pool contract: state getters
Relevant types:
tokens_owed_* in get_position only reflects fees settled during past interactions with the position. For the live pending total, use get_position_fees.
Missing-position behavior differs between getters. get_position, get_position_fees and claim_position_fees fail with PositionNotFound when called for a range the owner has no position in (including one you have already fully withdrawn). The aggregate views — get_user_position_snapshot and get_all_position_fees — safely return empty/zero results for a user with no positions.
Enumerating initialized ticks
To reconstruct the pool's liquidity distribution (for example, for a chart), you need every initialized tick — a tick referenced by at least one position boundary (liquidity_gross > 0 in TickInfo). Two strategies:
Simple: call
get_tick_boundsand scanget_ticks_batchover multiples of the tick spacing between the bounds. Fine for pools with a modest active range.Sparse (bitmap): ticks are indexed in a two-level bitmap. Ticks are grouped into chunks of 16:
chunk_pos = floor((tick / tick_spacing) / 16). Each bit of a 256-bit word flags a chunk containing at least one initialized tick:word_pos = chunk_pos >> 8, bit indexchunk_pos & 255(bit 0 = least significant). Scan words withget_chunk_bitmap_batch, then read only the flagged chunks' ticks viaget_ticks_batch.
Given active liquidity at the current tick (get_active_liquidity) and the liquidity_net deltas of initialized ticks, liquidity at any tick is reconstructed by walking outward from the current tick and applying the deltas: add liquidity_net when crossing a tick left-to-right, subtract when right-to-left.
Pool contract: swaps
Concentrated pools implement the standard Aquarius pool swap interface, so direct-pool swaps work exactly like the Executing swaps through specific pool example:
in_idx/out_idx are the token indices in the sorted pair (0 or 1).
Partial-fill behavior differs between the two swap modes. An exact-input swap that runs out of initialized liquidity before consuming the whole input does not refund the unswapped remainder: the remainder is treated like a swap fee — the protocol share is diverted to the treasury and the rest is distributed to the liquidity positions that covered the last active tick of the swap path. This is intentional: a refund leg could itself fail mid-transaction (for example, a missing trustline on the refund path of a chained swap), so the contract guarantees a simple invariant instead — exactly in_amount is consumed, and at least out_min is received, or the transaction fails. Always set a meaningful out_min and check estimate_swap first. Edge cases: a swap that cannot move any liquidity at all reverts with InsufficientLiquidity rather than consuming the input, and an exact-output swap_strict_receive that cannot produce out_amount fails with InsufficientLiquidity as well.
Multi-hop routes (swap_chained) through concentrated pools are also heavier computationally than through other pool types and can hit Soroban per-transaction limits sooner.
Pool contract: AQUA rewards
Remember: positions accrue rewards only while in range — tick_lower <= current_tick < tick_upper — and the in-range check is checkpoint-based: the reward weight is snapshotted when the owner interacts with the pool (deposit, withdraw, claim) and is not re-evaluated between interactions as the price moves. A position that left its range keeps accruing at the stale weight until the owner's next action, and one that re-entered its range earns nothing until then — perform an action (for example, claim) after the price crosses a range boundary to re-sync. Note: this checkpoint behavior is specific to the current contract version and may change in future versions.
The same operations are available through the router with (tokens, pool_index) arguments. Calling claim with nothing accrued succeeds and pays zero — it is a no-op, not an error. Reward claiming can be temporarily paused by protocol admins in emergencies; principal withdrawal can not.
Events
Events emitted by the pool contract, for indexers and monitoring (topics listed first, then the data payload):
deposit_liquidity
token0, token1
liquidity, amount0, amount1
withdraw_liquidity
token0, token1
liquidity, amount0, amount1
position_update
user
tick_lower, tick_upper, liquidity_delta
pool_state
—
sqrt_price_x96, tick, active_liquidity
claim_fees
owner, token0, token1
amount0, amount1
claim_reward
reward_token, user
amount
trade
token_in, token_out, trader
sold_amount, bought_amount, fee
update_reserves
—
reserve0, reserve1
Correlation notes for indexers: a swap emits trade, update_reserves and pool_state together; withdraw_position additionally emits claim_fees when it auto-sweeps the position's accrued fees.
Common errors
201
PoolAlreadyInitialized
Pool init attempted twice
205
DepositKilled
Deposits are paused by the protocol
2002
TokensNotSorted
Token vector not sorted by contract ID
2101
InvalidTickRange
Malformed tick range
2107
TickOutOfBounds
Tick outside allowed bounds (for example, first-deposit price outside the deposit range)
2109
TickNotSpacedCorrectly
Boundary is not a multiple of the tick spacing
2110
TickLowerNotLessThanUpper
tick_lower >= tick_upper
2111
TickLowerTooLow
Below MIN_TICK
2112
TickUpperTooHigh
Above MAX_TICK
2118
PositionNotFound
No position for (owner, range)
2119
TooManyPositions
More than 20 positions per user per pool
2121
InsufficientLiquidity
Withdraw amount exceeds position liquidity; or the pool cannot fill a swap (exact-output cannot produce out_amount, exact-input cannot move at all)
Generic validation errors (zero amounts, slippage guards like OutMinNotSatisfied) are shared with other Aquarius pool types.
Last updated