Constant product

The constant product AMM is the classic x × y = k pool: a bidirectional, stateful strategy where takers can swap in either direction and reserves persist across swaps in SwapVM storage. It is the Aqua analog of Uniswap V2, though the internal math and fee composition differ.

This is the xyc strategy type. It shares a single on-chain engine, the AquaSwapVMRouter, with the other archetypes (concentrated and pegged); the strategy type is selected by the instruction program, not by a separate contract.


Core instructions

Instruction Role
_dynamicBalancesXD Load reserves from storage; persist updated reserves after the swap
_xycSwapXD Compute x × y = k pricing

Program

Solidity
1
2
3
4
5
6
7
8
Program memory program = ProgramBuilder.init(_opcodes());
bytes memory bytecode = bytes.concat(
    program.build(_dynamicBalancesXD, BalancesArgsBuilder.build(
        dynamic([tokenA, tokenB]),
        dynamic([uint256(1_000e18), uint256(1_000e18)])
    )),
    program.build(_xycSwapXD)
);

Initial balanceIn / balanceOut values seed the on-chain reserves. After the first swap, storage is updated; subsequent calls load the updated state.


Pricing

Exact in: amountOut = ⌊amountIn × balanceOut / (balanceIn + amountIn)⌋

Exact out: amountIn = ⌈amountOut × balanceIn / (balanceOut − amountOut)⌉

Rounding always favors the maker: output floors, input ceilings.


Liquidity paths

The same program bytecode works across both liquidity paths; only MakerTraits changes.

Path useAquaInsteadOfSignature Balance source
Signature + Dynamic false SwapVM storage per orderHash
Aqua-backed true Aqua shared liquidity layer

In the Aqua-backed path the maker's tokens stay in their own wallet under a revocable, per-chain, per-token allowance and move only when a taker fills atomically. The protocol holds no tokens; the shared liquidity layer tracks virtual balances as an internal counter in Aqua.sol. Smart-contract and approval risk still apply.


Optional modifiers

Add any of these without changing _xycSwapXD. Placement relative to the swap opcode matters, so follow the order shown.

Goal Add
Swap fee (LP) _flatFeeAmountInXD before _xycSwapXD
Protocol fee (ERC-20) _protocolFeeAmountInXD before _xycSwapXD
MEV protection _decayXD before the fee, before _xycSwapXD
Expiry _deadline before balance setup

In the v1 MVP only the LP swap fee is non-zero. The _protocolFeeAmountInXD opcode exists in the contracts but the protocol fee is set to 0 and is not displayed.


Invariants to verify

  • Exact in/out symmetryexactIn(X) → Y implies exactOut(Y) → X within rounding.

  • Monotonicity — larger swaps get equal or worse price.

  • Strategy liveness — when one reserve is depleted, reverse swaps restore it.

  • Quote/swap consistencyquote() and swap() return identical amounts.


  • XYCSwap — instruction args and formula

  • Balances — dynamic balance args

  • Concentrated Liquidity — bounded price range variant

  • Decay AMM — same curve with MEV protection

Did you find what you need?