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
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 symmetry —
exactIn(X) → YimpliesexactOut(Y) → Xwithin rounding.Monotonicity — larger swaps get equal or worse price.
Strategy liveness — when one reserve is depleted, reverse swaps restore it.
Quote/swap consistency —
quote()andswap()return identical amounts.
Related
XYCSwap — instruction args and formula
Balances — dynamic balance args
Concentrated Liquidity — bounded price range variant
Decay AMM — same curve with MEV protection