This describes a pre-release version of LBAMM. Interfaces and behavior may change.
Verify the exact repository commit before building production integrations.
Submit Signed Orders
This guide shows how to:
- Accept a maker’s signed permit (EIP-712, PermitC)
- Execute it using
PermitTransferHandler - Submit the order via
directSwap
This flow assumes:
- Fill-or-kill permit
- No cosigner
- No custom hook validation
- No fee on top
- Exchange fee included
For handler internals and full permit semantics, see:
- Reference → PermitTransferHandler
- Reference → directSwap
Mental Model
In this flow:
- Maker signs a PermitC permit authorizing transfer of
tokenIn. - Executor (taker) submits
directSwap. - AMM:
- Pulls
tokenInfrom maker via PermitTransferHandler. - Pulls
tokenOutfrom executor. - Transfers
tokenOutto maker. - Transfers
tokenInto executor.
- Pulls
directSwap does not compute pricing.
All amounts must be supplied explicitly.
directSwap Entry Point
function directSwap(
SwapOrder calldata swapOrder,
DirectSwapParams calldata directSwapParams,
BPSFeeWithRecipient calldata exchangeFee,
FlatFeeWithRecipient calldata feeOnTop,
SwapHooksExtraData calldata swapHooksExtraData,
bytes calldata transferData
) external payable returns (uint256 amountIn, uint256 amountOut);
Construct SwapOrder
SwapOrder memory order = SwapOrder({
deadline: deadline,
recipient: maker,
amountSpecified: int256(amountIn), // >0 => swap-by-input
minAmountSpecified: 0, // not checked in directSwap
limitAmount: minAmountOut, // validated by permit
tokenIn: tokenIn,
tokenOut: tokenOut
});
Notes:
- Mode is determined by sign of
amountSpecified. minAmountSpecifiedis not enforced indirectSwap.limitAmountis part of the permit signature to ensure minimum amount out to permit signer.
Construct DirectSwapParams
directSwap requires the executor to supply the opposite side explicitly.
Mapping rule:
- If
amountSpecified > 0→swapAmount = amountOut - If
amountSpecified < 0→swapAmount = amountIn
For swap-by-input example:
DirectSwapParams memory params = DirectSwapParams({
swapAmount: amountOut, // executor provides tokenOut
maxAmountOut: maxExecutorPay, // cap executor payment (after fees)
minAmountIn: minExecutorReceive // minimum executor receives (after fees)
});
Definitions:
swapAmount— opposing side of the transaction fromSwapOrder.amountSpecified, ifamountSpecifiedis positive for swap by input thenswapAmountrepresents the transaction output amount, ifamountSpecifiedis negative for swap by output thenswapAmountrepresents the transaction input amount.maxAmountOut— maximum executor payment (post-fee).minAmountIn— minimum executor receipt (post-fee).
Include Exchange Fee
BPSFeeWithRecipient memory exchangeFee = BPSFeeWithRecipient({
recipient: exchangeFeeRecipient,
BPS: 30
});
FlatFeeWithRecipient memory feeOnTop = FlatFeeWithRecipient({
recipient: address(0),
amount: 0
});
- If recipient is zero, fee must be zero.
- Fee ordering semantics are documented in Fees & Economics.
Create Permit (Raw EIP-712 with Swap additional data)
PermitTransferHandler fill-or-kill permits use an EIP-712 payload of the form:
PermitTransferFromWithAdditionalData(
uint256 tokenType,
address token,
uint256 id,
uint256 amount,
uint256 nonce,
address operator,
uint256 expiration,
uint256 masterNonce,
Swap swapData
)
Swap(
bool partialFill,
address recipient,
int256 amountSpecified,
uint256 limitAmount,
address tokenOut,
address exchangeFeeRecipient,
uint16 exchangeFeeBPS,
address cosigner,
address hook
)
What must be bound by the maker signature
For the integration flow in this guide (fill-or-kill, no cosigner, no hook):
swapData.partialFill = falseswapData.recipient = makerswapData.amountSpecified=SwapOrder.amountSpecifiedswapData.limitAmount=SwapOrder.limitAmountswapData.tokenOut=SwapOrder.tokenOutswapData.exchangeFeeRecipient+swapData.exchangeFeeBPSmust match theexchangeFeepassed todirectSwapswapData.cosigner = address(0)swapData.hook = address(0)
TypeScript example: EIP-712 typed data to sign
This is a minimal example showing the nested structs. You must use the PermitC instance as the EIP-712 verifying contract.
const domain = {
name: "PermitC",
version: "1",
chainId: executionChainId,
verifyingContract: permitProcessor, // canonical PermitC instance
};
const types = {
Swap: [
{ name: "partialFill", type: "bool" },
{ name: "recipient", type: "address" },
{ name: "amountSpecified", type: "int256" },
{ name: "limitAmount", type: "uint256" },
{ name: "tokenOut", type: "address" },
{ name: "exchangeFeeRecipient", type: "address" },
{ name: "exchangeFeeBPS", type: "uint16" },
{ name: "cosigner", type: "address" },
{ name: "hook", type: "address" },
],
PermitTransferFromWithAdditionalData: [
{ name: "tokenType", type: "uint256" },
{ name: "token", type: "address" },
{ name: "id", type: "uint256" },
{ name: "amount", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "operator", type: "address" },
{ name: "expiration", type: "uint256" },
{ name: "masterNonce", type: "uint256" },
{ name: "swapData", type: "Swap" },
],
};
const message = {
20, // tokenType ERC20
token: tokenIn,
0, // tokenId is 0 for ERC20
amount: amountIn,
nonce,
operator: permitTransferHandler,
expiration: permitExpiration,
masterNonce,
swapData: {
partialFill: false,
recipient: maker,
amountSpecified: amountSpecified, // int256 (match SwapOrder.amountSpecified)
limitAmount: limitAmount, // match SwapOrder.limitAmount
tokenOut: tokenOut, // match SwapOrder.tokenOut
exchangeFeeRecipient,
exchangeFeeBPS, // uint16
cosigner: zeroAddress,
hook: zeroAddress,
},
};
const signature = await wallet.signTypedData({
domain,
types,
primaryType: "PermitTransferFromWithAdditionalData",
message,
});
Encode transferData
Solidity example:
bytes memory transferData = bytes.concat(
abi.encode(permitTransferHandler), // first 32 bytes
bytes1(0), // discriminator (fill-or-kill)
abi.encode(permitStruct) // ABI-encoded permit
);
For full helper implementations, see PermitTransferHandler reference.
Call directSwap
SwapHooksExtraData memory hookData = SwapHooksExtraData({
tokenInHook: "",
tokenOutHook: "",
poolHook: "",
poolType: ""
});
(uint256 amountIn, uint256 amountOut) =
amm.directSwap(
order,
params,
exchangeFee,
feeOnTop,
hookData,
transferData
);
Return values:
amountIn— totaltokenInmoved (including fees)amountOut— totaltokenOutmoved
Executor Requirements
Executor must:
- Hold sufficient
tokenOut - Approve LBAMM for
tokenOut - Ensure signed permit matches swap parameters
- Ensure
recipient == permit.from
Common failures:
- Signature invalid
- Permit expired
- Nonce reused
- Exchange fee mismatch (if bound in signed intent)
- Executor payment exceeds
maxAmountOut
