Fee

Fee instructions are applied to amountIn. All are wrapping instructions: they call ctx.runLoop() internally to execute the nested swap formula, then adjust amounts around the result.

Source: src/instructions/Fee.sol (part of the SwapVM instruction set on the Aqua router).

Fee scale: BPS = 1e9 (1e9 = 100%). A fee of 3000000 (3e6) = 0.3%. In the app, the swap fee is displayed to two decimals (for example, 0.30%).

Fees in v1: only the liquidity-provider fee (shown in the app as the "Swap fee") is non-zero. The protocol fee instructions on this page (_protocolFeeAmountInXD, _aquaProtocolFeeAmountInXD, _dynamicProtocolFeeAmountInXD, and _aquaDynamicProtocolFeeAmountInXD) exist in the contracts but are configured to 0 and are not surfaced in the UI. There is no on-chain fee event: fees are encoded as instructions in the strategy program and are reflected directly in the swap amounts. The LP fee auto-compounds into the maker's Aqua balance and is realized through the Pushed event. Tokens stay in the maker's wallet (or in the maker's Aqua virtual balance, an internal counter in Aqua.sol) until a taker fills; the protocol custodies none.


Constraint shared by all instructions

Must be placed before swap amounts are computed. That is, both amountIn and amountOut must be zero when this instruction executes. Violating this reverts with FeeShouldBeAppliedBeforeSwapAmountsComputation.

Quote/swap divergence: All instructions compute fee amounts in quote mode but skip the actual token transfer or Aqua pull. A quote may succeed while the swap reverts due to insufficient maker balance or missing ERC-20 approval. Do not use backward jumps to any of these instructions.


Instructions

_flatFeeAmountInXD

Solidity
1
function _flatFeeAmountInXD(Context memory ctx, bytes calldata args) internal

Applies a flat proportional fee on amountIn. There is no token transfer; it adjusts amounts only. This is the instruction behind the non-zero LP swap fee.

Field Offset Size Description
feeBps 0 4 bytes (uint32) Fee rate; max 1e9
Mode Behavior
exactIn Reduces amountIn by ⌈amountIn × feeBps / BPS⌉ before passing to swap formula; restores original amountIn after
exactOut Passes full amountIn to swap formula; adds ⌈amountIn × feeBps / (BPS − feeBps)⌉ after

_protocolFeeAmountInXD

Solidity
1
function _protocolFeeAmountInXD(Context memory ctx, bytes calldata args) internal

Flat fee on amountIn with a live ERC-20 transfer from maker to a recipient.

Field Offset Size Description
feeBps 0 4 bytes (uint32) Fee rate; max 1e9
to 4 20 bytes (address) Fee recipient

The transfer (safeTransferFrom(maker, to, feeAmount)) occurs during program execution, before SwapVM's final taker-to-maker tokenIn transfer. The maker must hold sufficient tokenIn balance and have granted ERC-20 approval before swap execution.


_aquaProtocolFeeAmountInXD

Solidity
1
function _aquaProtocolFeeAmountInXD(Context memory ctx, bytes calldata args) internal

Same as _protocolFeeAmountInXD but pulls fee from the maker's Aqua virtual balance via IAqua.pull(maker, orderHash, tokenIn, feeAmount, to) instead of a direct ERC-20 transfer. Increments ctx.swap.amountNetPulled by the fee.

Field Offset Size Description
feeBps 0 4 bytes (uint32) Fee rate; max 1e9
to 4 20 bytes (address) Fee recipient

_dynamicProtocolFeeAmountInXD

Solidity
1
function _dynamicProtocolFeeAmountInXD(Context memory ctx, bytes calldata args) internal

Fee rate and recipient are determined at execution time by calling IProtocolFeeProvider.getFeeBpsAndRecipient(...) on an external contract via staticcall. Transfers fee via safeTransferFrom.

Field Offset Size Description
feeProvider 0 20 bytes (address) Must implement IProtocolFeeProvider; address(0) skips the fee

Security: Uses staticcall, so the fee provider cannot mutate state. Protected by TransientLock at the orderHash level. A malicious provider can return large data causing high gas consumption; takers should verify provider trustworthiness.

Errors: FeeDynamicProtocolInvalidRecipient() if feeBps != 0 but to == address(0). FeeProtocolProviderFailedCall() if staticcall fails or returns unexpected data. FeeBpsOutOfRange(feeBps) if returned fee exceeds BPS.


_aquaDynamicProtocolFeeAmountInXD

Solidity
1
function _aquaDynamicProtocolFeeAmountInXD(Context memory ctx, bytes calldata args) internal

Same as _dynamicProtocolFeeAmountInXD but uses IAqua.pull for the transfer instead of safeTransferFrom.

Field Offset Size Description
feeProvider 0 20 bytes (address) Must implement IProtocolFeeProvider

  • FeeExperimental: experimental fee variants applied to amountOut. Not part of the shipped v1 opcode set.
  • XYCSwap: a typical swap formula nested inside fee instructions.
  • LimitSwap: another common nested swap formula.

Did you find what you need?