Skip to main content
Pre-Release Documentation

This describes a pre-release version of LBAMM. Interfaces and behavior may change.

Verify the exact repository commit before building production integrations.

Adding & Removing Liquidity

This page is the canonical integrator reference for LBAMM liquidity operations:

  • addLiquidity
  • removeLiquidity
  • collectFees
  • collectTokensOwed (settling “owed” amounts / transfer debt)

It is pool-type-agnostic: all pool types share the same core flow, while poolParams is interpreted by the pool type contract.


Mental model

A liquidity operation is:

  1. Pool-type computation (how much liquidity changes + what fees are collected)
  2. Hook validation + hook fees (token0/token1/position/pool)
  3. Core accounting updates (reserves / fee balances)
  4. Net settlement (collect from provider or pay to provider)
  5. (Optional) queued hook-fee transfers executed after the operation

Two important consequences for integrators:

  • Hook fees change what the provider pays/receives. You must bound them.
  • Outbound transfers can become “owed” instead of reverting. Providers may need to claim later.

Core structs

Hook calldata bundle

Modify liquidity params

struct LiquidityModificationParams {
address liquidityHook;
bytes32 poolId;

// Bounds on the liquidity-change amounts (exclusive of hook fees).
uint256 minLiquidityAmount0;
uint256 minLiquidityAmount1;
uint256 maxLiquidityAmount0;
uint256 maxLiquidityAmount1;

// Bounds on hook fees (token-denominated).
uint256 maxHookFee0;
uint256 maxHookFee1;

// Pool-type-specific bytes.
bytes poolParams;
}

Key semantics (integrator-critical):

  • minLiquidityAmount* / maxLiquidityAmount* are checked against the raw pool-type amounts (deposit* / withdraw*) exclusive of hook fees.
  • Hook fees are bounded separately by maxHookFee0 / maxHookFee1.
  • liquidityHook is part of position identity (see below).

Collect fees params

struct LiquidityCollectFeesParams {
address liquidityHook;
bytes32 poolId;

uint256 maxHookFee0;
uint256 maxHookFee1;

bytes poolParams;
}

Liquidity operations include per-hook calldata:

struct LiquidityHooksExtraData {
bytes token0Hook;
bytes token1Hook;
bytes liquidityHook;
bytes poolHook;
}

These fields are opaque to LBAMM core. They are forwarded to the configured hooks (if any).


LiquidityContext (what hooks see)

struct LiquidityContext {
address provider;
address token0;
address token1;
bytes32 positionId;
}

Position identity and liquidityHook binding

LBAMM derives a base position identity that includes:

  • provider (the caller for these functions)
  • liquidityHook (position hook address)
  • poolId

If you call removeLiquidity / collectFees with a different liquidityHook than the one used when adding liquidity, you will not resolve the same position.

Integrator rule:

Treat liquidityHook as part of the position key. Persist it and reuse it for future operations.


API surface

addLiquidity

function addLiquidity(
LiquidityModificationParams calldata liquidityParams,
LiquidityHooksExtraData calldata liquidityHooksExtraData
) external payable returns (
uint256 deposit0,
uint256 deposit1,
uint256 fees0,
uint256 fees1
);

Returns:

  • deposit0, deposit1: amounts deposited into pool reserves
  • fees0, fees1: fee amounts collected from pool fee balances during this operation

removeLiquidity

function removeLiquidity(
LiquidityModificationParams calldata liquidityParams,
LiquidityHooksExtraData calldata liquidityHooksExtraData
) external returns (
uint256 withdraw0,
uint256 withdraw1,
uint256 fees0,
uint256 fees1
);

Returns:

  • withdraw0, withdraw1: amounts withdrawn from pool reserves
  • fees0, fees1: fee amounts collected from pool fee balances during this operation

collectFees

function collectFees(
LiquidityCollectFeesParams calldata liquidityParams,
LiquidityHooksExtraData calldata liquidityHooksExtraData
) external returns (uint256 fees0, uint256 fees1);

Returns:

  • fees0, fees1: fee amounts collected from pool fee balances and distributed (or recorded as owed)

Hook participation and hook fees

Liquidity operations may involve up to four hook calls:

  • token0 hook
  • token1 hook
  • position liquidity hook (liquidityHook)
  • pool hook

A hook may return hookFee0 and/or hookFee1.

Integrator-impact:

  • On addLiquidity, hook fees can increase the net tokens collected from the provider.
  • On removeLiquidity and collectFees, hook fees can reduce the net tokens paid out to the provider (or increase what must be returned).

LBAMM enforces fee bounds:

if (hookFee0 > liquidityParams.maxHookFee0 || hookFee1 > liquidityParams.maxHookFee1) {
revert LBAMM__ExcessiveHookFees();
}

Integrator rule:

Always set maxHookFee0/maxHookFee1 to values you are willing to pay. Assume hook fees may be nonzero.


Step-by-step execution semantics

This section describes what LBAMM core does, independent of pool type.

addLiquidity flow (pool-type agnostic)

  1. Validate pool exists.
  2. Call pool type:
    • returns (positionId, deposit0, deposit1, fees0, fees1)
  3. Enforce amount bounds (exclusive of hook fees):
    • deposit* must be within [minLiquidityAmount*, maxLiquidityAmount*]
  4. Execute liquidity hooks (token0/token1/position/pool) and obtain hookFee0/hookFee1
  5. Enforce hook fee bounds (maxHookFee*)
  6. Update pool state:
    • reserves increase by deposit*
    • fee balances decrease by fees*
  7. Settle net amounts with provider:
netAmount0 = deposit0 - fees0 + hookFee0
netAmount1 = deposit1 - fees1 + hookFee1
  1. Emit LiquidityAdded(poolId, provider, deposit0, deposit1, fees0, fees1)
  2. Execute any queued “hook fees by hook” transfers (if hooks requested them)

removeLiquidity flow (pool-type agnostic)

  1. Validate pool exists.
  2. Call pool type:
    • returns (positionId, withdraw0, withdraw1, fees0, fees1)
  3. Enforce amount bounds (exclusive of hook fees):
    • withdraw* must be within [minLiquidityAmount*, maxLiquidityAmount*]
  4. Execute liquidity hooks and obtain hookFee0/hookFee1
  5. Enforce hook fee bounds (maxHookFee*)
  6. Update pool state:
    • reserves decrease by withdraw*
    • fee balances decrease by fees*
  7. Settle net amounts with provider:
netAmount0 = -(withdraw0 + fees0) + hookFee0
netAmount1 = -(withdraw1 + fees1) + hookFee1
  1. Emit LiquidityRemoved(poolId, provider, withdraw0, withdraw1, fees0, fees1)
  2. Execute queued hook-fee transfers (if any)

Note the sign convention: negative net amounts indicate payout to provider.


collectFees flow (pool-type agnostic)

  1. Validate pool exists.
  2. Call pool type:
    • returns (positionId, fees0, fees1)
  3. Execute liquidity hooks and obtain hookFee0/hookFee1
  4. Enforce hook fee bounds (maxHookFee*)
  5. Update pool state:
    • fee balances decrease by fees*
  6. Settle net amounts with provider:
netAmount0 = -(fees0) + hookFee0
netAmount1 = -(fees1) + hookFee1
  1. Emit FeesCollected(poolId, provider, fees0, fees1)
  2. Execute queued hook-fee transfers (if any)

Net settlement, native value, and “owed tokens” (debt)

LBAMM settles liquidity operations via an internal helper that uses a clear sign convention:

  • netAmount > 0collect tokens from provider
  • netAmount < 0distribute tokens to provider

Wrapped native support (msg.value)

If msg.value > 0 and the relevant token is wrappedNative, the AMM can deposit wrapped native on behalf of the provider.

If native value is sent but not used:

revert LBAMM__ValueNotUsed();

Failed outbound transfers become “owed”

When distributing ERC-20 tokens to the provider (i.e., netAmount < 0 and token is not wrapped native), LBAMM attempts a transfer:

  • If the transfer fails, LBAMM stores the amount as tokens owed instead of reverting.

This means integrators must handle a real possibility:

A successful liquidity operation may not immediately pay out all tokens. Some amounts may be claimable later.

Claiming owed tokens

Providers can claim owed balances:

function collectTokensOwed(address[] calldata tokensOwed) external;

Successful claims emit:

event TokensClaimed(address owedTo, address tokenOwed, uint256 amount);

If an owed transfer fails during claim, the claim reverts:

revert LBAMM__TokenOwedTransferFailed();

Events

Core emits:

event LiquidityAdded(bytes32 indexed poolId, address indexed provider, uint256 amount0, uint256 amount1, uint256 fees0, uint256 fees1);
event LiquidityRemoved(bytes32 indexed poolId, address indexed provider, uint256 amount0, uint256 amount1, uint256 fees0, uint256 fees1);
event FeesCollected(bytes32 indexed poolId, address indexed provider, uint256 fees0, uint256 fees1);

Pool types may emit additional pool-specific events.


Integration snippets

These are generic examples. Replace poolParams with pool-type-specific bytes.

Add liquidity (minimal, pool-type-agnostic)

LiquidityModificationParams memory p = LiquidityModificationParams({
liquidityHook: positionHook, // address(0) if none
poolId: poolId,

// Bound the raw deposit amounts returned by the pool type:
minLiquidityAmount0: min0,
minLiquidityAmount1: min1,
maxLiquidityAmount0: max0,
maxLiquidityAmount1: max1,

// Bound hook fees you are willing to pay:
maxHookFee0: maxHookFee0,
maxHookFee1: maxHookFee1,

// Pool-type-defined:
poolParams: poolParams
});

LiquidityHooksExtraData memory hx = LiquidityHooksExtraData({
token0Hook: token0HookData,
token1Hook: token1HookData,
liquidityHook: positionHookData,
poolHook: poolHookData
});

// Approvals (if token is not wrapped native)
IERC20(token0).approve(address(amm), expectedUpperBound0);
IERC20(token1).approve(address(amm), expectedUpperBound1);

// If token0 or token1 is wrappedNative, you may supply msg.value as needed.
(uint256 deposit0, uint256 deposit1, uint256 fees0, uint256 fees1) = amm.addLiquidity{value: msgValue}(p, hx);

Integrator notes:

  • Persist liquidityHook you used. It is required to resolve the same position later.
  • If token transfers to the provider fail (not on add, but on remove/collect), the provider may need to call collectTokensOwed.

Remove liquidity + collect fees

// IMPORTANT: liquidityHook must match the position’s identity.
LiquidityModificationParams memory rp = LiquidityModificationParams({
liquidityHook: positionHook,
poolId: poolId,

// Bound the raw withdraw amounts returned by the pool type:
minLiquidityAmount0: minOut0,
minLiquidityAmount1: minOut1,
maxLiquidityAmount0: maxOut0,
maxLiquidityAmount1: maxOut1,

// Bound hook fees charged during removal:
maxHookFee0: maxHookFee0,
maxHookFee1: maxHookFee1,

poolParams: poolParams
});

(uint256 w0, uint256 w1, uint256 f0, uint256 f1) = amm.removeLiquidity(rp, hx);

// Collect fees (may be separate or periodic)
LiquidityCollectFeesParams memory cp = LiquidityCollectFeesParams({
liquidityHook: positionHook,
poolId: poolId,
maxHookFee0: maxHookFee0,
maxHookFee1: maxHookFee1,
poolParams: poolParams
});

(uint256 cf0, uint256 cf1) = amm.collectFees(cp, hx);

// If any outbound transfers failed and were recorded as owed:
address;
owed[0] = token0;
owed[1] = token1;
amm.collectTokensOwed(owed);

Common failure modes (what can revert and why)

Even before the protocol is live, integrators should assume these classes of failures:

  • Pool existence: pool not created / invalid poolId
  • Pool type call reverts: invalid poolParams, pool-type-specific invariants fail
  • Bounds violated:
    • raw deposit*/withdraw* outside [minLiquidityAmount*, maxLiquidityAmount*]
    • hook fees exceed maxHookFee*LBAMM__ExcessiveHookFees()
  • Hook reverts: token/position/pool hook validation fails
  • Native value misuse: msg.value > 0 but wrapped native not used → LBAMM__ValueNotUsed()
  • State accounting issues: reserve/fee balance underflow/overflow checks; debt storage overflow guards
  • Claiming owed tokens: owed transfer fails on claim → LBAMM__TokenOwedTransferFailed()

Key takeaways

  • Liquidity ops are pool-type-defined, but settlement and enforcement are standardized.
  • Hook fees can materially change provider economics. Always bound with maxHookFee0/maxHookFee1.
  • Some outbound payments may become tokens owed rather than revert; providers may need to call collectTokensOwed.
  • liquidityHook is part of the position identity — persist and reuse it.

Limit Break

TwitterLimitBreak.comMedium

© 2026 Limit Break International, Inc. All rights reserved.

Privacy PolicyTerms of ServiceCookie PolicyDo Not Sell My Info