Don’t try this in production: an oracle swap strategy that will lose you money.
Part 1 of “propAMMs on 1inch Aqua”

Hi! It’s Mieszko, for the last 2.5 years I’ve been the tech lead of Nabla Finance, one of the first propAMMs on EVM. There’s a live propAMM deployed on Optimism consisting of two contracts serving a single WETH/USDC strategy.
ChainlinkPriceOracle0xbc841953f1aD4164e4807A748Bf92280959F2Af7OracleSwap0xda34c61B28Ef67811A00F299C2F77DD9c115a9b1
It is powered by 1Inch Aqua which means that anyone can create their own instance via by deploying 1Inch Aqua strategy.
With Chainlink’s push oracle pricing and simplest quoting algorithm it’s not a good strategy, though. It exists only to showcase how a propAMM could be implemented leveraging 1inch Aqua liquidity.
What is 1Inch Aqua and why would one built on it?
Aqua is a very efficient way to manage, deploy and source liquidity. Inteded as 1Inch’s answer to the question of institutional grade trading onchain. It enables simultaneous deployment of the same liquidity into multiple strategies (XYKs, CLLM ranges, RFQs and propAMMs) and rapid rebalancing and strategy parameters adjustment, all moving the ERC20s from market maker’s wallet.
Capital deployment and accounting are its only concerns. Pricing and execution lives elsewhere: either inside SwapVM, 1Inch’s bytecode engine deployed as AquaSwapVMRouter, or inside a bespoke 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 maker’s wallet exclusively during actual swaps. This leads to three profound consequences:
- Radical increase in capital efficiencty by Liquidity Multiplexing. The same amount of tokens can be deployed into arbitrary number of strategies. It’s quantified as Shared Liquidity Ratio.
- High flexibility for Market Makers. Freedom to instatantly modify strategy parameters indepenent of other LPs and experimentation without typical opportunity costs.
- Ease of development. Developers can now rapidly develop new swapping engines and strategy factories with instant access to liquidity and distribution.
- Tokens backing a strategy keep their other jobs: governance, airdrop allocations, staking to name a few.
It is worth streessing that there is no leverage involved. Aqua’s Capital Multiplexing works a lot like solvers use the same capital to serve orders from multiple aggregators at the same time.
But what is the price of this efficiency?
Traditional AMM swap with O(1) complexity in respect to number of liquidity providers n by forcing every LP into essentially a single strategy: the same curve, and fee tier (for CLMM it depeds on the number of ranges but remains essentially O(1)). It has been a conscious tradeoff of efficiency and flexibility for simplicity and independence from externalities.
On the contrary, Aqua introduces O(n) complexity (each strategy executes independantly) in return for high capital efficiency, ease of rebalancing, and engineering flexibility.
Yet, onchain cost of this complexity is greatly offset by transferring it offchain, to solvers and DEX aggregators. Parties to which “search across many heterogeneous liquidity sources” is the entire job description.
SwapVM and The Three Aqua App Paths
In order to reap the benefits of the offchain aggregation a strategy needs to be indexed by the aggregators. It is achievable, out of the box, thanks to Aqua’s sister protocol called SwapVM. It is an extensible onchain exchange framework currently exposing XYK, CLMM, stable-swap and external pricing strategies. All SwapVM “DEXes” (Aqua strategies) are automatically picked up by 1inch resolver network. Alternatively (or in parallel) you can take up integrations with other solver networks yourself.
There are three ways Aqua apps can be built:

- Path A: write a full applicationin Solidity without SwapVM involvement. Your contract calls
pull()andpush()on Aqua contract directly to handle transfers and virtual accounting. - Path B: compose a
Programfrom SwapVM’s built-in instruction set (XYCSwap,XYCConcentrate,PeggedSwap,Feeetc.) and ship it as data part of aStrategy. - Path C: deploy a contract with your proprietary quoting logic and call it from inside a SwapVM Program via the
Extructionopcode. SwapVM stays the executor delegating the custom pricing computation to your contract.
Only paths B and C give easy access to 1inch resolvers. Example in this article has been built with path A: bespoke Solidity contracts as it is best suited for showcasing propAMM architecture step by step. 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 pricing algorithm. It also needs distribution i.e. access to onchain order flow. It can be divided into four borad components:
1. Price source aka oracle. Latency and correctness are key properties.
2. Pricing algorithm: the secret sauce of calculating the actual quote. Should account for inventory skew, size, volatility and toxic flow suspicions. In extreme cases may prevent quoting completely.
3. Liquidity. Usually internal inventory by the propAMM creator (Nabla being a notable exception). Aqua opens up the possibility of crowdsourcing.
4. Infrastructure and distribution. Onchain accounting, UIs, offchain services and aggregators/solvers integrations. Arguably the least glamorous and most tedious component requiring large amount of infrastructure and relationship building. Aqua+SwapVM can handle most if not all of it.
Since Aqua+SwapVM can supply #3 and #4 it is, as far as I can tell, the first place where a builder can put essentially all of their attention on the price source and quoting algo. Note that these benefits are available for Paths B and C while.
The implementation
The price source
ChainlinkPriceOracle (in src/oracle/ChainlinkPriceOracle.sol) wraps Chainlink ETH/USD and USDC/USD price feeds, checks for staleness, normalizes to 18 decimals before providing them to the main contract.
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 normalization to 18 decimals
price = uint256(signedPrice) * (10 ** (18 - oracle.decimals()));
timestamp = updatedAt;
}
Very important note on using Chainlink push feeds for propAMMs — it’s extremely bad idea and would lose your LPs money rapidly. The feeds are orders of magnitude too slow.
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);
}
uint256 amountOut = Math.mulDiv(amountIn, priceIn, priceOut);
uint256 amountIn = Math.mulDiv(
amountOut,
priceOut,
priceIn,
Math.Rounding.Ceil
);
It’s amulDivadjusted for token decimals, andMath.Rounding.Ceilfor _quoteExactOut
This is the most naive implementation of a quoting algorithm. Do not use it in production. Quoting is a complex and tightly guarded subject since uccessful worth tehns if not hundreds 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. Make sure to subscribe.
The Aqua mechanics
As I hinted earlier, Aqua limits its’ concerns to accounting (and permissions) only. Whole swapping logic aka application is represented as an addressand 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. It’s the app’s concern to call Aqua push() and pull() functions in order to move the tokens and update virtual balances. An Aqua app needs to , at the very least, execute the following 2 calls.
AQUA.push(strategy.maker, taker, strategyHash, tokenIn, amountIn);
AQUA.pull(strategy.maker, strategyHash, tokenOut, amountOut, taker);

The input tokens land in the maker’s aka Strategy deployer (not to confuse with the app contract developer) 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 functions that mirroring 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 future instalment.
Pricing
- Zero spread and zero fee
- No inventory awareness.
nominalBalance0andnominalBalance1are in theStrategystruct, but the quoting algo doesn’t use them yet. - No depth curve and no size cap
- No volatility adjustments
- No toxic flow protection
- Inadequate price source
Aqua mechanics
- Quotes ignores
safeBalances, so a quote can succeed while the swap - Missing callback path prevents flash swap limits composability
- Most notably: SwapVM is not used, so no discoverability and no access to order flow
What’s next
I’m planing this series for a 3 to 5 articles going in two different yet complementary directions:
- Implementation as a SwapVM program with
ExtructionswapVM opcode (external pricing contract). Path C. - Deeper dive into propAMM pricing. Both price source and the quoting algorithm.
If you liked it please share it and subscribe for more.