This describes a pre-release version of LBAMM. Interfaces and behavior may change.
Verify the exact repository commit before building production integrations.
Fixed Price Pool
The Fixed Price Pool enforces a constant token ratio while supporting asymmetric liquidity provisioning through a height-based accounting model.
This document explains:
- How the liquidity model works
- How pools are created
- Position lifecycle (add → accrue → collect → withdraw)
- Swap behavior
- Quoter usage
- Event indexing and off-chain height reconstruction
This page is written for integrators interacting with LBAMM Core.
Liquidity Model
Fixed Ratio Pricing
Fixed pools enforce a constant ratio between token0 and token1 with a potential range of ratios from type(uint128).max:1 to 1:type(uint128).max.
The ratio is encoded within fixed pools as:
packedRatio = (ratio0 << 128) | uint128(ratio1);
Example:
- ratio0 = 1
- ratio1 = 4
- Swapping 100 token0 yields 400 token1 (before fees).
During pool creation a supplied ratio will be simplified by the greatest common divisor between the ratio amounts to avoid duplicate pools with the same parameters and effective price ratio. For example - a ratio of 9:3 will be simplified to 3:1.
The ratio is converted into sqrtPriceX96 during creation to integrate with LBAMM’s pricing and hook infrastructure.
The price does not change after deployment (except for rounding dust).
Height-Based Liquidity Accounting
Because price does not move, liquidity attribution is handled through a height system.
Conceptually:
- Liquidity providers deposit token0 and/or token1.
- Deposits are represented along height ranges.
- Swap flow moves height up or down.
- Positions earn fees while current height is within their range.
A unit of height corresponds to:
1 unit of token × number of active liquidity positions at that height.
If 6 providers each supply 100 tokens across height 0–100:
- Each height unit represents 6 tokens.
- A swap of 300 tokens moves height by 50.
This model enables:
- Fully asymmetric liquidity
- Deterministic swap execution
- Fair fee attribution
Creating a Fixed Pool
Pools are created via LBAMM core by calling the createPool function with:
PoolCreationDetails.poolTypeset to the Fixed Pool address.PoolCreationDetails.poolParamsset to the ABI encoded bytes of aFixedPoolCreationDetailsstruct.
FixedPoolCreationDetails
Pool-specific configuration is encoded into poolParams.
struct FixedPoolCreationDetails {
uint8 spacing0;
uint8 spacing1;
uint256 packedRatio;
}
Parameters
spacing0 / spacing1
- Height granularity for token0 and token1.
- Liquidity must align to
10^spacing. - Prevents griefing via excessive micro-positions.
- Maximum value: 24.
packedRatio
- Encodes fixed price ratio.
- Must represent non-zero ratio.
- Simplified internally to smallest values that represent the ratio (ie. 9:3 becomes 3:1).
Encoding Pool Creation
Example:
FixedPoolCreationDetails memory fixedDetails = FixedPoolCreationDetails({
spacing0: 4,
spacing1: 4,
packedRatio: (uint256(1) << 128) | uint128(4)
});
PoolCreationDetails memory details = PoolCreationDetails({
poolType: FIXED_POOL_TYPE,
fee: 30, // 0.30%
token0: token0,
token1: token1,
poolHook: address(0),
poolParams: abi.encode(fixedDetails)
});
(bytes32 poolId,,) = lbamm.createPool(
details,
"",
"",
"",
""
);
poolId is deterministic and encodes:
- pool type
- fee
- height spacing
- parameters such as token pair, pool hook, and packed ratio become a hashed representation within
poolId
Position Lifecycle
Positions are created by adding liquidity to the pool. Fixed pools utilize the base position identifier supplied by the LBAMM core without modification, meaning that a single liquidity provider has one effective position per liquidity hook that they deposit liquidity with. Additional deposits to a position or partial withdrawals from a position will reposition the net new position - existing position value plus additional deposit (or minus partial withdrawal) - around the current height.
Adding and removing liquidity will collect all owed fees for the position. Fees may also be collected through the LBAMM collectFees function without modifying liquidity.
Adding Liquidity
Liquidity is added to a fixed pool through the addLiquidity function on LBAMM core. Liquidity may be provided to either or both tokens in the pool in any amounts that align with the pool's defined token height spacing.
Pool-specific parameters are encoded into:
struct FixedLiquidityModificationParams {
uint256 amount0;
uint256 amount1;
bool addInRange0;
bool addInRange1;
uint256 endHeightInsertionHint0;
uint256 endHeightInsertionHint1;
uint256 maxStartHeight0;
uint256 maxStartHeight1;
}
Parameter Details
amount0 / amount1
- Maximum deposit amounts.
- Actual deposits determined by height alignment.
addInRange0 / addInRange1
- Allows deposit inside current height.
- Consumes portion of opposite token to align.
- For example:
- A pool with a ratio of 1 token0 to 3 token1 has a height spacing of 100 for token0, token0 current height is 10.
- If
addInRange0is false, the liquidity for token0 will deposit at a start height of 100. - If
addInRange0is true, the liquidity for token0 will deposit at a start height of 0 and 30 token1 will be removed fromamount1.
- For example:
endHeightInsertionHint0 / endHeightInsertionHint1
- Fixed pools maintain a linked list of heights for each token in the pool to efficiently traverse the heights.
- Start heights are simple to insert into the linked list as the nodes above and below the current height are known.
- End heights depend on the start height and amount of liquidity being added which could place them anywhere above the current height in the linked list.
- Insertion hints optimize the search for the insertion point in the linked list by giving the fixed pool a starting point for the search.
- Well defined hints are nodes that are nearby the expected end height of the liquidity position.
- Poorly defined hints may result in extra search costs as the pool searches for the correct node to insert at.
Integrators should monitor active heights via events to determine optimal end height insertion hints.
maxStartHeight0 / maxStartHeight1
- Front-running protection.
- Reverts if computed start height exceeds bound.
- Prevents forced out-of-range liquidity.
Example Add Liquidity
FixedLiquidityModificationParams memory fixedParams = FixedLiquidityModificationParams({
amount0: 100e18,
amount1: 0,
addInRange0: false,
addInRange1: false,
endHeightInsertionHint0: knownHeight0,
endHeightInsertionHint1: 0,
maxStartHeight0: currentHeight0 + 10,
maxStartHeight1: 0
});
LiquidityModificationParams memory params = LiquidityModificationParams({
liquidityHook: liquidityHook,
poolId: poolId,
minLiquidityAmount0: 100e18,
minLiquidityAmount1: 0,
maxLiquidityAmount1: 110e18,
maxLiquidityAmount1: 0,
maxHookFee0: 1e17,
maxHookFee1: 0,
poolParams: abi.encode(fixedParams)
});
lbamm.addLiquidity(params, hooksExtraData);
Collecting Fees
Fees may be collected through the LBAMM core collectFees function without modifying a liquidity position. Since positions in the fixed pool are determined by the provider and liquidity hook there are no pool parameters required for determining the position and poolParams in the LiquidityCollectFeesParams struct may be left as empty bytes.
Fees accrue while the current height of the token where the provider has supplied liquidity moves between the position's start and end height.
Collection:
- Updates fee checkpoints
- Reduces pool fee balances
- Transfers fees to provider
Example Collect Fees
LiquidityCollectFeesParams memory params = LiquidityCollectFeesParams({
liquidityHook: liquidityHook,
poolId: poolId,
maxHookFee0: 1e17,
maxHookFee1: 0,
poolParams: bytes("")
});
lbamm.collectFees(params, hooksExtraData);
Removing Liquidity
Liquidity is removed from a fixed pool through the removeLiquidity function on LBAMM core. Liquidity may be partially or fully removed from a position.
When liquidity is to be partially removed the LiquidityModificationParams.poolParams value is set to ABI encoded bytes of FixedLiquidityWithdrawalParams with withdrawAll set to false and params set to ABI encoded bytes of FixedLiquidityModificationParams. The amount0 and amount1 fields are the intended withdrawal amounts and the remaining fields are populated in the same manner as adding liquidity. Fixed Pool will compute the value of the position, deduct the intended withdrawal amounts and apply deposit logic to the remaining amounts. If there is any value that cannot be redeposited the withdraw amounts will increase over intended.
When liquidity is to be fully removed the LiquidityModificationParams.poolParams value is set to ABI encoded bytes of FixedLiquidityWithdrawalParams with withdrawAll set to true and params set to ABI encoded bytes of FixedLiquidityWithdrawAllParams with minAmount0 and minAmount1 providing front-run protection against swaps that change the expected withdrawal amount beyond provider intent without confirmation.
struct FixedLiquidityWithdrawalParams {
bool withdrawAll;
bytes params;
}
Example Remove Liquidity (Partial)
FixedLiquidityModificationParams memory fixedParams = FixedLiquidityModificationParams({
amount0: 100e18,
amount1: 0,
addInRange0: false,
addInRange1: false,
endHeightInsertionHint0: 20,
endHeightInsertionHint1: 0,
maxStartHeight0: currentHeight0 + 10,
maxStartHeight1: 0
});
FixedLiquidityWithdrawalParams memory withdrawParams = FixedLiquidityWithdrawalParams({
withdrawAll: false,
params: abi.encode(fixedParams)
});
LiquidityModificationParams memory params = LiquidityModificationParams({
liquidityHook: liquidityHook,
poolId: poolId,
minLiquidityAmount0: 100e18,
minLiquidityAmount1: 0,
maxLiquidityAmount1: 110e18,
maxLiquidityAmount1: 0,
maxHookFee0: 1e17,
maxHookFee1: 0,
poolParams: abi.encode(withdrawParams)
});
lbamm.removeLiquidity(params, hooksExtraData);
Example Remove Liquidity (Full)
FixedLiquidityWithdrawAllParams memory fixedParams = FixedLiquidityWithdrawAllParams({
minAmount0: 100e18,
minAmount1: 0
});
FixedLiquidityWithdrawalParams memory withdrawParams = FixedLiquidityWithdrawalParams({
withdrawAll: true,
params: abi.encode(fixedParams)
});
LiquidityModificationParams memory params = LiquidityModificationParams({
liquidityHook: liquidityHook,
poolId: poolId,
minLiquidityAmount0: 100e18,
minLiquidityAmount1: 0,
maxLiquidityAmount1: 110e18,
maxLiquidityAmount1: 0,
maxHookFee0: 1e17,
maxHookFee1: 0,
poolParams: abi.encode(withdrawParams)
});
lbamm.removeLiquidity(params, hooksExtraData);
Swap Behavior
Swaps execute at fixed ratio. Height moves instead of price.
Input-Based Swap
- Consumes up to
amountIn - May partially fill if insufficient output reserves
- Returns actual input consumed
Output-Based Swap
- Requests exact output
- Input rounded up to meet ratio
- May generate dust
Example (4:1 pool):
- Request 9 token1
- Requires 3 token0
- 3 token0 produces 12 token1
- 3 token1 becomes dust
Dust is internally accounted.
Swapping near liquidity exhaustion may revert.
Quoter
Fixed pools expose a companion FixedPoolQuoter contract that provides read-only calculations over the current FixedPoolType state (position ranges, current heights, fee growth checkpoints, etc.) without requiring the pool type itself to surface every convenience view.
The quoter uses a static delegatecall pattern to execute logic against the FixedPoolType storage layout while preventing state mutation. This keeps the pool type focused on execution logic while still enabling rich UX and integration tooling.
Quote the value required for an in-range add
When adding liquidity “in range”, the pool may require supplying some amount of the opposite token to cover the depth between the current height and the next spacing boundary. This helper returns the approximate amounts needed to perform an in-range add at the pool’s current height.
(uint256 amount0, uint256 amount1) =
fixedPoolQuoter.quoteValueRequiredForInRangeAdd(poolId);
amount0: token0 required to add token1 liquidity in-rangeamount1: token1 required to add token0 liquidity in-range
This is primarily a UX / routing primitive. It allows an integrator to:
- determine whether the user already holds enough of the opposite token
- pre-calculate required swap amounts before liquidity provisioning
- preview capital efficiency before submitting a transaction
Quote position principal value and accrued fees
This helper computes both principal value and fees for a given position, mirroring the pool type’s internal valuation logic without modifying fee checkpoints.
(uint256 value0, uint256 value1, uint256 fee0, uint256 fee1) =
fixedPoolQuoter.quotePositionValue(poolId, positionId);
value0,value1: current principal value attributed to the position (token0/token1)fee0,fee1: currently accrued, uncollected fees (token0/token1)
This is the primary primitive for:
- position dashboards and portfolio valuation
- estimating withdraw or collect outcomes before submitting a transaction
- monitoring liquidity health and behavior near exhaustion boundaries
- building analytics around height traversal and fee distribution
Events & Indexing
Fixed pools emit:
event FixedPoolPositionUpdated(
bytes32 indexed poolId,
bytes32 indexed positionId,
uint256 startHeight0,
uint256 endHeight0,
uint256 startHeight1,
uint256 endHeight1
);
event FixedPoolHeightConsumed(
bytes32 indexed poolId,
bool indexed zeroForOne,
uint256 currentHeight0,
uint256 currentHeight1
);
Off-Chain Height Reconstruction
To maintain height state off-chain:
- Track
FixedPoolPositionUpdated - Maintain active height intervals
- Track
FixedPoolHeightConsumed - Update current height
- Derive in-range liquidity
Insertion hints should reference known active heights from this reconstructed state.
