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
tokenInyou are sending. - You receive as much
tokenOutas execution allows. limitAmount= minimum output required.
Swap By Output
amountSpecified < 0- You specify how much
tokenOutyou want. - The AMM calculates how much
tokenInis 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 ifblock.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
transferHandleraddress. - 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.
