Virtual balances

1inch Aqua never takes custody of maker tokens. Instead, the Aqua registry keeps a virtual balance, a counter that records how much of a maker's ERC-20 allowance is currently allocated to each strategy. This counter is the mechanical heart of shared liquidity. Tokens stay in the maker's wallet, the protocol holds zero tokens, and the allowance that backs them is revocable per chain and per token.

Self-custodial by design. A virtual balance is an internal counter inside Aqua.sol. It is not a deposit, a lock, or a transfer of ownership. Real tokens move only during an atomic fill, straight from the maker's wallet to the taker. Smart-contract and approval risk still apply.


The core idea

When a maker calls ship(), no tokens are transferred to the registry. Instead, Aqua.sol records:

Solidity
1
2
3
4
balances[maker][app][strategyHash][token] = Balance {
  amount:      uint248,  // the virtual allocation in token base units
  tokensCount: uint8     // how many tokens belong to this strategy
}

The real tokens remain in the maker's wallet. ship() only consumes part of the maker's existing ERC-20 allowance to the Aqua registry (0x1111113ccf1426a8e30e2bff5e005d929bf6a90a), never ownership of the tokens.

During a swap:

  • pull() moves tokens directly from the maker's wallet to the taker, and the registry decrements amount.
  • push() credits tokens from the taker to the maker's wallet, and the registry increments amount. The incremented balance is immediately available to the strategy, so tokens received through swaps expand the strategy's usable virtual balance without manual rebalancing.

No intermediary custody, no wrapping, and zero tokens held by the protocol at rest.


Why it enables shared liquidity

Because tokens stay in the maker's wallet, a single ERC-20 allowance on a given chain can back many strategies at once:

Maker wallet: 10 WETH  (allowance to Aqua.sol)
  ├── Strategy A  — 3 WETH virtual
  ├── Strategy B  — 4 WETH virtual
  └── Strategy C  — 2 WETH virtual
      ────────────
  Total allocated: 9 WETH (< 10, safe)

A maker shipping Strategy D must ensure the sum of all virtual allocations does not exceed the actual allowance at execution time. The registry does not enforce a hard cap at ship(). It enforces at fill time: if the maker's real balance is insufficient when pull() fires, the transaction reverts. Strategies keep quoting from virtual balances even when the maker is temporarily underfunded, so integrators should monitor real versus virtual balance ratios.

Allowances are scoped per chain and per token. One approval on a chain backs many strategies on that chain; it does not span chains, and each token a strategy uses needs its own approval. The allowance stays revocable at any time.

This differs from pooled-liquidity AMMs such as Uniswap v2/v3, where tokens are deposited into a pool contract and segregated per pool. In Aqua there are no deposits. One wallet balance can participate in many strategies at the same time.


Shared Liquidity Ratio

The Shared Liquidity Ratio (SLR) measures how much quotable liquidity a maker's wallet balance can make available across all of that wallet's strategies:

       total liquidity made available across all strategies
SLR =  ----------------------------------------------------  >= 1
                      actual wallet balance

Example: a wallet holds 1,000 USDC and three strategies each reference up to 1,000 USDC of that same balance. That makes 3,000 USDC of availability backed by 1,000 USDC of real balance, an SLR of 3. The same balance can earn swap fees from every strategy it backs, because real utilization in any single strategy is usually low.

The ratio is a theoretical ceiling, not a forecast. Only the real wallet balance can ever actually fill, no matter how many strategies reference it. Nothing is borrowed, leveraged, or held as collateral. The multiplier simply reflects that an idle balance can be made available in several places until it is used.

The Shared Liquidity Ratio is an availability and efficiency metric, not a safety invariant. Multiple strategies may quote against the same virtual balance at once. Safety comes from the pull() execution model: if the maker's actual wallet balance is insufficient when a swap runs, pull() reverts (via IERC20.transferFrom failure) and the swap cannot complete, so Aqua never carries bad debt. While underfunded, the strategy simply stops filling until the wallet is topped up. That is an availability effect rather than a loss, and strategies are never liquidated.


Reading balances

Two view functions read the on-chain state:

Function Behaviour
rawBalances Returns the raw storage value; does not validate that the token belongs to the strategy
safeBalances Reverts if the token was not registered in the strategy at ship() time (uses tokensCount)

Use safeBalances in production integrations, and rawBalances for debugging or querying arbitrary slots.


Immutability consequence

Because virtual balances are keyed by (maker, app, strategyHash) and strategyHash = keccak256(abi.encode(strategy_struct)), changing any field of the strategy struct would produce a different hash and therefore a different storage slot. That is why strategies are immutable once shipped: to change parameters, dock the old strategy (which zeros its storage slot) and ship a new one with the updated struct.


  • Strategy shows how virtual balances fit into the strategy model.
  • Strategy Lifecycle shows how ship() and dock() change the balance slot.

Did you find what you need?