Controls

Controls is the control-flow and access-guard module of the SwapVM instruction set. Unlike the pricing modules, these instructions never compute a swap amount. They redirect execution or revert based on on-chain state checks. Controls ships as part of AquaOpcodes on the deployed Aqua router (AquaSwapVMRouter), and SwapVM, the Aqua swap engine, runs it inline while a taker fills a strategy.

Source: src/instructions/Controls.sol


Control flow

_salt

Solidity
1
function _salt(Context memory /* ctx */, bytes calldata /* args */) internal pure

No-op. Accepts any bytes as args. Used to make two otherwise identical programs produce different order hashes.


_jump

Solidity
1
function _jump(Context memory ctx, bytes calldata args) internal pure

Unconditional jump to a program counter position.

Field Offset Size Description
nextPC 0 2 bytes (uint16) Target instruction index (0–65535)

Build with ControlsArgsBuilder.buildJump(uint16 nextPC).

Errors: JumpMissingNextPCArg() if args too short.


_jumpIfTokenIn

Solidity
1
function _jumpIfTokenIn(Context memory ctx, bytes calldata args) internal pure

Jumps if ctx.query.tokenIn equals the specified token address. Otherwise falls through.

Field Offset Size Description
token 0 20 bytes (address) Token to match against tokenIn
nextPC 20 2 bytes (uint16) Jump target

Build with ControlsArgsBuilder.buildJumpIfToken(address token, uint16 nextPC).


_jumpIfTokenOut

Solidity
1
function _jumpIfTokenOut(Context memory ctx, bytes calldata args) internal pure

Jumps if ctx.query.tokenOut equals the specified token address. Otherwise falls through.

Same args layout as _jumpIfTokenIn.


Access and validity guards

These instructions revert a swap based on transaction validity or on what the taker holds. They constrain takers only; supplying liquidity by shipping a strategy stays permissionless. Each guard is a per-strategy instruction evaluated at swap time, not a protocol-wide switch.

_deadline

Solidity
1
function _deadline(Context memory ctx, bytes calldata args) internal view

Reverts if block.timestamp > deadline.

Field Offset Size Description
deadline 0 5 bytes (uint40) Expiry timestamp (Unix seconds)

Build with ControlsArgsBuilder.buildDeadline(uint40 deadline).

Errors: DeadlineReached(taker, deadline).


_onlyTakerTokenBalanceNonZero

Solidity
1
function _onlyTakerTokenBalanceNonZero(Context memory ctx, bytes calldata args) internal view

Reverts if IERC20(token).balanceOf(taker) == 0. Natively supports NFTs (ERC-721 balance ≥ 1 check).

Field Offset Size Description
token 0 20 bytes (address) ERC-20 or ERC-721 token to check

Build with ControlsArgsBuilder.buildTakerTokenBalanceNonZero(address token).

Errors: TakerTokenBalanceIsZero(taker, token).


_onlyTakerTokenBalanceGte

Solidity
1
function _onlyTakerTokenBalanceGte(Context memory ctx, bytes calldata args) internal view

Reverts if IERC20(token).balanceOf(taker) < minAmount.

Field Offset Size Description
token 0 20 bytes (address) Token to check
minAmount 20 32 bytes (uint256) Minimum required balance

Build with ControlsArgsBuilder.buildTakerTokenBalanceGte(address token, uint256 minAmount).

Errors: TakerTokenBalanceIsLessThanRequired(taker, token, balance, minAmount).


_onlyTakerTokenSupplyShareGte

Solidity
1
function _onlyTakerTokenSupplyShareGte(Context memory ctx, bytes calldata args) internal view

Reverts if taker holds less than minShareE18 of the total token supply.

Check: balance × 1e18 >= minShareE18 × totalSupply

Field Offset Size Description
token 0 20 bytes (address) Token to check
minShareE18 20 8 bytes (uint64) Minimum share in 1e18 scale (e.g., 1e16 = 1%)

Build with ControlsArgsBuilder.buildTakerTokenSupplyShareGte(address token, uint64 minShareE18).

Errors: TakerTokenBalanceSupplyShareIsLessThanRequired(taker, token, balance, totalSupply, minShareE18).


_onlyTxOriginTokenBalanceNonZero

Deployed taker access gate. This is the guard the Aqua dApp actually attaches to every strategy at launch. Its default-deny behavior is what gates takers; makers stay permissionless, and the gate is evaluated at swap time.

Solidity
1
function _onlyTxOriginTokenBalanceNonZero(Context memory ctx, bytes calldata args) internal view

Reverts unless IERC20(token).balanceOf(tx.origin) > 0. Like _onlyTakerTokenBalanceNonZero, it works with ERC-721 credentials (balance ≥ 1). The difference is the account it checks: this guard reads tx.origin, the externally owned account that started the transaction, rather than the resolved taker.

Field Offset Size Description
token 0 20 bytes (address) Access-credential token to check (the per-chain KycNFT at launch)

The Aqua frontend assembler wires this guard onto every strategy through the SwapVM SDK helper withTxOriginAccessToken(aquaKycToken) (from @1inch/swap-vm-sdk), pointing at the per-chain KycNFT access credential.

Errors: TxOriginTokenBalanceIsZero.

Because the check is on tx.origin, only a swap sent directly from an EOA that holds the credential passes. Smart-contract wallets, multisigs, and ERC-4337 bundlers cannot satisfy it.


  • Extruction — delegate pricing or control flow to an external contract via IExtruction
  • Invalidators — nonce and remaining-amount order invalidation. Part of the limit-order protocol opcode set on a separate router, not the Aqua router.

Did you find what you need?