Aqua
Shared liquidity layer protocol
Install / Use
/learn @1inch/AquaREADME
Aqua Protocol
Shared liquidity layer protocol enabling liquidity providers to allocate balances across multiple trading strategies without fragmentation.
Table of Contents
Traditional AMM Pools Aqua Protocol
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Single LP: Single LP:
[LP] [LP]($$)
│ │
├─────────┬─────────┐ │
│ │ │ │
│ │ │ ┌───▼────┐
┌───▼────┐┌───▼────┐┌───▼────┐ │ Aqua │
│ AMM ││ AMM ││ AMM │ └───┬────┘
│ Pool A ││ Pool B ││ Pool C │ │
│ ($$) ││ ($$) ││ ($$) │ ┌────────┼────────┐
└───┬────┘└───┬────┘└───┬────┘ │ │ │
│ │ │ │ │ │
[Taker] [Taker] [Taker] ┌───▼───┐┌───▼───┐┌───▼───┐
│ Aqua ││ Aqua ││ Aqua │
│ AMM A ││ AMM B ││ AMM C │
└───┬───┘└───┬───┘└───┬───┘
│ │ │
[Taker] [Taker] [Taker]
❌ Traditional AMM Pools: ✅ Aqua Protocol:
• Liquidity fragmented across • Shared liquidity via AQUA
multiple isolated pools virtual balances
• Capital ($$) locked in pools • Capital ($$) stays in wallet
Overview
Traditional DeFi protocols fragment liquidity by locking it in isolated pools. Aqua solves this through a registry-based allowance system where liquidity providers maintain a single token approval while distributing virtual balances across multiple strategies.
Key Benefits:
- Unified Liquidity: Single approval enables participation in unlimited strategies
- Capital Efficiency: Share liquidity across protocols without redeployment
- Granular Control: Per-strategy balance management
- No Custody: Tokens remain in LP wallets, only virtual balances tracked
Architecture
Components
Aqua.sol - Core registry contract
- Stores virtual balances:
balances[maker][app][strategyHash][token] - Manages lifecycle:
ship(),dock() - Provides swap interface:
pull(),push()
AquaApp - Base contract for trading applications
- Inherits to build AMMs, limit orders, auctions, etc.
- Accesses liquidity via Aqua interface
- Enforces reentrancy protection for safe callbacks
Strategy - Configuration identifier
- Defined by app-specific struct (e.g., token pair, fee, parameters)
- Identified by
keccak256(abi.encode(strategy)) - Immutable once shipped
Core Concepts
Virtual Balances
Aqua doesn't hold tokens - it maintains allowance records. Actual tokens remain in maker wallets until pulled during trades.
mapping(address maker =>
mapping(address app =>
mapping(bytes32 strategyHash =>
mapping(address token => Balance)))) private _balances;
Important: Funds ($$$) always stay in the LP's wallet. AQUA only tracks virtual balance allocations.
Strategy Hash
Uniquely identifies strategy configurations:
bytes32 strategyHash = keccak256(abi.encode(strategy));
Ensures same parameters always produce same identifier.
Strategy Immutability
Once a strategy is shipped, it becomes completely immutable:
- ✗ Parameters cannot be changed (e.g., fee rates, token pairs, weights)
- ✗ Initial liquidity amounts cannot be modified
- ✓ Token balances change ONLY through swap execution via
pull()/push()
Why Immutability?
Immutable data structures significantly reduce bugs in concurrent and distributed systems. As noted in the seminal paper "Out of the Tar Pit" by Ben Moseley and Peter Marks, immutability eliminates entire classes of bugs related to state management and makes systems vastly easier to reason about.
Easy Re-parameterization:
Since strategies don't own funds (tokens remain in your wallet with approval), you can easily adjust parameters:
dock(strategyHash)- Withdraws virtual balances (no token transfers, just accounting)ship(newStrategy)- Creates new strategy with updated parameters and/or liquidity allocations
This flexibility combines the safety of immutability with practical adaptability.
Pull/Push Interface
⚡ Important: Swap Execution Only
pull()andpush()are used exclusively during swap execution to transfer tokens between makers and takers. They are NOT used for liquidity management. Initial liquidity is set viaship()and shouldn't be modified afterward.
Pull: App withdraws tokens from maker to trader during swap
aqua.pull(maker, strategyHash, tokenOut, amountOut, recipient);
Push: Trader deposits tokens into maker's strategy balance during swap
aqua.push(maker, app, strategyHash, tokenIn, amountIn);
Liquidity Management:
- Add liquidity → Use
ship()to create new strategy - Remove liquidity → Use
dock()to withdraw from strategy - Change parameters → Use
dock()thenship()new strategy
Usage
For Liquidity Providers
💡 Strategy Management
- Immutable: Once shipped, parameters and initial liquidity are locked
- No custody: Your tokens stay in your wallet with approval to Aqua
- Easy updates:
dock()→ship()to change parameters (no token transfers needed)- Safer code: Immutability means fewer bugs and easier integration for traders
1. Approve tokens to Aqua (one-time)
token.approve(address(aqua), type(uint256).max);
2. Ship a strategy
XYCSwap.Strategy memory strategy = XYCSwap.Strategy({
maker: msg.sender,
token0: DAI,
token1: USDC,
feeBps: 30, // 0.3%
salt: bytes32(0)
});
address[] memory tokens = new address[](2);
tokens[0] = DAI;
tokens[1] = USDC;
uint256[] memory amounts = new uint256[](2);
amounts[0] = 1000e18; // 1000 DAI
amounts[1] = 1000e6; // 1000 USDC
bytes32 strategyHash = aqua.ship(
address(xycSwapApp),
abi.encode(strategy),
tokens,
amounts
);
3. Manage strategies
// Check balance
(uint256 balanceIn, balanceOut) = aqua.safeBalances(maker, app, strategyHash, tokenIn, tokenOut);
// Withdraw liquidity and deactivate strategy
aqua.dock(app, strategyHash, tokens);
// To change parameters or liquidity:
// 1. dock() existing strategy
// 2. ship() new strategy with updated params
For Traders
1. Implement specific callback interface
contract Trader is IAquaAppSwapCallback {
function aquaAppSwapCallback(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOut,
address maker,
address app,
bytes32 strategyHash,
bytes calldata takerData
) external override {
// Transfer tokenIn to complete the swap (requires token approval)
// This is the ONLY appropriate use of push() - during swap execution
IERC20(tokenIn).forceApprove(aqua, amountIn);
aqua.push(maker, app, strategyHash, tokenIn, amountIn);
}
}
2. Execute trades
aquaApp.swapExactIn(
strategy,
true, // zeroForOne
1e18, // amountIn
0.99e18, // amountOutMin
address(this), // recipient
"" // takerData
);
For Developers
1. Inherit from AquaApp
contract MyAMM is AquaApp {
constructor(IAqua aqua) AquaApp(aqua) {}
struct Strategy {
address maker; // Must-have to make strategyHash unique per user
address token0;
address token1;
// ... strategy parameters (IMMUTABLE once shipped)
}
}
2. Implement trading logic
function swap(
Strategy calldata strategy,
bool isZeroForOne,
uint256 amountIn
)
external
nonReentrantStrategy(strategy.maker, keccak256(abi.encode(strategy)))
returns (uint256 amountOut)
{
bytes32 strategyHash = keccak256(abi.encode(strategy));
address tokenIn = isZeroForOne ? strategy.token0 : strategy.token1;
address tokenOut = isZeroForOne ? strategy.token1 : strategy.token0;
(uint256 balanceIn, uint256 balanceOut) = AQUA.safeBalances(strategy.maker, address(this), strategyHash, tokenIn, tokenOut);
amountOut = //
Related Skills
node-connect
354.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
