Position Module
Manages liquidity position NFTs and their associated metadata.
Data Structures
Position
The main NFT struct representing position ownership:
struct Position has key, store {
id: UID, // Unique identifier
pool: ID, // Associated pool ID
index: u64, // Position index number
coin_type_a: TypeName, // First token type
coin_type_b: TypeName, // Second token type
name: String, // Display name
description: String, // Description
url: String, // Icon URL
tick_lower_index: I32, // Lower tick bound
tick_upper_index: I32, // Upper tick bound
liquidity: u128, // Current liquidity amount
lock_until: u64, // Lock expiration timestamp
}
PositionInfo
The computational data for position calculations:
struct PositionInfo has copy, drop, store {
position_id: ID, // Corresponding Position NFT ID
liquidity: u128, // Active liquidity
tick_lower_index: I32, // Lower tick
tick_upper_index: I32, // Upper tick
fee_growth_inside_a: u128, // Fee growth for token A
fee_growth_inside_b: u128, // Fee growth for token B
fee_owned_a: u64, // Accumulated fees for token A
fee_owned_b: u64, // Accumulated fees for token B
points_owned: u128, // Accumulated points
points_growth_inside: u128, // Points growth tracker
rewards: vector<PositionReward>, // Multi-token rewards
}
PositionManager
Central registry managing all positions:
struct PositionManager has store {
tick_spacing: u32, // Tick spacing for the pool
position_index: u64, // Next position index
positions: LinkedTable<ID, PositionInfo>, // Position data storage
}
PositionReward
Individual reward token tracking:
struct PositionReward has copy, drop, store {
growth_inside: u128, // Growth rate inside position range
amount_owned: u64, // Accumulated reward amount
}
Core Functions
Position Creation
open_position<CoinTypeA, CoinTypeB>
Creates a new liquidity position NFT.
Parameters:
manager
: Mutable reference to PositionManagerpool_id
: ID of the target poolpool_index
: Pool's index numbericon_url
: URL for position icontick_lower_index
: Lower bound of liquidity rangetick_upper_index
: Upper bound of liquidity rangelock_until
: Timestamp until position is lockedctx
: Transaction context
Returns: New Position NFT
Example:
let position = position::open_position<USDC, ETH>(
&mut manager,
pool_id,
1,
string::utf8(b"https://example.com/icon.png"),
i32::from(-1000), // Lower tick
i32::from(1000), // Upper tick
0, // No lock
ctx
);
Liquidity Management
increase_liquidity
Adds liquidity to an existing position.
Parameters:
manager
: Mutable reference to PositionManagerposition
: Mutable reference to Position NFTdelta_liquidity
: Amount of liquidity to addfee_growth_inside_a
: Current fee growth for token Afee_growth_inside_b
: Current fee growth for token Bpoints_growth_inside
: Current points growthrewards_growth_inside
: Vector of current reward growth rates
Returns: New total liquidity amount
decrease_liquidity
Removes liquidity from a position.
Parameters: Similar to increase_liquidity
but removes instead of adds Additional Checks:
Validates position is not locked
Ensures sufficient liquidity exists
Fee Management
update_fee
Updates accumulated fees for a position.
public(friend) fun update_fee(
manager: &mut PositionManager,
position_id: ID,
fee_growth_a: u128,
fee_growth_b: u128,
): (u64, u64)
reset_fee
Collects accumulated fees and resets counters.
public(friend) fun reset_fee(
manager: &mut PositionManager,
position_id: ID,
): (u64, u64)
Position Lifecycle
1. Creation
// Create new position
let position = open_position<TokenA, TokenB>(
&mut manager, pool_id, pool_index, icon_url,
lower_tick, upper_tick, lock_until, ctx
);
2. Liquidity Addition
// Add liquidity
let new_liquidity = increase_liquidity(
&mut manager, &mut position, liquidity_amount,
fee_growth_a, fee_growth_b, points_growth, rewards_growth
);
3. Fee Collection
// Collect fees
let (fee_a, fee_b) = update_and_reset_fee(
&mut manager, position_id, fee_growth_a, fee_growth_b
);
4. Liquidity Removal
// Remove liquidity
let remaining_liquidity = decrease_liquidity(
&mut manager, &mut position, liquidity_to_remove,
fee_growth_a, fee_growth_b, points_growth, rewards_growth, clock
);
5. Position Closure
// Close empty position
if (is_empty(&position_info)) {
close_position(&mut manager, position);
}
Fee and Reward Management
Fee Calculation
Fees are calculated using the formula:
delta_fee = liquidity × (current_growth - last_growth) / 2^64
The system tracks fee growth both globally and per position to calculate accumulated fees accurately.
Multi-token Rewards
The rewards system supports multiple reward tokens simultaneously:
// Each position can have multiple rewards
struct PositionReward {
growth_inside: u128, // Growth rate inside position's range
amount_owned: u64, // Accumulated reward amount
}
Reward Distribution
Rewards are distributed proportionally based on:
Position liquidity amount
Time in range
Growth rates specific to the position's tick range
Position Locking
Lock Mechanism
Positions can be locked until a specific timestamp to prevent premature liquidity removal:
// Lock position until timestamp
lock_position(&mut position, lock_until_timestamp);
// Check lock status
let lock_time = get_lock_until(&position);
Lock Validation
When decreasing liquidity, the system validates:
let current_time = clock::timestamp_ms(clock);
assert!(position.lock_until <= current_time, 11);
API Reference
Query Functions
Position Information
liquidity(position: &Position): u128
- Get position liquiditytick_range(position: &Position): (I32, I32)
- Get tick rangepool_id(position: &Position): ID
- Get associated pool IDindex(position: &Position): u64
- Get position index
Position Info Queries
info_liquidity(info: &PositionInfo): u128
- Get liquidity from infoinfo_fee_owned(info: &PositionInfo): (u64, u64)
- Get accumulated feesinfo_points_owned(info: &PositionInfo): u128
- Get accumulated pointsinfo_rewards(info: &PositionInfo): &vector<PositionReward>
- Get rewards
Manager Queries
is_position_exist(manager: &PositionManager, id: ID): bool
- Check if position existsfetch_positions(manager: &PositionManager, start: vector<ID>, limit: u64): vector<PositionInfo>
- Get multiple positions
Validation Functions
check_position_tick_range
Validates tick range parameters:
tick_lower < tick_upper
Ticks within valid bounds
Ticks align with tick spacing
is_empty
Checks if position can be closed:
Zero liquidity
No accumulated fees
No pending rewards
Examples
Complete Position Lifecycle Example
// 1. Create position manager
let manager = position::new(10, ctx); // 10 tick spacing
// 2. Open new position
let position = position::open_position<USDC, ETH>(
&mut manager,
pool_id,
1,
string::utf8(b"https://example.com/icon.png"),
i32::from(-100), // Lower tick
i32::from(100), // Upper tick
0, // No lock
ctx
);
// 3. Add liquidity
let liquidity_added = position::increase_liquidity(
&mut manager,
&mut position,
1000000, // 1M liquidity units
fee_growth_a,
fee_growth_b,
points_growth,
rewards_growth
);
// 4. Later: collect fees
let (fee_a, fee_b) = position::update_and_reset_fee(
&mut manager,
object::id(&position),
new_fee_growth_a,
new_fee_growth_b
);
// 5. Remove some liquidity
let remaining = position::decrease_liquidity(
&mut manager,
&mut position,
500000, // Remove 500K liquidity
fee_growth_a,
fee_growth_b,
points_growth,
rewards_growth,
clock
);
// 6. Check if position can be closed
let position_info = position::borrow_position_info(
&manager,
object::id(&position)
);
if (position::is_empty(position_info)) {
position::close_position(&mut manager, position);
}
Batch Position Query Example
// Query multiple positions
let positions = position::fetch_positions(
&manager,
vector::empty<ID>(), // Start from beginning
10 // Limit to 10 positions
);
// Process each position
let i = 0;
while (i < vector::length(&positions)) {
let pos_info = vector::borrow(&positions, i);
let liquidity = position::info_liquidity(pos_info);
let (fee_a, fee_b) = position::info_fee_owned(pos_info);
// Process position data...
i = i + 1;
}
Best Practices
Position Management
Always validate tick ranges before creating positions
Check lock status before attempting to decrease liquidity
Update fees and rewards before liquidity changes
Use batch queries for efficient data retrieval
Error Handling
The module uses various assertion codes:
1
: Fee overflow in token A/B2
: Reward overflow3
: Points overflow5
: Invalid tick range6
: Position not found7
: Cannot close non-empty position8
: Liquidity addition overflow9
: Insufficient liquidity to remove10
: Invalid reward index11
: Position is locked
Gas Optimization
Use
fetch_positions
for batch operationsCombine fee updates with liquidity changes
Close empty positions to free storage
Security Considerations
Position Ownership: Only the NFT holder can modify the position
Tick Validation: All tick ranges are validated for correctness
Overflow Protection: All arithmetic operations include overflow checks
Lock Enforcement: Locked positions cannot have liquidity decreased
Access Control: Critical functions are
friend
-only
This documentation provides a comprehensive guide to the Ferra CLMM Position Management system. For additional technical details, refer to the source code and inline comments.
Last updated