AquaQoSDocs

Architecture

Official Aqua and SwapVM stay load-bearing. AquaQoS adds one opcode and one restricted vault.

AquaQoS is deliberately small: 47 lines of router and 226 lines of vault on top of unmodified, pinned 1inch code. The official contracts still price, account and settle; AquaQoS decides only whether a fill may proceed.

ComponentOriginResponsibility
AquaOfficial, unmodified, pinned v1.0.0 (81c26e4)Virtual balances; pull and push token transfers
AquaSwapVMRouterOfficial SwapVM, pinned f09a41eProgram execution, settlement order, taker callbacks
AquaQoSRouterAquaQoSSubclasses the official router, handles opcode 0x05 and delegates every other opcode to it
AquaQoSVaultAquaQoSMaker custody plus policy state: guarantees, baselines, reservations and lifecycle

Swap path

One guarded exact-output swap
taker ── swap ──▶ AquaQoSRouter  (official SwapVM settlement)

                    ├─ opcode 0x05  CAPACITY_GUARD
                    │     runs the inner program: XYC, then salt
                    │     requires empty fee state
                    │     quote → vault.checkCapacity(hash, tokenOut, amountOut)   view
                    │     swap  → vault.reserve(hash, tokenOut, amountOut)         transient

                    └─ settlement
                          Aqua.pull   vault ──▶ taker   (output)
                          Aqua.push   taker ──▶ vault   (input)

The guard runs first in the program but checks last: it executes the rest of the program itself, so it sees the final output amount and fee state rather than a value that a later instruction could still change.

contracts/AquaQoSRouter.sol
function _runOpcode(Context memory ctx, uint256 opcode, bytes calldata args) internal override {
    if (opcode != CAPACITY_GUARD) {
        super._runOpcode(ctx, opcode, args);
        return;
    }
    bytes calldata program = ctx.program();
    // 05 00 | 50 00 (XYC) | 02 08 (salt) | uint64 salt.
    require(ctx.vm.nextPC == 2 && args.length == 0 && program.length == 14
        && bytes6(program[:6]) == hex"050050000208", InvalidGuardProgram());
    ctx.runLoop();
    require(ctx.vm.nextPC == program.length, InvalidGuardProgram());
    require(FeeMeta.unwrap(ctx.fee.meta) == 0 && ctx.fee.feeTotal == 0
        && ctx.fee.receivers.length == 0, UnsupportedFees());
    ICapacityVault vault = ICapacityVault(ctx.query.maker);
    if (ctx.vm.isStaticContext) {
        vault.checkCapacity(ctx.query.orderHash, ctx.query.tokenOut, ctx.swap.amountOut);
    } else {
        vault.reserve(ctx.query.orderHash, ctx.query.tokenOut, ctx.swap.amountOut);
    }
}

The canonical program

Every protected strategy runs exactly one 14-byte program, and the vault builds it itself:

BytesInstructionMeaning
05 00CAPACITY_GUARDCustom opcode 0x05 with no arguments; must be first
50 00XYCOfficial constant-product swap, no arguments
02 08 + 8 bytesSaltOfficial no-op carrying a uint64 salt, so strategies get distinct hashes

Opcode 0x05 is the next free slot in the pinned core-extension bank. SwapVM's reserved 0xf00xff bank is untouched. Because the vault ships only programs it built, a protected order cannot omit, duplicate or jump around the guard.

Design choices

Repository map

AquaQoSRouter.sol
AquaQoSVault.sol
sources.lock.json

Verify

On this page