Provisioning Liquidity to Subnets
Overview
The Liquidity Position feature allows users to provide trading liquidity for specific subnets, within specified price ranges for the subnet token. This system is based on Uniswap V3's concentrated liquidity model and enables providers to earn fees from trading activity.
By creating a liquidity position (LP), any TAO holder can contribute to the health of a subnet by providing liquidity for efficient trading, thereby earning fees from trading activity.
Liquidity providers participate by creating liquidity positions (LPs). Each LP is defined by its:
- Price Range: Defined by
price_low
andprice_high
in TAO - Liquidity Amount: The total liquidity provided
- Position ID: Unique identifier for the position
- Fee Tracking: Separate tracking for TAO and Alpha fees earned
Liquidity Positions vs. Staking
While both staking and liquidity provision involve committing tokens to support the Bittensor network, they serve different purposes and operate through distinct mechanisms.
Staking is designed to support validators and miners by providing them with consensus power. When you stake TAO to a validator, you're essentially voting for that validator's participation in the subnet's consensus mechanism. The validator's total stake (including your delegation) determines their share of emissions and influence in the network.
Stakers earn emissions off of their stake, which are distributed each tempo.
Liquidity provision, on the other hand, is focused on market making and trading facilitation. By providing liquidity to a subnet's trading pool, you're enabling other users to trade between TAO and the subnet's Alpha tokens. This creates a more liquid market and improves price discovery for the subnet's token.
Liquidity providers earn fees when others stake or unstake within the price range defined on the position.
Subnet creators can enable and disable user liquidity provision via the toggle_user_liquidity
function.
Tokenomics
Fees
Liquidity providers earn fees from trading activity within their price range:
- TAO Fees: Fees earned in TAO tokens
- Alpha Fees: Fees earned in Alpha tokens
- Fee Distribution: Proportional to liquidity provided and trading volume
The calculate_fees()
function calculates both TAO and Alpha fees based on global fee data and position liquidity.
Dynamic token composition
A liquidity position (LP) can hold TAO, alpha, or both. This depends on the subnet's current token price relative to the range specified for the LP when it was created.
Price Below Range (current_price < price_low
):
- Position becomes 100% Alpha tokens
amount_alpha = liquidity * (1/sqrt_price_low - 1/sqrt_price_high)
amount_tao = 0
Price Above Range (current_price > price_high
):
- Position becomes 100% TAO tokens
amount_alpha = 0
amount_tao = liquidity * (sqrt_price_high - sqrt_price_low)
Price Within Range (price_low <= current_price <= price_high
):
- Position maintains mixed token composition
amount_alpha = liquidity * (1/sqrt_current_price - 1/sqrt_price_high)
amount_tao = liquidity * (sqrt_current_price - sqrt_price_low)
See how it's computed
The LiquidityPosition.to_token_amounts()
method shows how token composition changes based on current price vs. range boundaries.
def to_token_amounts(
self, current_subnet_price: Balance
) -> tuple[Balance, Balance]:
sqrt_price_low = math.sqrt(self.price_low)
sqrt_price_high = math.sqrt(self.price_high)
sqrt_current_subnet_price = math.sqrt(current_subnet_price)
if sqrt_current_subnet_price < sqrt_price_low:
amount_alpha = self.liquidity * (1 / sqrt_price_low - 1 / sqrt_price_high)
amount_tao = 0
elif sqrt_current_subnet_price > sqrt_price_high:
amount_alpha = 0
amount_tao = self.liquidity * (sqrt_price_high - sqrt_price_low)
else:
amount_alpha = self.liquidity * (
1 / sqrt_current_subnet_price - 1 / sqrt_price_high
)
amount_tao = self.liquidity * (sqrt_current_subnet_price - sqrt_price_low)
return Balance.from_rao(int(amount_alpha), self.netuid), Balance.from_rao(
int(amount_tao)
)
Price Range Considerations
- Narrow Ranges: Higher fee concentration but more likely to become single-token when price moves
- Wide Ranges: Lower fee concentration but more likely to maintain mixed token composition
Liquidity Position Lifecycle
Creating a Position
When creating a liquidity position, users provide liquidity in the form of a single liquidity
parameter (in RAO). The system automatically calculates and charges the appropriate amounts of TAO and Alpha tokens from the user's wallet based on the current price.
- User calls
add_liquidity()
withliquidity
,price_low
, andprice_high
parameters - System converts price range to tick indices using
price_to_tick()
- System calculates required TAO and Alpha amounts based on current price and range
- Tokens are transferred from user's wallet to the liquidity pool
- A new
LiquidityPosition
is created with a uniqueposition_id
Modifying a Position
Position management through modify_liquidity
allows you to adjust existing positions. When adding liquidity with a positive liquidity_delta
, additional TAO and Alpha tokens are transferred from your wallet and the position's liquidity field is updated. When removing liquidity with a negative liquidity_delta
, the system calculates the exact TAO and Alpha token amounts based on the current price and your position's price range using the same mathematical formulas as position creation See source code. These calculated amounts are returned to your wallet and the position's liquidity field is updated.
Fee Accumulation and Distribution
Fees are generated when users perform swaps (trading TAO for Alpha or vice versa) within your position's price range. The fee accumulation and distribution system works as follows:
Fee Generation
Fees are calculated per swap transaction using the subnet's fee rate (default 0.3% or 196/65535) See source code. When a swap occurs, the add_fees()
function distributes the fee proportionally to all active liquidity providers based on their share of the current liquidity See source code.
Fee Tracking
The system maintains two levels of fee tracking:
- Global Fee Counters:
FeeGlobalTao
andFeeGlobalAlpha
track total fees accumulated across the entire subnet See source code - Tick-Level Tracking: Individual ticks record the global fee state when they are crossed, enabling precise fee calculation for positions See source code
Fee Calculation
Each position calculates its earned fees using the collect_fees()
method, which:
- Determines the fees accumulated within the position's price range
- Subtracts previously collected fees to get the new fees earned
- Multiplies by the position's liquidity share to get the final fee amount See source code
Fee Distribution
Fees are NOT distributed automatically per tempo like emissions. Instead, fees are only distributed when you actively interact with your position:
- When modifying a position (adding or removing liquidity): All accumulated fees are automatically collected and sent to your wallet See source code
- When removing a position entirely: All accumulated fees are collected along with your position's tokens See source code
Fees are NOT added to your position's liquidity.
Fees are tracked in the position's fees_tao
and fees_alpha
fields and are only distributed to your wallet when you perform a position operation. This means your position's token composition and liquidity remain unchanged by fee accumulation - only the fee tracking variables are updated See source code.
This means you must actively manage your positions to claim your earned fees - they remain locked in the position until you perform a position operation (modify or remove).
Removing a Position
When a position is destroyed/removed, the position's liquidity is converted back to tokens based on the current subnet price relative to your position's price range. The position is then deleted from the system.
Managing positions
Adding a liquidity position
Create a liquidity position with add_liquidity
.
await subtensor.add_liquidity(
wallet=wallet,
netuid=netuid,
liquidity=Balance.from_tao(1.0),
price_low=Balance.from_tao(1.5),
price_high=Balance.from_tao(2.0),
wait_for_inclusion=True,
wait_for_finalization=False,
period=None
)
Modifying a position
Use modify_liquidity
with the desired amount to add or subtract liquidity to an existing position.
# Adding liquidity (positive delta)
await subtensor.modify_liquidity(
wallet=wallet,
netuid=netuid,
position_id=position_id,
liquidity_delta=Balance.from_tao(0.5),
wait_for_inclusion=True,
wait_for_finalization=False,
period=None
)
# Subtracting liquidity (negative delta)
await subtensor.modify_liquidity(
wallet=wallet,
netuid=netuid,
position_id=position_id,
liquidity_delta=Balance.from_tao(0.6) * -1, # or -Balance.from_tao(0.6)
wait_for_inclusion=True,
wait_for_finalization=False,
period=None
)
Removing a liquidity position
Removes liquidity and credits balances back to the creator's wallet.
await subtensor.remove_liquidity(
wallet=wallet,
netuid=netuid,
position_id=position_id,
wait_for_inclusion=True,
wait_for_finalization=False,
period=None
)
Listing positions
Get all positions on a specific subnet for a specific wallet. Returns a list of LiquidityPosition
objects with calculated fees.
positions = await subtensor.get_liquidity_list(
wallet=wallet,
netuid=netuid,
block=None
)