Worked Examples

This page walks through two fully-worked numeric examples on Aqua. The first is a fee round-trip that shows exactly how the LP swap fee is charged, in wei-level arithmetic, and where it is set in the SDK and the on-chain opcode. The second is an illustrative net-P&L for a concentrated-range position, to show how gross swap-fee income and impermanent loss combine.

The fee example is exact: every number is reproducible from the on-chain formula. The P&L example uses illustrative assumptions — they are clearly labelled and are not a forecast or a promise of return.

Example 1 — Fee round-trip on the LP swap fee

Aqua expresses fees in basis points on a 1e9 base, where 1e9 = 100%. A LP swap fee of 0.30% is therefore feeBps = 3,000,000 on that base (0.0030 × 1e9 = 3,000,000). In v1 the LP "Swap fee" is the only non-zero fee.

Where the fee is set — SDK

The maker sets the input fee on the strategy with withFeeTokenIn. The SDK takes the fee in conventional basis points (10,000 bps = 100%) and scales it onto the 1e9 base internally via FlatFeeArgs.fromBps, which multiplies by 100,000:

TypeScript
1
2
3
4
5
6
7
8
import { AquaXYCAmmStrategy } from "@1inch/swap-vm-sdk";

const program = AquaXYCAmmStrategy.new()
  .withFeeTokenIn(30) // 30 bps = 0.30% swap fee on the input token
  .build();

// Internally: FlatFeeArgs.fromBps(30) => fee = 30 * 100_000 = 3_000_000
//   i.e. feeBps = 3,000,000 on the 1e9 base  (3,000,000 / 1e9 = 0.30%)

Where the fee is applied — opcode

At execution time the flatFeeAmountInXD instruction (the Fee opcode) runs before the swap-amounts computation. For an exact-in swap it reduces the input amount by the fee, rounding the fee up in the pool's favour, and feeds the remainder into the XYC swap curve:

// swap-vm/src/instructions/Fee.sol  (BPS = 1e9)
if (ctx.query.isExactIn) {
    ctx.swap.amountIn -= Math.ceilDiv(ctx.swap.amountIn * feeBps, BPS);
    ctx.runLoop();
}

So the fee amount is ceilDiv(amountIn × feeBps, 1e9), and the amount that actually reaches the swap curve (and hence the maker) is amountIn − fee.

Concrete swap — taker sends 1,000 USDC

USDC has 6 decimals, so 1,000 USDC = 1,000,000,000 base units. With feeBps = 3,000,000 and BPS = 1e9:

Step Formula Value (base units) Human
1. Taker input (amountIn) given 1,000,000,000 1,000.000000 USDC
2. Fee ceilDiv(1,000,000,000 × 3,000,000, 1,000,000,000) 3,000,000 3.000000 USDC
3. Net into swap curve 1,000,000,000 − 3,000,000 997,000,000 997.000000 USDC
4. Fee retained by maker = step 2 3,000,000 3.000000 USDC

The fee is exactly 3 USDC. The remaining 997 USDC is what the constant-product XYC curve prices to produce the taker's amountOut; the 3 USDC never leaves the pool — it stays in the maker's virtual tokenIn balance.

Resulting amountOut (illustrative reserves)

The exact amountOut depends on the live virtual reserves. For a constant-product XYC pool the output is amountOut = R_out × dx / (R_in + dx), where dx is the net input (997 USDC). Taking illustrative reserves of 3,000,000 USDC and 1,000 WETH (a 3,000 USDC/WETH mid-price):

Case dx (USDC) amountOut = 1,000 × dx / (3,000,000 + dx)
With 0.30% fee 997 ≈ 0.332223 WETH
Fee-free reference 1,000 ≈ 0.333222 WETH

The fee costs the taker ≈ 0.000999 WETH of output — the value the maker keeps. Reserve numbers here are illustrative; the 3 USDC / 997 USDC split above is exact and independent of reserves.

The fee auto-compounds

Because the retained 3 USDC stays inside the maker's virtual reserves rather than being paid out, it is added to the maker's Aqua balance and surfaced by the Pushed(maker, app, strategyHash, token, amount) event. On the next quote the curve prices against the larger reserve, so accrued fees compound into the position automatically — makers do not claim them separately.

Example 2 — Concentrated-range net P&L (illustrative)

Every number in this section is an ILLUSTRATIVE ASSUMPTION, not a forecast. Swap-fee income is not guaranteed: it depends entirely on how much taker volume is actually routed through your range while the price is inside it. A concentrated range is not leverage — it concentrates the same capital into a tighter band, which raises fee capture when in range and raises impermanent loss when the price moves. Impermanent loss can exceed fees, and a range that the price has left earns nothing until it returns.

Setup

A maker concentrates WETH/USDC liquidity into the 1,500–3,000 USDC-per-WETH band and charges a 0.30% swap fee:

TypeScript
1
2
3
4
5
6
7
8
9
10
import { AquaXYCAmmStrategy } from "@1inch/swap-vm-sdk";

const ONE_E18 = 1_000_000_000_000_000_000n;

const program = AquaXYCAmmStrategy.newConcentrate({
  rawPriceMin: ONE_E18 / 3000n, // 3,000 USDC per WETH (upper USDC price)
  rawPriceMax: ONE_E18 / 1500n // 1,500 USDC per WETH (lower USDC price)
})
  .withFeeTokenIn(30) // 30 bps = 0.30%
  .build();

Assumptions

Input Assumed value Note
Position size $50,000 Illustrative capital in the range
Swap fee 0.30% (withFeeTokenIn(30)) Exact, from Example 1
Period 30 days Illustrative window
Number of fills (N) 400 Each is a Swapped event
Average fill notional $2,000 Illustrative
Volume routed in-range $800,000 = 400 × $2,000
Realised impermanent loss $1,500 Illustrative price-drift within the band

Net P&L

Gross swap-fee income is the fee rate applied to the in-range volume; each fill's fee accrues via Swapped(orderHash, maker, taker, tokenIn, tokenOut, amountIn, amountOut) and is retained into the maker's balance via Pushed (same mechanism as Example 1). Net P&L subtracts the illustrative impermanent loss:

Line Formula Amount
Gross swap-fee income 0.30% × $800,000 +$2,400
Impermanent loss (illustrative) assumption −$1,500
Net P&L (30 days, illustrative) $2,400 − $1,500 +$900

The +$900 net figure is only as good as the assumptions above. If less volume is routed in-range, gross fees fall proportionally; if the price drifts further within or out of the band, impermanent loss rises and can outweigh fees, turning net P&L negative. Use this template with your own realised Swapped volume and mark-to-market to compute an actual result — do not treat these inputs as expected values.

How to measure the real numbers: sum the fee portion of each Swapped event over your period for gross income, read your virtual-reserve growth from Pushed events, and mark your position to the current price to compute realised impermanent loss. Net P&L is gross fees minus that impermanent loss.

Illustrative operating numbers

This section puts concrete numbers on two setups a quant, market maker, or stablecoin issuer would parameterize: a volatile-pair XYC WETH/USDC pool, and a pegged USDC/DAI pool on the PeggedSwap curve. The fee model is exact (reused verbatim from Example 1: feeBps on the 1e9 base, withFeeTokenIn(30) = 0.30% = feeBps 3,000,000). The PeggedSwap curve parameters are exact facts from the deployed contract (linearWidth is coefficient A scaled by 1e27; the valid range is 0 to MAX_LINEAR_WIDTH = 5000e27 inclusive). Every reserve, volume, price-drift, and P&L figure below is an ILLUSTRATIVE ASSUMPTION.

ILLUSTRATIVE — not a forecast, not a promise of yield. The reserves, daily volumes, impermanent-loss figures, and net P&L below are worked-example inputs, not expected values. Swap-fee income is earned only from taker volume actually routed through your pool while price is in range; it is not guaranteed. Impermanent loss can exceed fees and turn a net figure negative. Aqua’s shared liquidity is capital availability for makers to quote against — it is not leverage and it does not pay a yield. Replace every ILLUSTRATIVE number with your own realised Swapped volume and mark-to-market before drawing any conclusion.

Setup A — XYC WETH/USDC (volatile pair)

A constant-product XYC pool quoting WETH against USDC at a 0.30% swap fee. The fee line is exact; reserves and volume are ILLUSTRATIVE.

TypeScript
1
2
3
4
5
import { AquaXYCAmmStrategy } from "@1inch/swap-vm-sdk";

const program = AquaXYCAmmStrategy.new()
  .withFeeTokenIn(30) // 30 bps = 0.30% -> feeBps 3,000,000 on the 1e9 base (EXACT)
  .build();
Parameter Value Basis
USDC reserve (6 dec) 3,000,000 USDC ILLUSTRATIVE
WETH reserve (18 dec) 1,000 WETH ILLUSTRATIVE
Implied mid-price 3,000 USDC / WETH = 3,000,000 / 1,000 (ILLUSTRATIVE)
Pool TVL ~$6,000,000 3M USDC + 1,000 WETH × $3,000 (ILLUSTRATIVE)
Swap fee 0.30% (feeBps 3,000,000) EXACT
Sample daily volume $2,000,000 ILLUSTRATIVE

Per fill the fee is exactly ceilDiv(amountIn × 3,000,000, 1e9) retained into the maker’s virtual reserves (it auto-compounds, per Example 1). Aggregated across a day of ILLUSTRATIVE volume:

Line Formula Amount (ILLUSTRATIVE)
Gross daily swap-fee income 0.30% × $2,000,000 +$6,000
Impermanent loss (day, assumed) assumption −$2,000
Net (day, ILLUSTRATIVE) $6,000 − $2,000 +$4,000

The 0.30% rate and the per-fill ceilDiv arithmetic are exact; the $6,000 gross scales linearly with whatever volume actually routes through the pool, and the −$2,000 IL is a placeholder — a larger price move raises it and can outweigh fees.

Setup B — Pegged USDC/DAI (PeggedSwap curve)

PeggedSwap uses the square-root-linear invariant √(x/X₀) + √(y/Y₀) + A(x/X₀ + y/Y₀) = 1 + A, with curvature p=0.5 hardcoded. The five program args are exact fields of PeggedSwapArgsBuilder.Args (swap-vm/src/instructions/PeggedSwap.sol): x0, y0, linearWidth, rateLt, rateGt.

  • rateLt / rateGt — decimal-normalization multipliers assigned by token address: rateLt applies to the token with the lower address, rateGt to the greater. For an 18-vs-6-decimal pair the 18-dec token uses rate 1 and the 6-dec token uses rate 1e12, normalizing both to a common 1e18 scale. These are exact (structural), not tunable.

  • x0 / y0 — initial reserve normalization factors = initial balance × that token’s rate. x0 corresponds to the lower-address token, y0 to the greater.

  • linearWidth — coefficient A scaled by 1e27. Higher A means tighter price near 1:1. For tight stablecoin pairs (USDC/USDT, USDC/DAI) the contract’s parameter guide recommends A ≈ 100e27–300e27. Hard cap: MAX_LINEAR_WIDTH = 5000e27.

// PeggedSwapArgsBuilder.Args  (swap-vm/src/instructions/PeggedSwap.sol, v1.0.1)
// USDC (6 dec) / DAI (18 dec).  ILLUSTRATIVE assumption below: the 18-decimal
// token holds the LOWER address, so rateLt = 1 (DAI) and rateGt = 1e12 (USDC).
// If your actual addresses order the other way, swap rateLt<->rateGt and x0<->y0.
Args({
  x0:          1_000_000e18,      // lower-address (DAI) reserve x rate = 1,000,000e18 x 1   (ILLUSTRATIVE reserve)
  y0:          1_000_000e18,      // greater-address (USDC) reserve x rate = 1e12 x 1e12  (ILLUSTRATIVE reserve)
  linearWidth: 200e27,            // A = 200, mid of the recommended 100e27-300e27 band   (ILLUSTRATIVE choice)
  rateLt:      1,                 // 18-dec token (DAI) -> scale 1               (EXACT: structural)
  rateGt:      1_000_000_000_000  // 6-dec token (USDC) 1e12 -> normalize to 1e18 (EXACT: structural)
})
Arg Value Basis
x0 1,000,000e18 ILLUSTRATIVE initial reserve × rate
y0 1,000,000e18 ILLUSTRATIVE initial reserve × rate
linearWidth (A) 200e27 ILLUSTRATIVE choice, inside EXACT 100e27–300e27 guide band; ≤ 5000e27 cap
rateLt 1 EXACT — 18-dec (lower-address) token
rateGt 1e12 EXACT — 6-dec (greater-address) token
Swap fee (optional) 0.30% (feeBps 3,000,000) EXACT if withFeeTokenIn(30) is set

Peg behaviour and bounds. Raising A tightens the curve so a swap near the 1:1 anchor moves price very little (deep effective liquidity); the swap output still rounds in the maker’s favour, exactly as in Example 1. This curve has finite reserves and a hard price boundary — it is designed for assets pegged to a fixed ratio (stablecoins, wrapped tokens), and is not suitable for a drifting peg whose ratio changes over time without a moving anchor. linearWidth must satisfy 0 ≤ A ≤ 5000e27; a value above the cap reverts at parse time with PeggedSwapInvalidLinearWidth. None of this constitutes a promise of return: with a well-chosen A a pegged pool captures fee volume at low slippage, but income still depends entirely on realised taker flow.

To turn either template into an actual result, sum the fee portion of each Swapped event over your period for gross income, read virtual-reserve growth from Pushed events, and mark the position to current price for realised impermanent loss. Net = gross fees − impermanent loss.

Did you find what you need?