How Aqua and SwapVM could open up the development of actively priced AMMs to independent developers.
1inch Aqua separates liquidity accounting from pricing and execution. Combined with SwapVM, that could make building actively priced AMMs aka propAMMs, much easier for independent developers to build and distribute.
For the last three years I’ve been the tech lead at Nabla Finance, one of the first propAMMs on EVM. To explore that design space, I deployed a separate WETH/USDC propAMM on Optimism using two custom contracts on top of 1inch Aqua.
ChainlinkPriceOracle 0xfF61660F6fE99916b7D5ee5a6e0c9Ad4B8c03D9AOracleSwap 0x146d7001bD33114DF0575CE6fF9Ed16b4fe93BD7
Aqua offers a completely new approach to DEX LP with its unique set of properties and trade offs, which include many benefits for independent builders. It also means that theoretically anybody can supply capital to the propAMM described in this article (OracleSwap) with their own set of parameters via a so called Aqua Strategy. You can try it out for yourself.
WARNING: OracleSwap is an unaudited educational implementation. It uses Chainlink push feeds with zero spread, zero fees and deliberately naive quoting. It is not intended to be profitable or to hold meaningful funds; as written, it is expected to lose money against informed flow.
PropAMMs on Aqua Series
Part 1: Custom AquaAppPart 2: SwapVM + Extruction (Coming soon)Part 3: Pricing and oracles. (Coming soon)
What is 1Inch Aqua and why would one build on it?
Aqua is a very efficient way to manage, deploy and source liquidity. It is intended as 1inch’s answer to the question of institutional grade trading onchain. It enables simultaneous deployment of the same liquidity into multiple strategies of various sorts (XYKs, CLMM ranges, RFQs and propAMMs) as well as rapid rebalancing and strategy parameters adjustment, all without moving the actual ERC20s from market maker’s wallet.
Capital deployment and accounting are its only concerns. Pricing and execution live elsewhere: either inside SwapVM, 1inch’s bespoke bytecode engine deployed as AquaSwapVMRouter, or inside a custom smart contract you write yourself aka AquaApp.
It feels like leverage
In contrast with earlier onchain solutions there are no “deposits” to Aqua. It operates solely on token approvals and virtual balances. ERC20s leave and enter the maker’s wallet exclusively during actual, atomic swaps. This leads to four profound consequences:
Radical increase in capital efficiency by Liquidity Multiplexing. The same amount of tokens can be deployed into an arbitrary number of strategies. It’s quantified as Shared Liquidity Ratio.High flexibility for Market Makers. Freedom to instantly modify strategy parameters independent of other LPs and experimentation without typical opportunity costs.Ease of development. Developers can rapidly build new swapping engines and strategy factories on top of maker supplied liquidity managed by Aqua; SwapVM based strategies additionally have a native path into 1inch’s routing infrastructure.Tokens backing a strategy keep their other jobs: governance, airdrop allocations, staking to name a few.
It is worth stressing that Liquidity Multiplexing does not involve leverage of any sort. Aqua’s Capital parallelisation simply allows the same capital to service multiple strategies at the same time. While it’s radically novel for onchain swap liquidity, it is the ordinary way solvers and to an extent even traditional CLOB market makers operate.
But what is the price of this efficiency?
Traditional non concentrated AMM pool swaps with O(1) computational complexity with respect to the number of liquidity providers n by forcing every LP into a single strategy: the same curve, and fee tier. For CLMM theoretical complexity depends on the number of liquidity ranges involved in a swap but practically remains O(1). It has been a conscious trade off of sacrificing efficiency and flexibility for simplicity, reliability and complete independence of execution.
On the contrary, Aqua introduces O(m) complexity where m is the number of strategies and m >= n (n is still the number of LPs) at all times. Each strategy belongs to a single LP and each LP can deploy multiple strategies in every “pool” and every strategy executes independently. In this case computational efficiency is sacrificed in favor of high capital efficiency, ease of rebalancing, and engineering flexibility. Yet, the onchain cost of this complexity is greatly reduced by moving it offchain, to solvers and DEX aggregators. Parties to which “efficient search across many heterogeneous liquidity sources” lies at the core of their operations.
The original model was perfect for its time, CLMMs introduced more efficiency and more sophisticated market making strategies yet neither of them came close to what offchain and traditional Market Making enabled. Aqua attempts to bridge that gap in a novel and creative way.
SwapVM and The Three Aqua App Paths
In order to benefit from offchain aggregation, a strategy needs to be discoverable by aggregators and solver networks. SwapVM provides the native 1inch execution path: Pathfinder discovers Aqua liquidity offchain and routes eligible Aqua strategies through 1inch’s routing infrastructure, subject to its routing and access controls. SwapVM is an extensible onchain exchange framework currently exposing XYK, CLMM, stable swap and external pricing building blocks. Alternatively (and in parallel) 3rd party developers can take up integrations with other solver networks themselves.
There are three paths by which Aqua apps can be built:
Path A: write a full application in Solidity without touching the SwapVM. Your contract calls pull() and push() on the Aqua contract directly to handle transfers and virtual accounting.Path B: compose a Program from SwapVM’s built in instruction (opcode) set (XYCSwap, XYCConcentrate, PeggedSwap, Fee etc.) and ship it as data part of a Strategy.Path C: deploy a contract with your proprietary quoting logic and call it from inside a SwapVM Program via the Extruction instruction. SwapVM stays the executor delegating the custom quoting computation to your contract.
Paths B and C use SwapVM and therefore have the native 1inch routing path; Path A is a bespoke AquaApp and requires separate discovery/routing integration. The example in this article has been built with path A, though. A bespoke Solidity contract is best suited for showcasing propAMM architecture step by step. Besides, nobody would want this particular integration to be indexed, unless they are dedicated to losing money. Should there be enough interest, I will cover the other paths in the future articles of this series.
What makes a propAMM?
In a nutshell it’s liquidity + proprietary quoting automated onchain. Quoting consists of price source and quoting algorithm. It also needs distribution i.e. access to onchain order flow. It can be divided into four broad components:
Price source aka oracle. Latency and correctness are key properties to optimise.Quoting algorithm: the proprietary part. Should account at least for inventory skew, size, volatility and toxic flow suspicions. In extreme cases may prevent quoting completely.Liquidity. Usually internal inventory supplied by the propAMM creator (Nabla being a notable exception). Aqua provides a generalised accounting layer through which independent makers can allocate their own liquidity to strategies.Infrastructure and distribution. Onchain accounting, UIs, offchain services and aggregator/solver integrations. Arguably the least glamorous and most tedious component, requiring a large amount of infrastructure and relationship building. Aqua provides liquidity and accounting, while SwapVM provides a native path into 1inch’s routing/distribution infrastructure, substantially reducing the amount of custom plumbing required.
By providing for #3 and much of the execution/routing for #4, Aqua+SwapVM lets a builder concentrate much more of their effort on the price source and quoting algorithm, particularly with Path C.
The implementation
TLDR
Contracts: github.com/yellowBirdy/aqua-propAMMFrontend: oracleswap.mieszkomanijak.devExample swap tx: Optimism transaction
The price source
ChainlinkPriceOracle (in src/oracle/ChainlinkPriceOracle.sol) wraps Chainlink price feeds, checks for staleness, and normalizes the values to 18 decimals before providing them to the main contract. In the deployment it’s configured to ETH/USD and USDC/USD feeds.** (18 – oracle.decimals())); timestamp = updatedAt; }
function getAssetPrice(address asset)
external view
returns (uint256 assetPrice)
{
(uint256 price, uint256 timestamp) = _fetchAssetPrice(asset);
if (timestamp + priceMaxAge < block.timestamp) revert STALE_PRICE();
return price;
}
function _fetchAssetPrice(address asset)
internal view
returns (uint256 price, uint256 timestamp)
{
AggregatorV3Interface oracle = oracleByAsset[asset];
if (address(oracle) == address(0)) revert UNKNOWN_ASSET();
(, int256 signedPrice,, uint256 updatedAt,) = oracle.latestRoundData();
/// @notice casting a negative value to uint256 will yield a dangerous result
if (signedPrice <= 0) revert INVALID_PRICE();
/// @notice normalization to 18 decimals
price = uint256(signedPrice) * (10 ** (18 – oracle.decimals()));
timestamp = updatedAt;
}
The pricing algorithm (it’s a mulDiv)
uint8 decIn = IERC20Metadata(tokenIn).decimals();
uint8 decOut = IERC20Metadata(tokenOut).decimals();
if (decIn > decOut) {
priceOut *= 10 ** (decIn – decOut);
} else {
priceIn *= 10 ** (decOut – decIn);
}
amountOut = Math.mulDiv(amountIn, priceIn, priceOut);
Exact out mirrors the calculation but rounds the required input up, in favour of the maker:
uint8 decIn = IERC20Metadata(tokenIn).decimals();
uint8 decOut = IERC20Metadata(tokenOut).decimals();
if (decOut > decIn) {
priceIn *= 10 ** (decOut – decIn);
} else {
priceOut *= 10 ** (decIn – decOut);
}
amountIn = Math.mulDiv(
amountOut,
priceOut,
priceIn,
Math.Rounding.Ceil
);
Both quotes reduce to a decimal adjusted mulDiv; _quoteExactOut uses Math.Rounding.Ceil so rounding favours the maker.
This is the most naive implementation of a price source and quoting algorithm. Do not use it in production. Quoting is a complex and tightly guarded subject worth potentially hundreds of millions of USD. In the future installments of this series I intend to provide you with examples of better solutions and guidance on what to pay attention to when designing your own.
The Aqua mechanics
As I mentioned earlier, Aqua limits its concerns to accounting (and permissions) only. The whole swapping logic aka application is represented as an address and an opaque bytes string called strategy.
maker → application → strategyHash → token → balance
Strategy is an arbitrary struct defined by each app. In our case it is:
struct Strategy {
address maker;
address token0;
address token1;
bytes32 salt;
uint256 nominalBalance0;
uint256 nominalBalance1;
}
Strategy memory strat = Strategy(
makerAddress,
USDC,
WETH,
bytes32(0),
5_000_000,
2_000_000_000_000_000
);
bytes32 stratHash = keccak256(abi.encode(strat));
That’s it. Aqua handles token movement and virtual accounting, while the app defines the swap flow. In this no callback implementation, OracleSwap first receives tokenIn from the taker and approves Aqua. It then credits the maker’s strategy with push() and transfers tokenOut from the maker to the recipient with pull():
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
IERC20(tokenIn).forceApprove(address(AQUA), amountIn);
AQUA.push(
strategy.maker,
address(this),
strategyHash,
tokenIn,
amountIn
);
AQUA.pull(
strategy.maker,
strategyHash,
tokenOut,
amountOut,
to
);
The input tokens land in the maker’s (aka Strategy deployer, not to be confused with the app contract deployer aka the Dev) account, output tokens are sent to the taker, then maker’s virtual balances are updated accordingly.
The rest of src/OracleSwap.sol is unremarkable in the way you want: swapExactIn, swapExactOut, quote functions for both directions mirroring the swaps deterministically so a simulation and a fill agree, and slippage bounds that revert with InsufficientOutputAmount or ExcessiveInputAmount.
Tooling: Foundry for contracts and tests, Hardhat Ignition for deployment and CLI scripts for strategy lifecycle (ship and dock) and swap execution.
What I left out and what’s coming
The honest inventory. Almost every item here is a potential future installment.
Pricing
Zero spread and zero feeNo inventory awareness. nominalBalance0 and nominalBalance1 are in the Strategy struct, but the quoting algo doesn’t use them yet.No depth curve and no size capNo volatility adjustmentsNo toxic flow protectionInadequate price source
Aqua mechanics
Quotes ignore safeBalances, so a quote can succeed while the swap revertsMissing callback path prevents flash swaps and limits composabilityMost notably: SwapVM is not used, so this custom AquaApp does not get the native 1inch discovery/routing path available to SwapVM based strategies.
What’s next
I’m planning this series for 3 to 5 articles going in two different yet complementary directions:
Implementation as a SwapVM program with Extruction SwapVM opcode (external pricing contract). Path C.Deeper dive into propAMM pricing. Both price source and the quoting algorithm.
If you liked it please share and let me know if you would like to read the other parts.
Originally published at https://mieszkomanijak.dev on September 15, 2026.
PropAMMs for Everyone? Building on 1inch Aqua. Part 1. was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.
