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
12345678
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
12345678910
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
12345
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:rateLtapplies to the token with the lower address,rateGtto the greater. For an 18-vs-6-decimal pair the 18-dec token uses rate1and the 6-dec token uses rate1e12, 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.x0corresponds to the lower-address token,y0to the greater.linearWidth— coefficient A scaled by1e27. 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.