This describes a pre-release version of LBAMM. Interfaces and behavior may change.
Verify the exact repository commit before building production integrations.
Executors & Solvers
This page defines the roles of executors, makers, and solvers in LBAMM order flow.
The key invariant to remember:
The executor is always
msg.senderof the AMM call.
All execution semantics — including fee collection and hook visibility — are defined relative to that address.
Core Roles
Executor
The executor is the address that calls the AMM contract.
In all swap functions:
function singleSwap(...) external payable returns (...);
function multiSwap(...) external payable returns (...);
function directSwap(...) external payable returns (...);
The executor is strictly:
msg.sender
The executor:
- Supplies input tokens
- Pays exchange fees and fee-on-top
- Triggers all hook execution
- Is visible to token, pool, and handler logic
The protocol does not track tx.origin or any “true initiator.”
For all protocol-level reasoning, executor = caller.
Maker
The maker is the party whose intent is being executed.
Examples:
- A user who signs a permit authorizing the transfer of tokens
- A user who places an onchain CLOB order
- Any actor whose order defines swap parameters but does not call the AMM directly
The maker does not need to be the executor.
In many advanced flows, the maker and executor are different parties.
Taker
The taker is the counterparty that consumes maker liquidity.
- In a
directSwap, the executor is the taker. - In a pool-based swap filling a maker order, the executor may be:
- the taker directly, or
- a solver/searcher acting on behalf of the taker.
Solver (Searcher)
A solver is a professional executor that:
- Observes maker orders (permits, CLOB orders, etc.)
- Searches for profitable execution paths offchain
- Submits a transaction that matches maker intent
- Captures surplus via fee surfaces such as
feeOnTop
The searching process is strictly offchain.
From the protocol’s perspective, a solver is simply an executor.
Execution Context
SwapOrder
All pool-based swaps use:
struct SwapOrder {
uint256 deadline;
address recipient;
int256 amountSpecified;
uint256 minAmountSpecified;
uint256 limitAmount;
address tokenIn;
address tokenOut;
}
Fee Configuration
Two fee surfaces are available at the swap level.
Exchange Fee (BPS)
struct BPSFeeWithRecipient {
address recipient;
uint256 BPS;
}
Invalid when:
BPS > 10_000BPS > 0 && recipient == address(0)
Semantics:
- Applied to the full input amount required from the executor
- Scales down proportionally on partial fill
- Charged once per
singleSwapormultiSwap - Not charged per hop in multi-hop execution
Fee On Top (Flat)
struct FlatFeeWithRecipient {
address recipient;
uint256 amount;
}
Invalid when:
amount > 0 && recipient == address(0)
Semantics:
- Flat fee taken from executor
- Does not scale down on partial fill
- Charged once per execution
- Not charged per hop
multiSwap Fee Behavior
In multiSwap:
- Exchange fee and fee-on-top are applied once to the total input
- Intermediate hops:
- Do not pay exchange fee or fee-on-top
- May still incur token hook fees per hop
Direct Swaps
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);
In a direct swap:
- The executor supplies output tokens directly
- No AMM pool liquidity is used
- Pool hooks are not invoked
- Token hooks are invoked
The executor is the taker.
Solvers Filling Maker Intent via Pools
A common advanced flow:
- Maker signs a permit:
- Sell 100 TokenA
- Receive 10 TokenB
- A pool exists where:
- 90 TokenA → 10 TokenB
- A solver executes
singleSwapand captures surplus.
Example Flow
Maker intent:
- 100 TokenA → 10 TokenB
Pool pricing:
- 90 TokenA → 10 TokenB
Solver execution:
swapOrder.amountSpecified = 100 TokenAfeeOnTop.amount = 10 TokenAfeeOnTop.recipient = solverrecipient = maker
Execution Outcome
- 100 TokenA collected from executor
- 10 TokenA paid to solver as fee-on-top
- 90 TokenA routed through pool
- Pool returns 10 TokenB
- 10 TokenB sent to maker
Result:
- Maker receives exactly 10 TokenB for 100 TokenA
- Solver captures 10 TokenA
- Pool executes swap for 90 TokenA
Pseudocode (Solver Perspective)
SwapOrder memory order = SwapOrder({
deadline: block.timestamp + 300,
recipient: maker,
amountSpecified: 100e18, // 100 TokenA
minAmountSpecified: 100e18,
limitAmount: 10e18, // must deliver 10 TokenB
tokenIn: TokenA,
tokenOut: TokenB
});
FlatFeeWithRecipient memory feeOnTop = FlatFeeWithRecipient({
recipient: solver,
amount: 10e18 // captured surplus
});
BPSFeeWithRecipient memory exchangeFee = BPSFeeWithRecipient({
recipient: address(0),
BPS: 0
});
amm.singleSwap(
order,
poolId,
exchangeFee,
feeOnTop,
swapHooksExtraData,
permitTransferData
);
Partial Fills
If partial fills occur:
- Exchange fee scales proportionally
- Fee-on-top does not scale down
Executors should ensure their fee configuration remains acceptable given the possibility of partial execution.
Executor Visibility & Hook Interaction
The executor is always msg.sender.
Hooks may inspect and restrict based on:
- Executor address
- Recipient address
- Executor → recipient relationships
- Transfer handler usage
Some tokens may permit only specific executors.
If an integrator inserts a router or aggregator:
- That router becomes the executor
- Hooks see the router, not the end-user
Integrators must design flows accordingly.
Summary
- Executor =
msg.sender - Maker = party whose intent is being executed
- Taker = counterparty consuming maker liquidity
- Solver = professional executor that searches offchain and captures surplus
- Exchange fees scale with fill
- Fee-on-top does not
- multiSwap charges fees once
- Hooks see the executor directly
