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/B
2: Reward overflow
3: Points overflow
5: Invalid tick range
6: Position not found
7: Cannot close non-empty position
8: Liquidity addition overflow
9: Insufficient liquidity to remove
10: Invalid reward index
11: Position is locked
Gas Optimization
Use fetch_positions for batch operations
Combine 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.
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
}
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
}
struct PositionReward has copy, drop, store {
growth_inside: u128, // Growth rate inside position range
amount_owned: u64, // Accumulated reward amount
}
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
);
// Create new position
let position = open_position<TokenA, TokenB>(
&mut manager, pool_id, pool_index, icon_url,
lower_tick, upper_tick, lock_until, ctx
);
// Each position can have multiple rewards
struct PositionReward {
growth_inside: u128, // Growth rate inside position's range
amount_owned: u64, // Accumulated reward amount
}
// Lock position until timestamp
lock_position(&mut position, lock_until_timestamp);
// Check lock status
let lock_time = get_lock_until(&position);
let current_time = clock::timestamp_ms(clock);
assert!(position.lock_until <= current_time, 11);
// 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);
}
// 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;
}