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
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
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
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
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
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
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
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
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.
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.
Related
- 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.