Modifiers are cross-cutting SwapVM instructions that extend a strategy. SwapVM is the Aqua swap engine; a modifier layers onto an existing program without changing its core swap formula.
Each entry below lists which archetype it applies to, where in the program it goes, and what it changes.
On-chain, these modifiers are opcodes drawn from two different router opcode tables. The Aqua router (AquaSwapVMRouter) inherits Controls, XYCSwap, XYCConcentrate, Decay, Fee, PeggedSwap, Extruction. Fee, decay (MEV), and access-control (Controls) modifiers run on the Aqua router. Rate guards (MinRate), the oracle and base-fee adjusters, and invalidators belong to the Limit Order and Fusion router opcode set, not the Aqua router. They are documented here for completeness and are called out as such.
Modifiers on the Aqua router
Fees
All fee instructions are wrapping: they call ctx.runLoop() internally. Place them before the swap formula. They execute the formula inside the nested loop and adjust the returned amounts.
A fee instruction must be placed before swap amounts are computed. Both amountIn and amountOut must be zero when the fee instruction starts.
In the v1 MVP only the LP fee (surfaced in the app as the "Swap fee", instruction _flatFeeAmountInXD) is non-zero. The protocol-fee instructions exist in the contracts but are set to 0 in v1 and are not displayed. The fee scale is 1e9 and feePercent is a decimal in [0,1] (for example 0.0030 = 0.30%). There is no on-chain fee event; fees are instructions embedded in the program and reflected in swap amounts. LP fees auto-compound to the maker balance via the Pushed event.
| Instruction | Leg | Transfer | When to use |
|---|---|---|---|
_flatFeeAmountInXD |
amountIn |
None | The LP swap fee (the only non-zero fee in v1). Reduces effective input before pricing. |
_flatFeeAmountOutXD |
amountOut |
None | LP fee deducted from the taker's received amount. |
_protocolFeeAmountInXD |
amountIn |
ERC-20 from maker | Protocol revenue on the input leg; requires maker pre-approval. Set to 0 in v1. |
_protocolFeeAmountOutXD |
amountOut |
ERC-20 from maker | Protocol revenue on the output leg. Set to 0 in v1. |
_aquaProtocolFeeAmountInXD |
amountIn |
Aqua pull | Protocol fee on the input leg, settled from Aqua balances. Set to 0 in v1. |
_aquaProtocolFeeAmountOutXD |
amountOut |
Aqua pull | Protocol fee on the output leg, settled from Aqua balances. Set to 0 in v1. |
_dynamicProtocolFeeAmountInXD |
amountIn |
ERC-20 from maker | Fee rate read from an external IProtocolFeeProvider at execution time. Set to 0 in v1. |
_aquaDynamicProtocolFeeAmountInXD |
amountIn |
Aqua pull | Dynamic protocol fee settled via IAqua.pull(). Set to 0 in v1. |
Corresponding Fee.sol builders: buildFlatFee, buildProtocolFee, buildDynamicProtocolFee.
Applies to: all archetypes.
MEV protection (decay)
The Decay opcode adds a post-swap spread that discourages sandwich attacks.
| Instruction | Where in program | What it does |
|---|---|---|
_decayXD |
After _dynamicBalancesXD, before the swap formula |
Temporarily inflates balanceIn and deflates balanceOut after each swap, then decays back over period seconds. Creates a spread that discourages sandwich attacks. |
Applies to: bidirectional AMM strategies (requires dynamic balances).
Program position:
_dynamicBalancesXD → _decayXD → [fee] → swap formula
Access control
Access-control checks are Controls opcodes. Place them at the start of the program, before balance setup or swap logic. Makers are permissionless; only takers are gated. At launch the gate is applied per strategy, not as a protocol-wide switch, and it is checked at swap time.
| Instruction | What it checks | When to use |
|---|---|---|
_deadline |
block.timestamp <= deadline |
All strategies. Bounds maker exposure to stale orders. |
_onlyTxOriginTokenBalanceNonZero |
balanceOf(tx.origin) > 0; reverts TxOriginTokenBalanceIsZero otherwise |
The deployed launch gate. The dApp assembler calls withTxOriginAccessToken(aquaKycToken) on every strategy, gating fills to KycNFT holders. |
_onlyTakerTokenBalanceGte |
Holder balance ≥ minAmount |
Gate fills to holders above a minimum balance. |
_onlyTakerTokenSupplyShareGte |
Holder balance ≥ X% of total supply | Gate fills to large holders. |
note
The launch gate checks tx.origin, so smart accounts, multisigs, and ERC-4337 bundlers cannot pass it. Per-strategy taker allow-lists (_whitelistSingleTaker / _whitelistMultipleTakers) exist in source but are not registered on-chain today, so do not treat them as an available feature.
The launch gate checks tx.origin, so smart accounts, multisigs, and ERC-4337 bundlers cannot pass it. Per-strategy taker allow-lists (_whitelistSingleTaker / _whitelistMultipleTakers) exist in source but are not registered on-chain today, so do not treat them as an available feature.
Applies to: all archetypes.
Modifiers on the Limit Order and Fusion router
The instructions in this section are part of the Limit Order and Fusion router opcode set. They are not in the Aqua router opcode table (Controls, XYCSwap, XYCConcentrate, Decay, Fee, PeggedSwap, Extruction). Several also depend on static balances, which are not available.
Rate guards
Applied after the swap formula (amounts must already be computed).
| Instruction | Behavior | When to use |
|---|---|---|
_requireMinRate1D |
Reverts if amountIn / amountOut < rateIn / rateOut |
Hard floor. Reject the swap. |
_adjustMinRate1D |
Caps amounts to the minimum rate instead of reverting | Soft floor. Adjust and continue. |
Dynamic pricing (post-swap adjusters)
Applied after the swap formula. Both require amountIn > 0 && amountOut > 0.
| Instruction | What it does | When to use |
|---|---|---|
_oraclePriceAdjuster1D |
Improves the taker's rate toward the Chainlink oracle price, capped at maxPriceDecay |
Oracle-anchored limit orders; keep the rate tracking the market. |
_baseFeeAdjuster1D |
Improves the taker's rate when block.basefee exceeds a baseline, proportional to gas cost |
Gas-responsive orders; compensate the taker for high-gas periods. |
Applies to: single-direction strategies with static balances. Both work with 1→0 swaps (token1 → token0). Static balances are not available.
Invalidators
Invalidators (for example _invalidateBit1D) mark an order as used after amounts are known. They belong to the Limit Order and Fusion router opcode set, not the Aqua router.
Combining modifiers
Ordering within a program follows the rules described in Program Model.
note
The single-direction example below is a Limit Order and Fusion router program. It requires static balances, which are not available.
The single-direction example below is a Limit Order and Fusion router program. It requires static balances, which are not available.
A fully composed single-direction limit order with a fee, a gas adjustment, and an access check:
_deadline ← access guard first
_staticBalancesXD ← set rate
_flatFeeAmountInXD ← wrapping: fee before formula
_limitSwap1D ← formula runs inside fee's runLoop
_baseFeeAdjuster1D ← post-swap: adjust for gas cost
_invalidateBit1D ← invalidate after amounts known
An Aqua router 2D AMM with MEV protection and a flat fee:
_dynamicBalancesXD ← load reserves
_decayXD ← wrapping: MEV spread before fee
_flatFeeAmountInXD ← wrapping: fee before formula
_xycSwapXD ← formula runs inside nested loops
Related
- Instruction set overview: full reference for every instruction.
- Program Model: ordering rules and invariants.