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.

Pool Swaps

A pool swap is the canonical taker order in LBAMM.

When a user calls singleSwap or multiSwap, they are submitting a taker order that matches against:

  • Continuous maker liquidity supplied to pools
  • Any implicit liquidity made available through pool invariants

This page explains how pool swaps work from an integrator perspective.


Swap Modes

LBAMM supports two swap modes, encoded by the sign of amountSpecified:

Swap By Input

  • amountSpecified > 0
  • You specify how much tokenIn you are sending.
  • You receive as much tokenOut as execution allows.
  • limitAmount = minimum output required.

Swap By Output

  • amountSpecified < 0
  • You specify how much tokenOut you want.
  • The AMM calculates how much tokenIn is required.
  • limitAmount = maximum input allowed.

The sign convention allows a single struct to represent both swap directions cleanly.


SwapOrder Struct

struct SwapOrder {
uint256 deadline;
address recipient;
int256 amountSpecified; // >0 = swap by input, <0 = swap by output
uint256 minAmountSpecified; // minimum amount in partial fill cases
uint256 limitAmount; // minOut (input mode) or maxIn (output mode)
address tokenIn;
address tokenOut;
}

Field Semantics

  • deadline
    Reverts if block.timestamp > deadline.

  • recipient
    Address that receives the final output tokens.

  • amountSpecified
    Positive → swap by input Negative → swap by output

  • minAmountSpecified
    Minimum acceptable filled amount of the specified token in case of partial fill.

  • limitAmount

    • Input mode → minimum output required.
    • Output mode → maximum input allowed.
  • tokenIn / tokenOut Defines the swap direction.


Fee Structs

struct BPSFeeWithRecipient {
uint16 BPS;
address recipient;
}

struct FlatFeeWithRecipient {
uint256 amount;
address recipient;
}
  • exchangeFee (BPS-based)

    • Applied to swap notional.
    • Typically used by frontends or order books.
  • feeOnTop (flat fee)

    • Additional fixed fee.
    • Not protected inside swap signature semantics.

SwapHooksExtraData

struct SwapHooksExtraData {
bytes tokenInHook;
bytes tokenOutHook;
bytes poolHook;
bytes poolType;
}

Allows passing hook-specific data for:

  • Token hooks
  • Pool hooks
  • Pool-type logic

If unused, pass empty bytes.


singleSwap

function singleSwap(
SwapOrder calldata swapOrder,
bytes32 poolId,
BPSFeeWithRecipient calldata exchangeFee,
FlatFeeWithRecipient calldata feeOnTop,
SwapHooksExtraData calldata swapHooksExtraData,
bytes calldata transferData
) external payable returns (uint256 amountIn, uint256 amountOut);

Executes a swap against a single pool.

Behavior

  • Validates deadline and limits
  • Executes token + pool hooks
  • Delegates pricing and invariant logic to the pool type
  • Collects input tokens (directly or via transfer handler)
  • Transfers output tokens to recipient

multiSwap

function multiSwap(
SwapOrder calldata swapOrder,
bytes32[] calldata poolIds,
BPSFeeWithRecipient calldata exchangeFee,
FlatFeeWithRecipient calldata feeOnTop,
SwapHooksExtraData[] calldata swapHooksExtraDatas,
bytes calldata transferData
) external payable returns (uint256 amountIn, uint256 amountOut);

Executes a multi-hop route across multiple pools.

Each hop’s output becomes the next hop’s input.


Multi-Hop Ordering Rules

The poolIds array must be supplied in execution order.

The correct order depends on swap mode.


Swap By Input

You must start with the pool that accepts tokenIn.

Route flows forward:

tokenIn → poolIds[0]
→ intermediate token
→ poolIds[1]
...
→ final pool
→ tokenOut

Example:

Swapping A → D through B and C:

[A/B pool][B/C pool][C/D pool]

Swap By Output

You must start with the pool that outputs tokenOut.

Route flows backward from the desired output:

tokenOut ← poolIds[0]
← intermediate token
← poolIds[1]
...
← final pool
← tokenIn

The AMM internally computes required inputs backwards across the route.


transferData

Both singleSwap and multiSwap accept:

bytes transferData

This parameter enables custom transfer handlers.

Default Behavior (transferData = empty)

If transferData.length == 0:

  • The caller of the AMM must have approved the AMM for tokenIn.
  • The AMM will directly collect the required input amount using transferFrom.
  • No transfer handler is invoked.

This is the standard ERC-20 approval flow.


Custom Settlement (transferData ≠ empty)

If transferData.length > 0:

  • The first 32 bytes are interpreted as the transferHandler address.
  • The remaining bytes are forwarded to that handler as transferExtraData.
  • The transfer handler is responsible for supplying the required input tokens to the AMM.

This enables advanced settlement flows such as:

  • Permit-based transfers
  • Onchain order book matching
  • Custom escrow or custody logic

If a transfer handler is used, the AMM does not rely on ERC-20 allowance from the caller.


Partial Fills

Partial fills are allowed only on the first hop.

If a pool cannot fully satisfy the specified amount:

  • Execution may partially fill.
  • The filled amount must be ≥ minAmountSpecified.
  • Subsequent hops must execute fully.

This ensures deterministic routing and prevents cascading partial state across hops.


Return Values

Both singleSwap and multiSwap return:

(uint256 amountIn, uint256 amountOut)

These values represent:

  • Total input tokens collected
  • Total output tokens delivered

Fees are reflected in these values.


Solidity Example

SwapOrder memory order = SwapOrder({
deadline: block.timestamp + 300,
recipient: msg.sender,
amountSpecified: int256(1e18), // swap by input
minAmountSpecified: 0,
limitAmount: 0.99e18, // minimum output
tokenIn: address(tokenA),
tokenOut: address(tokenB)
});

(uint256 amountIn, uint256 amountOut) = amm.singleSwap(
order,
poolId,
BPSFeeWithRecipient({BPS: 0, recipient: address(0)}),
FlatFeeWithRecipient({amount: 0, recipient: address(0)}),
SwapHooksExtraData("", "", "", ""),
""
);

viem Example

await publicClient.writeContract({
address: ammAddress,
abi: ammAbi,
functionName: 'singleSwap',
args: [
{
deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
recipient: userAddress,
amountSpecified: 10n ** 18n,
minAmountSpecified: 0n,
limitAmount: 99n * 10n ** 16n,
tokenIn: tokenA,
tokenOut: tokenB,
},
{ BPS: 0, recipient: zeroAddress },
{ amount: 0n, recipient: zeroAddress },
{ tokenInHook: '0x', tokenOutHook: '0x', poolHook: '0x', poolType: '0x' },
'0x'
],
});

Pool swaps represent the most direct taker interaction with LBAMM.
They match against continuous maker liquidity while preserving full extensibility through hooks and transfer handlers.

Limit Break

TwitterLimitBreak.comMedium

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

Privacy PolicyTerms of ServiceCookie PolicyDo Not Sell My Info