The first SwapVM strategy composer: a sentence in, validated bytecode out
AquaPilot is the first strategy composer for the 1inch SwapVM instruction set.
1inch shipped Aqua and SwapVM for developers in November 2025, but there is still no public Aqua frontend and no first-party tool that turns an intent into a SwapVM program. SwapVM is a real instruction VM — limit swaps, AMM curves, concentrated liquidity, Dutch auctions, TWAP, MEV decay and Jump/JumpIfTokenIn control flow — and composing a correct non-trivial program from it is genuine DeFi engineering. AquaPilot fills that gap.
You describe a liquidity strategy in one sentence. A constrained extraction step turns it into typed parameters — never into opcodes. A deterministic, human-authored template assembles those parameters into a multi-leg SwapVM program: static balances, a branch on the swap direction, limit-swap legs, a deadline and a partial-fill invalidator. A validator then checks the program against a rule base derived directly from the protocol's own require statements before a single byte is submitted.
That validator is the point. The SwapVM documentation warns that instruction order is security-critical: the same instructions in a different order change strategy behaviour. So AquaPilot treats ordering as a first-class safety property rather than a footnote. It rejects programs that swap before balances are set, that recompute an already-computed amount, that omit a deadline or invalidator, or that jump backwards into a quote/swap-divergent instruction.
Execution is real. The composed program is shipped to Aqua, filled by genuine counterparty swaps against the SwapVM router, and cancelled or settled — with every number in the lifecycle UI traced back to an on-chain Pushed, Pulled, Shipped or Docked event. Nothing in the interface is simulated.
This is deliberately not an "AI agent for DeFi." A human authors and verifies the strategy; the model only fills typed slots; the validator has the last word.
The hard part is that the Aqua TypeScript SDK does not build SwapVM programs. It exposes Ship/Dock virtual-balance operations and event classes, and its README contains no strategy examples at all. The real composer lives in the Solidity swap-vm repository. So we read the contracts and ported the encoder to TypeScript.
We verified the wire format from both directions: the encoder in test/utils/ProgramBuilder.sol (abi.encodePacked(opcode, uint8(args.length), args)) and the decoder in ContextLib.runLoop, which shifts the opcode out of the first byte and the argument length out of the second. That gives [1 byte opcode][1 byte argsLength][args], a 255-byte cap per instruction, and a 65,535-byte cap on jump-addressable programs because jump targets are uint16. We then derived each instruction's packed argument layout from its ArgsBuilder library and parse function.
Correctness is enforced by a golden-fixture test rather than by inspection: a Foundry script builds reference programs with 1inch's own ProgramBuilder and writes the resulting bytecode to JSON, and our TypeScript encoder must reproduce those bytes exactly. Testing a port against its own reimplementation proves nothing, so the oracle is always the real contract.
Two source findings shaped the architecture. First, Aqua is not an opcode set — it is MakerTraits bit 254, useAquaInsteadOfSignature. AquaSwapVMRouter dispatches neither LimitSwap nor StaticBalances nor the invalidators, so a limit-order ladder is impossible there. We use SwapVMRouter with the Aqua trait enabled, which gives the full instruction set and Aqua-backed settlement at once. Second, neither repository publishes a mainnet deployment; every address parameter is a zero placeholder. So we fork mainnet for real tokens and liquidity and deploy the official, unmodified contracts onto the fork ourselves, from pinned git submodules. No 1inch code is vendored into our repository.
The validator's rule base is mechanically derived from the protocol's own failure modes — SetBalancesExpectZeroBalances, LimitSwapRequiresBothBalancesNonZero, LimitSwapRecomputeDetected, LimitSwapDirectionMismatch, MakerTraitsTokensNotSorted — plus the quote/swap divergence documented on DynamicBalances and the invalidators, which are wrapping instructions that call runLoop internally and therefore must never be jump targets.
Stack: TypeScript monorepo (npm workspaces), Foundry and Anvil for the fork and fixtures, viem for submission and log decoding, Next.js for the lifecycle UI, and constrained structured output for parameter extraction.

