FlowX CLMM Guideline
A concentrated liquidity market maker (CLMM) protocol built on the Sui blockchain, inspired by Uniswap v3. FlowX CLMM allows liquidity providers to concentrate their capital within custom price ranges
๐ Overview
FlowX CLMM implements a concentrated liquidity model with the following core features:
๐ฏ Key Features
Concentrated Liquidity
Capital efficiency through custom price ranges
Multiple Fee Tiers
Flexible fee structures (0.01%, 0.05%, 0.3%, 1%)
Position Management
NFT-based position tracking and management
Protocol Rewards
Built-in reward distribution system
Oracle Integration
Price feeds and historical data tracking
Modular Architecture
Clean separation with versioning support
๐ฐ Benefits
For Liquidity Providers: Higher capital efficiency and customizable risk exposure
For Traders: Lower slippage and better price discovery
For Developers: Modular design for easy integration and extension
๐๏ธ Architecture
Core Modules
๐ฆ Pool Manager (pool_manager.move
)
Central registry for all pools in the protocol.
Pool creation and registration
Fee tier management
Administrative functions
Protocol fee collection
๐ Pool (pool.move
)
Individual pool implementation containing the core AMM logic.
Liquidity management
Swap execution
Tick state management
Oracle data collection
Reward distribution
๐ Position Manager (position_manager.move
)
Manages the lifecycle of liquidity positions.
Position creation and closure
Liquidity adjustments
Fee collection
Reward claiming
๐ Swap Router (swap_router.move
)
Handles swap execution with various input/output specifications.
Exact input/output swaps
Price limit enforcement
Slippage protection
๐ง Core Functions
๐ Pool Management
๐ Position Management
๐ฐ Fee & Reward Collection
๐ Swap Functions
๐ API Reference
This section provides detailed parameter descriptions for all core functions.
Pool Management Functions
create_pool_v2<X, Y>
Creates a new liquidity pool for token pair X/Y
Public
create_and_initialize_pool_v2<X, Y>
Creates and initializes pool with initial price in one transaction
Public
create_pool_v2<X, Y>
Purpose: Creates a new liquidity pool for token pair X/Y
Fee Rate: Must be previously enabled (e.g., 3000 for 0.3%)
Access: Public - can be called by any user
create_and_initialize_pool_v2<X, Y>
Purpose: Creates and initializes pool with initial price in one transaction
Initial Price: Specified as Q64.64 fixed-point sqrt price
Access: Public - can be called by any user
Position Management Functions
open_position<X, Y>
Opens new liquidity position within specified tick range
Position NFT
close_position
Closes empty position and destroys NFT
None
increase_liquidity<X, Y>
Adds liquidity to existing position
Auto-refunds excess
decrease_liquidity<X, Y>
Removes liquidity from position
Token balances
collect<X, Y>
Collects accumulated trading fees from position
Fee coins
collect_pool_reward<X, Y, RewardToken>
Collects reward tokens earned by position
Reward coins
open_position<X, Y>
Purpose: Opens new liquidity position within specified tick range
Tick Range: Must respect pool's tick spacing requirements
Returns: Position NFT for tracking and management
increase_liquidity<X, Y>
Purpose: Adds liquidity to existing position
Slippage Protection:
amount_x_min
andamount_y_min
parametersAuto-refund: Excess tokens automatically returned
Deadline: Prevents execution of stale transactions
decrease_liquidity<X, Y>
Purpose: Removes liquidity from position
Returns: Token balances proportional to liquidity removed
Minimum Output: Slippage protection via
amount_x_min
/amount_y_min
collect<X, Y>
Purpose: Collects accumulated trading fees from position
Fee Collection: Specify maximum amounts to collect
Returns: Collected fee balances for both tokens
collect_pool_reward<X, Y, RewardToken>
Purpose: Collects reward tokens earned by position
Reward Type: Specify exact reward token type
Returns: Coin of specified reward token type
close_position
Purpose: Closes empty position and destroys NFT
Requirements: Position must have zero liquidity, fees, and rewards
Swap Functions
Exact Input
swap_exact_x_to_y
, swap_exact_y_to_x
Swap all tokens for maximum output
Exact Output
swap_x_to_exact_y
, swap_y_to_exact_x
Use minimum input for precise output
High-Level Router
swap_exact_input
, swap_exact_output
Simplified interface with auto pool selection
Exact Input Swaps
swap_exact_x_to_y: Swap all X tokens for maximum Y tokens
swap_exact_y_to_x: Swap all Y tokens for maximum X tokens
Use Case: When you want to sell entire token balance
Exact Output Swaps
swap_x_to_exact_y: Use minimum X tokens to get exact Y amount
swap_y_to_exact_x: Use minimum Y tokens to get exact X amount
Use Case: When you need precise output amount
High-Level Router Functions
swap_exact_input: Router function for exact input swaps with deadline validation
swap_exact_output: Router function for exact output swaps with deadline validation
Use Case: Simplified interface for common swap operations with automatic pool selection
Swap Functions
swap_exact_x_to_y<X, Y>(pool, coin_in, sqrt_price_limit, versioned, clock, ctx)
Swaps an exact amount of token X for token Y.
public fun swap_exact_x_to_y<X, Y>(
pool: &mut Pool<X, Y>,
coin_in: Coin<X>,
sqrt_price_limit: u128,
versioned: &Versioned,
clock: &Clock,
ctx: &TxContext
): Balance<Y>
Parameters:
pool
: Mutable reference to the pool to execute swap incoin_in
: X tokens to swap (entire amount will be consumed)sqrt_price_limit
: Maximum acceptable sqrt price after swap (slippage protection)versioned
: Reference to versioned object for package version validationclock
: Clock object for timing validationctx
: Transaction context
Returns:
Balance<Y>
: Resulting Y token balance from the swap
swap_exact_y_to_x<X, Y>(pool, coin_in, sqrt_price_limit, versioned, clock, ctx)
Swaps an exact amount of token Y for token X.
Parameters:
pool
: Mutable reference to the pool to execute swap incoin_in
: Y tokens to swap (entire amount will be consumed)sqrt_price_limit
: Minimum acceptable sqrt price after swap (slippage protection)versioned
: Reference to versioned object for package version validationclock
: Clock object for timing validationctx
: Transaction context
Returns:
Balance<X>
: Resulting X token balance from the swap
swap_x_to_exact_y<X, Y>(pool, coin_in, amount_out, sqrt_price_limit, versioned, clock, ctx)
Swaps token X for an exact amount of token Y.
Parameters:
pool
: Mutable reference to the pool to execute swap incoin_in
: X tokens to swap (excess will be refunded)amount_out
: Exact amount of Y tokens to receivesqrt_price_limit
: Maximum acceptable sqrt price after swapversioned
: Reference to versioned object for package version validationclock
: Clock object for timing validationctx
: Transaction context
Returns:
Balance<Y>
: Exact amount of Y tokens requested
swap_y_to_exact_x<X, Y>(pool, coin_in, amount_out, sqrt_price_limit, versioned, clock, ctx)
Swaps token Y for an exact amount of token X.
Parameters:
pool
: Mutable reference to the pool to execute swap incoin_in
: Y tokens to swap (excess will be refunded)amount_out
: Exact amount of X tokens to receivesqrt_price_limit
: Minimum acceptable sqrt price after swapversioned
: Reference to versioned object for package version validationclock
: Clock object for timing validationctx
: Transaction context
Returns:
Balance<X>
: Exact amount of X tokens requested
swap_exact_input<X, Y>(pool_registry, fee, coin_in, amount_out_min, sqrt_price_limit, deadline, versioned, clock, ctx)
High-level router function for exact input swaps with automatic pool selection and deadline validation.
public fun swap_exact_input<X, Y>(
pool_registry: &mut PoolRegistry,
fee: u64,
coin_in: Coin<X>,
amount_out_min: u64,
sqrt_price_limit: u128,
deadline: u64,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): Coin<Y>
Parameters:
pool_registry
: Mutable reference to the pool registryfee
: Pool fee rate to select the correct poolcoin_in
: Input tokens to swap (entire amount will be consumed)amount_out_min
: Minimum amount of output tokens expected (slippage protection)sqrt_price_limit
: Price limit for slippage protectiondeadline
: Transaction deadline timestamp to prevent stale transactionsversioned
: Reference for package version validationclock
: Clock object for timing validationctx
: Transaction context
Returns:
Coin<Y>
: Output token coin from the swap
Features:
Automatic token ordering (handles both XโY and YโX directions)
Built-in deadline validation
Minimum output amount validation
Simplified interface for common swap operations
swap_exact_output<X, Y>(pool_registry, fee, coin_in, amount_out, sqrt_price_limit, deadline, versioned, clock, ctx)
High-level router function for exact output swaps with automatic pool selection and deadline validation.
public fun swap_exact_output<X, Y>(
pool_registry: &mut PoolRegistry,
fee: u64,
coin_in: Coin<X>,
amount_out: u64,
sqrt_price_limit: u128,
deadline: u64,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): Coin<Y>
Parameters:
pool_registry
: Mutable reference to the pool registryfee
: Pool fee rate to select the correct poolcoin_in
: Input tokens to swap (excess will be refunded)amount_out
: Exact amount of output tokens to receivesqrt_price_limit
: Price limit for slippage protectiondeadline
: Transaction deadline timestamp to prevent stale transactionsversioned
: Reference for package version validationclock
: Clock object for timing validationctx
: Transaction context
Returns:
Coin<Y>
: Exact amount of output tokens requested
Features:
Automatic token ordering (handles both XโY and YโX directions)
Built-in deadline validation
Automatic refund of excess input tokens
Simplified interface for precise output amount swaps
๐ก Developer Guide
๐ฏ Complete End-to-End Example
Here's a comprehensive example showing how to create a pool, provide liquidity, perform swaps, and collect fees:
public fun complete_clmm_example<USDC, SUI>(
pool_registry: &mut PoolRegistry,
position_registry: &mut PositionRegistry,
usdc_metadata: &CoinMetadata<USDC>,
sui_metadata: &CoinMetadata<SUI>,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
) {
// 1. Create and initialize pool
let initial_sqrt_price = 13043817825332782212; // sqrt(0.5) for 1 USDC = 2 SUI
pool_manager::create_and_initialize_pool_v2<USDC, SUI>(
pool_registry,
3000, // 0.3% fee
initial_sqrt_price,
usdc_metadata,
sui_metadata,
versioned,
clock,
ctx
);
// 2. Open position in price range $1.80 - $2.20 per SUI
let lower_sqrt_price = 11832159566199029792; // sqrt(1/2.20)
let upper_sqrt_price = 14142135623730950624; // sqrt(1/1.80)
let tick_lower = tick_math::get_tick_at_sqrt_price(lower_sqrt_price);
let tick_upper = tick_math::get_tick_at_sqrt_price(upper_sqrt_price);
let position = position_manager::open_position<USDC, SUI>(
position_registry,
pool_registry,
3000,
tick_lower,
tick_upper,
versioned,
ctx
);
// 3. Add liquidity to position
let usdc_coins = coin::mint_for_testing<USDC>(10000_000000, ctx); // 10,000 USDC
let sui_coins = coin::mint_for_testing<SUI>(20000_000000000, ctx); // 20,000 SUI
let deadline = clock::timestamp_ms(clock) + 300_000; // 5 minutes
position_manager::increase_liquidity<USDC, SUI>(
pool_registry,
&mut position,
usdc_coins,
sui_coins,
9500_000000, // Min 9,500 USDC (5% slippage)
19000_000000000, // Min 19,000 SUI (5% slippage)
deadline,
versioned,
clock,
ctx
);
// 4. Perform a swap (someone else trades)
let trader_usdc = coin::mint_for_testing<USDC>(1000_000000, ctx); // 1,000 USDC
let pool = pool_manager::borrow_mut_pool<USDC, SUI>(pool_registry, 3000);
let current_sqrt_price = pool::sqrt_price_current(pool);
let min_sqrt_price = (current_sqrt_price * 95) / 100; // 5% slippage
let sui_out = swap_router::swap_exact_x_to_y<USDC, SUI>(
pool,
trader_usdc,
min_sqrt_price,
versioned,
clock,
ctx
);
// 5. Collect accumulated fees after some trading activity
let (usdc_fees, sui_fees) = position_manager::collect<USDC, SUI>(
pool_registry,
&mut position,
18446744073709551615, // Collect all fees (max u64)
18446744073709551615,
versioned,
clock,
ctx
);
// 6. Collect any reward tokens (if available)
let rewards = position_manager::collect_pool_reward<USDC, SUI, FLOW>(
pool_registry,
&mut position,
18446744073709551615, // Collect all rewards (max u64)
versioned,
clock,
ctx
);
// 7. Remove liquidity when done
let position_liquidity = position::liquidity(&position);
let remove_deadline = clock::timestamp_ms(clock) + 300_000;
position_manager::decrease_liquidity<USDC, SUI>(
pool_registry,
&mut position,
position_liquidity, // Remove all liquidity
0, // Accept any amount (or set minimums)
0,
remove_deadline,
versioned,
clock,
ctx
);
// 8. Close empty position
position_manager::close_position(position_registry, position, versioned, ctx);
// Transfer collected fees and rewards
transfer::public_transfer(usdc_fees, tx_context::sender(ctx));
transfer::public_transfer(sui_fees, tx_context::sender(ctx));
transfer::public_transfer(rewards, tx_context::sender(ctx));
transfer::public_transfer(coin::from_balance(sui_out, ctx), tx_context::sender(ctx));
}
๐ Quick Integration Examples
Basic Liquidity Provision
// 1. Open position
let position = position_manager::open_position<USDC, SUI>(
&mut position_registry,
&pool_registry,
3000, // 0.3% fee
tick_lower,
tick_upper,
&versioned,
ctx
);
// 2. Add liquidity
position_manager::increase_liquidity<USDC, SUI>(
&mut pool_registry,
&mut position,
usdc_coins,
sui_coins,
min_usdc_amount,
min_sui_amount,
deadline,
&versioned,
&clock,
ctx
);
Basic Token Swap
// Swap exact USDC for maximum SUI
let sui_out = swap_router::swap_exact_x_to_y<USDC, SUI>(
&mut pool,
usdc_in,
min_sqrt_price, // Slippage protection
&versioned,
&clock,
ctx
);
Router Function Examples
// High-level exact input swap with automatic pool selection
let usdc_in = coin::mint_for_testing<USDC>(1000_000000, ctx); // 1000 USDC
let deadline = clock::timestamp_ms(&clock) + 300_000; // 5 minutes
let current_sqrt_price = pool::sqrt_price_current(&pool);
let min_sqrt_price = (current_sqrt_price * 95) / 100; // 5% slippage
let sui_out = swap_router::swap_exact_input<USDC, SUI>(
&mut pool_registry,
3000, // 0.3% fee pool
usdc_in,
1900_000000000, // Min 1900 SUI output (5% slippage)
min_sqrt_price,
deadline,
&versioned,
&clock,
ctx
);
// High-level exact output swap with automatic pool selection
let usdc_in = coin::mint_for_testing<USDC>(2000_000000, ctx); // 2000 USDC (excess refunded)
let exact_sui_out = 500_000000000; // Exactly 500 SUI
let max_sqrt_price = (current_sqrt_price * 105) / 100; // 5% slippage
let sui_out = swap_router::swap_exact_output<USDC, SUI>(
&mut pool_registry,
3000, // 0.3% fee pool
usdc_in, // Excess will be refunded
exact_sui_out,
max_sqrt_price,
deadline,
&versioned,
&clock,
ctx
);
// Verify exact amount received
assert!(coin::value(&sui_out) == exact_sui_out, 0);
Swap Best Practices
Swap Flow:
Input Validation: Checks pool state, price limits, and token amounts
Route Calculation: Determines optimal path through active liquidity
Tick Traversal: Executes swap across multiple price ranges if needed
Fee Collection: Deducts swap fees and protocol fees
Price Update: Updates pool price and oracle data
Output Delivery: Transfers resulting tokens to user
Swap Types:
Exact Input Swaps (swap_exact_x_to_y
, swap_exact_y_to_x
):
// Swap all USDC for maximum SUI possible
let sui_out = swap_router::swap_exact_x_to_y<USDC, SUI>(
&mut pool,
usdc_in, // Entire amount consumed
min_sqrt_price, // Price limit (slippage protection)
&versioned,
&clock,
ctx
);
Exact Output Swaps (swap_x_to_exact_y
, swap_y_to_exact_x
):
// Swap minimum USDC needed for exact 1 SUI
let sui_out = swap_router::swap_x_to_exact_y<USDC, SUI>(
&mut pool,
usdc_in, // May have excess refunded
1_000_000_000, // Exactly 1 SUI (9 decimals)
max_sqrt_price, // Price limit
&versioned,
&clock,
ctx
);
Position Management Best Practices
Opening Positions:
// Choose tick range based on strategy
let tick_lower = tick_math::get_tick_at_sqrt_price(lower_price_sqrt);
let tick_upper = tick_math::get_tick_at_sqrt_price(upper_price_sqrt);
// Ensure ticks are valid for the pool's tick spacing
let tick_spacing = pool::tick_spacing(&pool);
let adjusted_lower = (tick_lower / tick_spacing) * tick_spacing;
let adjusted_upper = (tick_upper / tick_spacing) * tick_spacing;
Liquidity Management:
Narrow Ranges: Higher fees, higher impermanent loss risk
Wide Ranges: Lower fees, lower impermanent loss risk
Active Management: Monitor price movements and adjust ranges
Fee Collection Strategy:
// Collect fees regularly to compound returns
let (fee_x, fee_y) = position_manager::collect<X, Y>(
&mut pool_registry,
&mut position,
18446744073709551615, // Max u64 to collect all
18446744073709551615, // Max u64 to collect all
&versioned,
&clock,
ctx
);
// Reinvest fees by adding liquidity
position_manager::increase_liquidity<X, Y>(
&mut pool_registry,
&mut position,
coin::from_balance(fee_x, ctx),
coin::from_balance(fee_y, ctx),
0, // No minimum since we're reinvesting fees
0,
deadline,
&versioned,
&clock,
ctx
);
Defensive Programming Practices
// Always use deadlines for time-sensitive operations
let deadline = clock::timestamp_ms(clock) + 300_000; // 5 minutes
// Set reasonable slippage tolerance (e.g., 0.5%)
let amount_min = (expected_amount * 995) / 1000;
// Check pool state before operations
assert!(pool::is_initialized(&pool), E_POOL_NOT_INITIALIZED);
// Set appropriate price limits for swaps
let current_sqrt_price = pool::sqrt_price_current(&pool);
// For X to Y swaps (price decreasing), set a lower limit
let min_sqrt_price_limit = (current_sqrt_price * 95) / 100; // 5% slippage
// Ensure it's above the absolute minimum
let safe_min_limit = math::max(min_sqrt_price_limit, tick_math::min_sqrt_price() + 1);
// For Y to X swaps (price increasing), set an upper limit
let max_sqrt_price_limit = (current_sqrt_price * 105) / 100; // 5% slippage
// Ensure it's below the absolute maximum
let safe_max_limit = math::min(max_sqrt_price_limit, tick_math::max_sqrt_price() - 1);
// Check price limits before swap to avoid errors
if (x_for_y) {
assert!(sqrt_price_limit < current_sqrt_price, E_PRICE_LIMIT_ALREADY_EXCEEDED);
assert!(sqrt_price_limit > tick_math::min_sqrt_price(), E_PRICE_LIMIT_OUT_OF_BOUNDS);
} else {
assert!(sqrt_price_limit > current_sqrt_price, E_PRICE_LIMIT_ALREADY_EXCEEDED);
assert!(sqrt_price_limit < tick_math::max_sqrt_price(), E_PRICE_LIMIT_OUT_OF_BOUNDS);
};
Precision & Safety Features
Q64.64 Fixed-Point Arithmetic: For precise price calculations
Overflow-Safe Operations: Implements overflow-safe arithmetic operations
Rounding Controls: Provides rounding controls for fee calculations
Integration Patterns
Direct Pool Swap Integration
Best practices for integrating with the core pool::swap
function for custom swap implementations:
// Complete swap integration pattern
public fun custom_swap_exact_input<X, Y>(
pool: &mut Pool<X, Y>,
input_coin: Coin<X>,
min_output_amount: u64,
max_slippage_bps: u64, // basis points (e.g., 50 = 0.5%)
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): Coin<Y> {
let input_amount = coin::value(&input_coin);
let current_sqrt_price = pool::sqrt_price_current(pool);
// Calculate price limit based on slippage tolerance
let price_limit = if (true) { // x_for_y = true
let slippage_factor = 10000 - max_slippage_bps; // e.g., 9950 for 0.5%
(current_sqrt_price * (slippage_factor as u128)) / 10000
} else {
let slippage_factor = 10000 + max_slippage_bps; // e.g., 10050 for 0.5%
(current_sqrt_price * (slippage_factor as u128)) / 10000
};
// Execute swap
let (balance_x_out, balance_y_out, receipt) = pool::swap<X, Y>(
pool,
true, // x_for_y
true, // exact_in
input_amount, // amount_specified
price_limit,
versioned,
clock,
ctx
);
// Pay for the swap
pool::pay<X, Y>(
pool,
receipt,
coin::into_balance(input_coin), // payment_x
balance::zero<Y>(), // payment_y
versioned,
ctx
);
// Validate minimum output
let output_amount = balance::value(&balance_y_out);
assert!(output_amount >= min_output_amount, E_INSUFFICIENT_OUTPUT_AMOUNT);
// Return output (balance_x_out should be zero for x_for_y swaps)
balance::destroy_zero(balance_x_out);
coin::from_balance(balance_y_out, ctx)
}
// Precise swap with receipt-based payment amounts
public fun precise_swap_exact_input<X, Y>(
pool: &mut Pool<X, Y>,
input_coin: Coin<X>,
min_output_amount: u64,
sqrt_price_limit: u128,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): (Coin<X>, Coin<Y>) { // Returns refunded input and output in (x, y) order
let input_amount = coin::value(&input_coin);
// Execute swap
let (balance_x_out, balance_y_out, receipt) = pool::swap<X, Y>(
pool,
true, // x_for_y
true, // exact_in
input_amount, // amount_specified
sqrt_price_limit,
versioned,
clock,
ctx
);
// Extract exact payment amounts from receipt
let amount_x_needed = swap_receipt::amount_x_needed(&receipt);
let amount_y_needed = swap_receipt::amount_y_needed(&receipt);
// For exact_in swaps, amount_x_needed should equal input_amount
// For x_for_y swaps, amount_y_needed should be zero
assert!(amount_y_needed == 0, E_UNEXPECTED_Y_PAYMENT_REQUIRED); // Should be zero for x_for_y
// Split exact amount needed for payment
let payment_coin = coin::split(&mut input_coin, amount_x_needed, ctx);
let payment_x = coin::into_balance(payment_coin);
// Complete the payment
pool::pay<X, Y>(
pool,
receipt,
payment_x,
balance::zero<Y>(),
versioned,
ctx
);
// Validate minimum output
let output_amount = balance::value(&balance_y_out);
assert!(output_amount >= min_output_amount, E_INSUFFICIENT_OUTPUT_AMOUNT);
// Convert output balance to coin
let output_coin = coin::from_balance(balance_y_out, ctx);
balance::destroy_zero(balance_x_out); // Should be zero for x_for_y swaps
// Return refunded input and output in (x, y) order
(input_coin, output_coin)
}
Direct Pool Liquidity Modification Integration
Best practices for integrating with the core pool::modify_liquidity
function for custom liquidity management:
// Complete liquidity addition integration pattern
public fun add_liquidity_to_position<X, Y>(
pool: &mut Pool<X, Y>,
position: &mut Position,
desired_amount_x: u64,
desired_amount_y: u64,
min_amount_x: u64,
min_amount_y: u64,
coin_x: Coin<X>,
coin_y: Coin<Y>,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): (Coin<X>, Coin<Y>) { // Returns refunded coins
// Validate input amounts
assert!(coin::value(&coin_x) >= desired_amount_x, E_INSUFFICIENT_INPUT_AMOUNT);
assert!(coin::value(&coin_y) >= desired_amount_y, E_INSUFFICIENT_INPUT_AMOUNT);
// Get current pool state for calculations
let current_sqrt_price = pool::sqrt_price_current(pool);
let current_liquidity = pool::liquidity(pool);
let tick_lower = position::tick_lower_index(position);
let tick_upper = position::tick_upper_index(position);
// Calculate optimal liquidity amount based on desired token amounts
let target_liquidity = calculate_liquidity_for_amounts(
current_sqrt_price,
tick_lower,
tick_upper,
desired_amount_x,
desired_amount_y
);
// Prepare exact amounts needed (may be less than desired)
// Convert tick boundaries to sqrt prices
let sqrt_price_lower = tick_math::get_sqrt_price_at_tick(tick_lower);
let sqrt_price_upper = tick_math::get_sqrt_price_at_tick(tick_upper);
let (exact_amount_x, exact_amount_y) = liquidity_math::get_amounts_for_liquidity(
current_sqrt_price,
sqrt_price_lower,
sqrt_price_upper,
target_liquidity,
true // add = true for adding liquidity
);
// Validate minimum amounts
assert!(exact_amount_x >= min_amount_x, E_INSUFFICIENT_OUTPUT_AMOUNT);
assert!(exact_amount_y >= min_amount_y, E_INSUFFICIENT_OUTPUT_AMOUNT);
// Split exact amounts from input coins
let balance_x_in = coin::into_balance(coin::split(&mut coin_x, exact_amount_x, ctx));
let balance_y_in = coin::into_balance(coin::split(&mut coin_y, exact_amount_y, ctx));
// Execute liquidity modification
let liquidity_delta = i128::from(target_liquidity);
let (actual_amount_x, actual_amount_y) = pool::modify_liquidity<X, Y>(
pool,
position,
liquidity_delta,
balance_x_in,
balance_y_in,
versioned,
clock,
ctx
);
// Validate actual amounts meet expectations
assert!(actual_amount_x >= min_amount_x, E_INSUFFICIENT_LIQUIDITY_ADDED);
assert!(actual_amount_y >= min_amount_y, E_INSUFFICIENT_LIQUIDITY_ADDED);
// Return any remaining coins as refund
(coin_x, coin_y)
}
// Advanced liquidity removal with precise control
public fun remove_liquidity_from_position<X, Y>(
pool_registry: &mut PoolRegistry,
pool: &mut Pool<X, Y>,
position: &mut Position,
liquidity_to_remove: u128,
min_amount_x_out: u64,
min_amount_y_out: u64,
collect_fees: bool,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): (Balance<X>, Balance<Y>) {
// Validate position has enough liquidity
let current_position_liquidity = position::liquidity(position);
assert!(current_position_liquidity >= liquidity_to_remove, E_INSUFFICIENT_LIQUIDITY);
// Get position details for calculations
let tick_lower = position::tick_lower_index(position);
let tick_upper = position::tick_upper_index(position);
let current_sqrt_price = pool::sqrt_price_current(pool);
// Calculate expected amounts to receive
// Convert tick boundaries to sqrt prices
let sqrt_price_lower = tick_math::get_sqrt_price_at_tick(tick_lower);
let sqrt_price_upper = tick_math::get_sqrt_price_at_tick(tick_upper);
let (expected_amount_x, expected_amount_y) = liquidity_math::get_amounts_for_liquidity(
current_sqrt_price,
sqrt_price_lower,
sqrt_price_upper,
liquidity_to_remove,
false // add = false for removing liquidity
);
// Ensure expected amounts meet minimum requirements
assert!(expected_amount_x >= min_amount_x_out, E_INSUFFICIENT_EXPECTED_OUTPUT);
assert!(expected_amount_y >= min_amount_y_out, E_INSUFFICIENT_EXPECTED_OUTPUT);
// Execute liquidity removal
let liquidity_delta = i128::neg_from(liquidity_to_remove);
let (actual_amount_x, actual_amount_y) = pool::modify_liquidity<X, Y>(
pool,
position,
liquidity_delta,
balance::zero<X>(), // No input when removing liquidity
balance::zero<Y>(), // No input when removing liquidity
versioned,
clock,
ctx
);
// Validate actual amounts meet minimum requirements
assert!(actual_amount_x >= min_amount_x_out, E_INSUFFICIENT_OUTPUT_AMOUNT);
assert!(actual_amount_y >= min_amount_y_out, E_INSUFFICIENT_OUTPUT_AMOUNT);
// Collect fees if requested
if (collect_fees) {
let (collected_x, collected_y) = position_manager::collect<X, Y>(
pool_registry,
position,
max_u64, // Collect all available fees
max_u64, // Collect all available fees
ctx
);
(collected_x, collected_y)
} else {
(
balance::zero<X>(),
balance::zero<Y>()
)
}
}
Position Fees and Rewards Management
Best practices for collecting fees and rewards from positions:
// Optimized fee collection with threshold checking
public fun collect_fees<X, Y>(
pool_registry: &mut PoolRegistry,
position: &mut Position,
versioned: &Versioned,
ctx: &mut TxContext
): (Balance<X>, Balance<Y>) {
let (fee_x, fee_y) = position_manager::collect<X, Y>(
pool_registry,
position,
max_u64, // Collect all available fees
max_u64, // Collect all available fees
versioned,
ctx
);
(fee_x, fee_y)
}
// Collect specific reward token type from position
public fun collect_position_reward<X, Y, RewardToken>(
pool_registry: &mut PoolRegistry,
position: &mut Position,
versioned: &Versioned,
clock: &Clock,
ctx: &mut TxContext
): Coin<RewardToken> { // Returns collected reward coin
// Collect the specific reward token type
let collected_reward = position_manager::collect_pool_reward<X, Y, RewardToken>(
pool_registry,
position,
max_u64, // Collect all available rewards
versioned,
clock,
ctx
);
collected_reward
}
Position Monitoring
Best practices for getting position token amounts:
// Get current token amounts in a position
public fun get_position_amounts<X, Y>(
pool: &Pool<X, Y>,
position: &Position
): (u64, u64) { // Returns (amount_x, amount_y)
let tick_lower = position::tick_lower_index(position);
let tick_upper = position::tick_upper_index(position);
let current_sqrt_price = pool::sqrt_price_current(pool);
let position_liquidity = position::liquidity(position);
// Calculate current token amounts in the position
if (position_liquidity > 0) {
// Convert tick boundaries to sqrt prices
let sqrt_price_lower = tick_math::get_sqrt_price_at_tick(tick_lower);
let sqrt_price_upper = tick_math::get_sqrt_price_at_tick(tick_upper);
liquidity_math::get_amounts_for_liquidity(
current_sqrt_price,
sqrt_price_lower,
sqrt_price_upper,
position_liquidity,
false // add = false for getting amounts
)
} else {
(0, 0)
}
}
Multi-Hop Swaps
For token pairs without direct pools, implement multi-hop routing:
// USDC -> SUI -> BTC (two-hop swap)
// Step 1: USDC -> SUI
let sui_balance = swap_router::swap_exact_x_to_y<USDC, SUI>(
&mut usdc_sui_pool,
usdc_in,
min_sqrt_price_1,
&versioned,
&clock,
ctx
);
// Step 2: SUI -> BTC
let btc_balance = swap_router::swap_exact_x_to_y<SUI, BTC>(
&mut sui_btc_pool,
coin::from_balance(sui_balance, ctx),
min_sqrt_price_2,
&versioned,
&clock,
ctx
);
Flash Loans Integration
Leverage flash loans for arbitrage and other strategies:
// Flash loan pattern for arbitrage
public fun arbitrage_opportunity<X, Y>(
pool: &mut Pool<X, Y>,
flash_amount_x: u64,
flash_amount_y: u64,
versioned: &Versioned,
ctx: &mut TxContext
): (Balance<X>, Balance<Y>) {
// 1. Flash loan tokens from pool
let (loan_x, loan_y, flash_receipt) = pool::flash<X, Y>(
pool,
flash_amount_x,
flash_amount_y,
versioned,
ctx
);
// 2. Get debt amounts from receipt (includes fees)
let (total_debt_x, total_debt_y) = pool::flash_receipt_debts(&flash_receipt);
// 3. Execute arbitrage logic here
// Example: swap tokens, interact with other protocols, etc.
// ...your arbitrage logic...
// 4. Prepare repayment (must include loan amount + fees)
// Ensure you have enough to repay the debt
let repay_x = loan_x; // Add your tokens if needed: balance::join(&mut loan_x, additional_x);
let repay_y = loan_y; // Add your tokens if needed: balance::join(&mut loan_y, additional_y);
// 5. Repay the flash loan with fees
pool::repay<X, Y>(
pool,
flash_receipt,
repay_x,
repay_y,
versioned,
ctx
);
// 6. Return any profit
(balance::zero<X>(), balance::zero<Y>())
}
๐ฎ Price Oracle System
FlowX CLMM includes a sophisticated time-weighted average price (TWAP) oracle system that provides reliable price feeds and historical data tracking. The oracle automatically records price and liquidity data with each swap, creating a decentralized price feed that follows the Uniswap V3 oracle design.
๐ฏ Oracle Features
TWAP Calculation
Time-weighted average prices over any time period
Automated Recording
Automatic price updates with every transaction
Historical Data
Up to 1000 observations stored per pool
Manipulation Resistant
Requires significant capital to manipulate prices
Gas Efficient
Optimized storage and calculation algorithms
๐ Oracle Data Structure
Each oracle observation contains:
Timestamp (seconds): When the observation was recorded (in seconds, not milliseconds)
Tick Cumulative (I64): Cumulative sum of tick values over time (signed integer)
Seconds per Liquidity (u256): Time-weighted measure of liquidity depth
Initialization Status: Whether the observation slot is active
๐ง Core Oracle Functions
Get Historical Price Data
/// Get TWAP data for specified time periods
public fun observe<X, Y>(
self: &Pool<X, Y>,
seconds_agos: vector<u64>,
clock: &Clock
): (vector<I64>, vector<u256>)
Parameters:
self
: Pool to query oracle data fromseconds_agos
: Array of time periods to look back (in seconds)clock
: Clock object for current timestamp
Returns:
vector<I64>
: Tick cumulatives for each time period (signed integers)vector<u256>
: Seconds per liquidity cumulatives for each time period
Increase Oracle Capacity
/// Increase the maximum number of observations this pool will store
public fun increase_observation_cardinality_next<X, Y>(
self: &mut Pool<X, Y>,
observation_cardinality_next: u64,
versioned: &Versioned,
ctx: &TxContext
)
Parameters:
self
: Pool to increase capacity forobservation_cardinality_next
: New maximum number of observations (max 1000)versioned
: Versioned object for package version validationctx
: Transaction context
๐ก TWAP Calculation Examples
Basic TWAP Price Calculation
use flowx_clmm::i64;
use flowx_clmm::i32;
use flowx_clmm::tick_math;
/// Calculate TWAP price over specified time period
public fun calculate_twap_price<X, Y>(
pool: &Pool<X, Y>,
period_seconds: u64,
clock: &Clock
): u128 {
// Get tick cumulatives for current time and period ago
let seconds_agos = vector[0, period_seconds];
let (tick_cumulatives, _) = pool.observe(seconds_agos, clock);
let current_cumulative = *vector::borrow(&tick_cumulatives, 0);
let past_cumulative = *vector::borrow(&tick_cumulatives, 1);
// Calculate time-weighted average tick (handle signed arithmetic)
let tick_delta = i64::sub(current_cumulative, past_cumulative);
let average_tick_i64 = i64::div(tick_delta, i64::from(period_seconds));
// Convert I64 to I32 for tick math
let average_tick = if (i64::is_neg(average_tick_i64)) {
i32::neg_from(i64::abs_u64(average_tick_i64))
} else {
i32::from_u64(i64::abs_u64(average_tick_i64))
};
// Convert tick to sqrt price
tick_math::get_sqrt_price_at_tick(average_tick)
}
/// Get current oracle state information
public fun get_oracle_info<X, Y>(pool: &Pool<X, Y>): (u64, u64, u64) {
(
pool.observation_index(), // Current observation index
pool.observation_cardinality(), // Number of populated observations
pool.observation_cardinality_next() // Maximum observations capacity
)
}
Data Access Functions
// Pool oracle state getters (read-only)
pool.observation_index(); // Current observation index: u64
pool.observation_cardinality(); // Active observations count: u64
pool.observation_cardinality_next(); // Maximum capacity: u64
pool.borrow_observations(); // Direct access to observations vector
โ ๏ธ Error Handling
Common Error Scenarios
E_INSUFFICIENT_OUTPUT_AMOUNT
Slippage exceeded
Increase tolerance or wait
E_EXCESSIVE_INPUT_AMOUNT
Price moved unfavorably
Retry with updated limits
E_ZERO_AMOUNT
Cannot operate with zero amounts
Provide non-zero amounts
E_NOT_EMPTY_POSITION
Position must be empty before closing
Remove all liquidity and collect fees
E_PRICE_LIMIT_ALREADY_EXCEEDED
Current price beyond specified limit
Update price limit
E_PRICE_LIMIT_OUT_OF_BOUNDS
Price limit outside valid range
Use valid price range
Error Handling and Edge Cases
Common Error Scenarios:
E_INSUFFICIENT_OUTPUT_AMOUNT
: Slippage exceeded, increase tolerance or waitE_EXCESSIVE_INPUT_AMOUNT
: Price moved unfavorably, retry with updated limitsE_ZERO_AMOUNT
: Cannot operate with zero amountsE_NOT_EMPTY_POSITION
: Position must be empty before closing (zero liquidity, zero coin owed, and zero reward)E_PRICE_LIMIT_ALREADY_EXCEEDED
: The current price has already moved beyond the specified price limit before swap executionE_PRICE_LIMIT_OUT_OF_BOUNDS
: The specified price limit is outside the valid range (below minimum or above maximum sqrt price)
๐งฎ Mathematical Libraries
FlowX CLMM includes optimized mathematical libraries for precise calculations:
Core Math Libraries
tick_math.move
Tick โ Price conversions
get_sqrt_price_at_tick
, get_tick_at_sqrt_price
sqrt_price_math.move
Square root price calculations
get_next_sqrt_price_from_amount
, get_amount_delta
liquidity_math.move
Liquidity calculations
add_delta
, get_amounts_for_liquidity
full_math_u128.move
High-precision arithmetic
mul_div
, mul_div_round_up
swap_math.move
Swap calculations
compute_swap_step
๐ ๏ธ Development
๐งช Testing
Unit Tests
# Run all tests
sui move test
# Run with verbose output
sui move test --verbose
# Run with gas profiling
sui move test --gas-limit 1000000000
๐ Environment Configuration
Environment Variables
# .env.example
SUI_NETWORK=testnet
JSON_RPC_ENDPOINT=https://fullnode.testnet.sui.io:443 # RPC endpoint for Sui network (testnet/mainnet)
PRIVATE_KEY=your_private_key_here
๐ฆ Deployment
๐งช Testnet Deployment
# 2. Build package
sui move build
# 3. Deploy to testnet
sui client publish --gas-budget 100000000
๐ Mainnet Deployment
# 1. Switch to mainnet
sui client switch --env mainnet
# 2. Verify build
sui move build --verification
# 3. Deploy with higher gas budget
sui client publish --gas-budget 200000000
Last updated
Was this helpful?