> For the complete documentation index, see [llms.txt](https://docs.ferra.ag/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ferra.ag/integration/damm/smart-contract/position-module.md).

# Position Module

### Overview

The Position module provides:

* Position NFT creation and management
* Liquidity tracking within tick ranges
* Fee accumulation and collection
* Reward and points tracking
* Position locking mechanism
* Batch position queries

### Data Structures

#### Position

The main NFT struct representing position ownership:

```move
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 (0 = unlocked)
}
```

#### PositionInfo

The computational data for position calculations:

```move
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 within a pool:

```move
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:

```move
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**

```move
public(friend) fun open_position<CoinTypeA, CoinTypeB>(
    manager: &mut PositionManager,
    pool_id: ID,
    pool_index: u64,
    icon_url: String,
    tick_lower_index: I32,
    tick_upper_index: I32,
    ctx: &mut TxContext,
): Position
```

Creates a new liquidity position NFT.

**Parameters:**

| Parameter          | Description                          |
| ------------------ | ------------------------------------ |
| `manager`          | Mutable reference to PositionManager |
| `pool_id`          | ID of the target pool                |
| `pool_index`       | Pool's index number                  |
| `icon_url`         | URL for position icon                |
| `tick_lower_index` | Lower bound of liquidity range       |
| `tick_upper_index` | Upper bound of liquidity range       |
| `ctx`              | Transaction context                  |

**Returns:** New Position NFT

**Example:**

```move
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
    ctx
);
```

#### Liquidity Management

**increase\_liquidity**

```move
public(friend) fun increase_liquidity(
    manager: &mut PositionManager,
    position: &mut Position,
    delta_liquidity: u128,
    fee_growth_inside_a: u128,
    fee_growth_inside_b: u128,
    points_growth_inside: u128,
    rewards_growth_inside: vector<u128>,
): u128
```

Adds liquidity to an existing position.

**Parameters:**

| Parameter               | Description                           |
| ----------------------- | ------------------------------------- |
| `manager`               | Mutable reference to PositionManager  |
| `position`              | Mutable reference to Position NFT     |
| `delta_liquidity`       | Amount of liquidity to add            |
| `fee_growth_inside_a`   | Current fee growth for token A        |
| `fee_growth_inside_b`   | Current fee growth for token B        |
| `points_growth_inside`  | Current points growth                 |
| `rewards_growth_inside` | Vector of current reward growth rates |

**Returns:** New total liquidity amount

**decrease\_liquidity**

```move
public(friend) fun decrease_liquidity(
    manager: &mut PositionManager,
    position: &mut Position,
    delta_liquidity: u128,
    fee_growth_inside_a: u128,
    fee_growth_inside_b: u128,
    points_growth_inside: u128,
    rewards_growth_inside: vector<u128>,
    clock: &Clock,
): u128
```

Removes liquidity from a position.

**Additional Checks:**

* Validates position is not locked
* Ensures sufficient liquidity exists

#### Fee Management

**update\_fee**

```move
public(friend) fun update_fee(
    manager: &mut PositionManager,
    position_id: ID,
    fee_growth_a: u128,
    fee_growth_b: u128,
): (u64, u64)
```

Updates accumulated fees for a position.

**reset\_fee**

```move
public(friend) fun reset_fee(
    manager: &mut PositionManager,
    position_id: ID,
): (u64, u64)
```

Collects accumulated fees and resets counters.

**update\_and\_reset\_fee**

```move
public(friend) fun update_and_reset_fee(
    manager: &mut PositionManager,
    position_id: ID,
    fee_growth_a: u128,
    fee_growth_b: u128,
): (u64, u64)
```

Updates and collects fees in one operation.

#### Points Management

**update\_points**

```move
public(friend) fun update_points(
    manager: &mut PositionManager,
    position_id: ID,
    points_growth_inside: u128,
): u128
```

Updates accumulated points for a position.

#### Reward Management

**update\_rewards**

```move
public(friend) fun update_rewards(
    manager: &mut PositionManager,
    position_id: ID,
    rewards_growth_inside: vector<u128>,
): vector<u64>
```

Updates accumulated rewards for a position.

**update\_and\_reset\_rewards**

```move
public(friend) fun update_and_reset_rewards(
    manager: &mut PositionManager,
    position_id: ID,
    rewards_growth_inside: vector<u128>,
    reward_index: u64,
): u64
```

Updates and collects specific reward token.

**reset\_rewarder**

```move
public(friend) fun reset_rewarder(
    manager: &mut PositionManager,
    position_id: ID,
    reward_index: u64,
): u64
```

Resets reward counter for specific token.

#### Position Locking

**lock\_position**

```move
public(friend) fun lock_position(
    position: &mut Position,
    lock_until: u64,
)
```

Locks a position until a specific timestamp.

**get\_lock\_until**

```move
public fun get_lock_until(position: &Position): u64
```

Gets the lock expiration timestamp.

#### Position Closure

**close\_position**

```move
public(friend) fun close_position(
    manager: &mut PositionManager,
    position: Position
)
```

Closes an empty position and burns the NFT.

**Requirements:**

* Position must have zero liquidity
* No accumulated fees
* No pending rewards

### Query Functions

#### Position Information

```move
// Get position liquidity
public fun liquidity(position: &Position): u128

// Get tick range
public fun tick_range(position: &Position): (I32, I32)

// Get associated pool ID
public fun pool_id(position: &Position): ID

// Get position index
public fun index(position: &Position): u64

// Get position name
public fun name(position: &Position): String

// Get position description
public fun description(position: &Position): String
```

#### PositionInfo Queries

```move
// Get liquidity from info
public fun info_liquidity(info: &PositionInfo): u128

// Get accumulated fees
public fun info_fee_owned(info: &PositionInfo): (u64, u64)

// Get fee growth inside
public fun info_fee_growth_inside(info: &PositionInfo): (u128, u128)

// Get accumulated points
public fun info_points_owned(info: &PositionInfo): u128

// Get points growth inside
public fun info_points_growth_inside(info: &PositionInfo): u128

// Get rewards vector
public fun info_rewards(info: &PositionInfo): &vector<PositionReward>

// Get tick range from info
public fun info_tick_range(info: &PositionInfo): (I32, I32)

// Get position ID from info
public fun info_position_id(info: &PositionInfo): ID
```

#### Manager Queries

```move
// Check if position exists
public fun is_position_exist(
    manager: &PositionManager, 
    position_id: ID
): bool

// Borrow position info
public fun borrow_position_info(
    manager: &PositionManager, 
    position_id: ID
): &PositionInfo

// Fetch multiple positions
public fun fetch_positions(
    manager: &PositionManager,
    start: vector<ID>,
    limit: u64
): vector<PositionInfo>

// Get initialized rewards count
public fun inited_rewards_count(
    manager: &PositionManager, 
    position_id: ID
): u64

// Get rewards amount owned
public fun rewards_amount_owned(
    manager: &PositionManager, 
    position_id: ID
): vector<u64>
```

#### Validation Functions

**is\_empty**

```move
public fun is_empty(position_info: &PositionInfo): bool
```

Checks if position can be closed:

* Zero liquidity
* No accumulated fees
* No pending rewards

### Position Lifecycle

#### 1. Creation

```move
// Create new position
let position = open_position<TokenA, TokenB>(
    &mut manager, pool_id, pool_index, icon_url, 
    lower_tick, upper_tick, ctx
);
```

#### 2. Liquidity Addition

```move
// 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

```move
// Collect fees
let (fee_a, fee_b) = update_and_reset_fee(
    &mut manager, position_id, fee_growth_a, fee_growth_b
);
```

#### 4. Liquidity Removal

```move
// 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

```move
// Close empty position
if (is_empty(&position_info)) {
    close_position(&mut manager, position);
}
```

### Fee and Reward Calculation

#### 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:

```move
// 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:

```move
// 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:

```move
let current_time = clock::timestamp_ms(clock);
assert!(position.lock_until <= current_time, E_POSITION_LOCKED);
```

### Error Codes

| Code | Constant                       | Description                    |
| ---- | ------------------------------ | ------------------------------ |
| 601  | `E_MATH_OVERFLOW`              | Fee calculation overflow       |
| 602  | `E_REWARD_OVERFLOW`            | Reward calculation overflow    |
| 603  | `E_POINTS_OVERFLOW`            | Points calculation overflow    |
| 606  | `E_POSITION_NOT_FOUND`         | Position not found in registry |
| 608  | `E_LIQUIDITY_OVERFLOW`         | Liquidity addition overflow    |
| 609  | `E_INSUFFICIENT_LIQUIDITY`     | Not enough liquidity to remove |
| 610  | `E_REWARD_INDEX_OUT_OF_BOUNDS` | Invalid reward index           |
| 611  | `E_POSITION_LOCKED`            | Position is currently locked   |

### Examples

#### Complete Position Lifecycle

```move
// 1. Create position manager (done by pool)
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
    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

```move
// 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;
}
```

#### Locking Position for Vesting

```move
// Lock position for 30 days
let thirty_days_ms = 30 * 24 * 60 * 60 * 1000;
let lock_until = clock::timestamp_ms(&clock) + thirty_days_ms;
position::lock_position(&mut position, lock_until);

// Attempting to remove liquidity before unlock will fail
// with E_POSITION_LOCKED error
```

### Best Practices

#### Position Management

1. Always validate tick ranges before creating positions
2. Check lock status before attempting to decrease liquidity
3. Update fees and rewards before liquidity changes
4. Use batch queries for efficient data retrieval

#### Error Handling

* `601`: Fee overflow - reduce liquidity or collect fees more frequently
* `602`: Reward overflow - collect rewards more frequently
* `603`: Points overflow - rare, indicates very long-held position
* `606`: Position not found - verify position ID exists
* `608`: Liquidity overflow - reduce liquidity delta
* `609`: Insufficient liquidity - check position liquidity before removing
* `610`: Invalid reward index - verify rewarder is initialized
* `611`: Position locked - wait until lock expires

#### Gas Optimization

* Use `fetch_positions` for batch operations
* Combine fee updates with liquidity changes
* Close empty positions to free storage

### Security Considerations

1. **Position Ownership**: Only the NFT holder can modify the position
2. **Tick Validation**: All tick ranges are validated for correctness
3. **Overflow Protection**: All arithmetic operations include overflow checks
4. **Lock Enforcement**: Locked positions cannot have liquidity decreased
5. **Access Control**: Critical functions are `friend`-only (callable only by pool module)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ferra.ag/integration/damm/smart-contract/position-module.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
