Modifiers

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

  • Instruction set overview: full reference for every instruction.
  • Program Model: ordering rules and invariants.

Did you find what you need?