The Decay AMM is a constant-product (xyc) automated market maker with a Mooniswap-style virtual-balance decay modifier. You author it as a program for SwapVM (the Aqua swap engine) and register it on the Aqua router (AquaSwapVMRouter). After each swap, _decayXD temporarily widens the spread by inflating balanceIn and deflating balanceOut, then decays back to true reserves over period seconds. This discourages sandwich attacks by making back-runs land at a worse price immediately after the initial swap.
A Decay AMM is a maker strategy on Aqua: liquidity stays in the maker's wallet under a revocable, per-chain, per-token allowance and moves only when a taker fills a swap atomically. The Aqua registry holds no tokens of its own.
Core instructions
A Decay AMM strategy composes three instructions from the SwapVM instruction set:
| Instruction | Role |
|---|---|
_dynamicBalancesXD |
Load reserves; persist after swap |
_decayXD |
Apply virtual offset; call ctx.runLoop() for subsequent instructions |
_xycSwapXD |
Compute x × y = k pricing inside the decay loop |
_decayXD is a wrapping instruction; it calls ctx.runLoop() internally, so the swap formula executes inside its nested loop. Place it before _xycSwapXD.
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(_decayXD, DecayArgsBuilder.build(300)), // 300-second decay period
program.build(_xycSwapXD)
);
Args (from Decay)
| Field | Size | Type | Description |
|---|---|---|---|
period |
2 bytes | uint16 |
Seconds until the virtual offset fully decays to zero |
How decay works
After a swap, the storage records
(amountIn, amountOut, timestamp)of the last swap.On the next quote/swap,
_decayXDcomputes elapsed time since the last swap.If
elapsed < period, it applies a proportional virtual offset:balanceInis inflated andbalanceOutis deflated byoffset × (1 − elapsed/period).The offset is largest immediately after a swap and approaches zero as
elapsed → period.
This means a sandwich attacker's back-run sees a wider spread proportional to how quickly it follows the victim swap.
Program position
_dynamicBalancesXD → _decayXD → [fee] → _xycSwapXD
If adding a fee, place it between _decayXD and _xycSwapXD; both are wrapping instructions and will nest correctly.
_dynamicBalancesXD → _decayXD → _flatFeeAmountInXD → _xycSwapXD
See Modifiers for the full wrapping order example.
Optional modifiers
| Goal | Add |
|---|---|
| Flat fee | _flatFeeAmountInXD between _decayXD and _xycSwapXD |
| Expiry | _deadline before balance setup |
Related
Decay — instruction args and virtual offset math
XYCSwap — constant-product formula
Constant Product — same curve without MEV protection
Modifiers — wrapping instruction ordering rules