---
title: "AIMM spread and fee model"
description: "Direction-invariant multi-factor spread; one output-side fee at half the spread; convex coverage toll charged before it"
audience: tech
type: explanation
status: live
lang: en
updated: "2026-09-04"
publish: true
---
# AIMM spread and fee model

> One swap pays exactly two charges, both on the output token and in this order: the convex coverage toll, then the fee at half the spread.

---

## 1. Scope and status

Formulas are stated in contract units and cited to the implementing function. The surrounding function name is the stable reference.

**Path risk aggregation is shipped.** `minFee` and `confidence` sum over legs, sigma composes in quadrature, and the staleness surcharge sums per leg ([§3.2](#32-the-five-inputs-and-how-they-aggregate)). Nothing caps the composed spread from above; the only bound is the saturating narrowing into the `uint16` quote field, reasoning in [§3.1](#31-definition).

**The multi-anchor tree is shipped too.** The interior leg prices under the same `_legMid` law as the terminal one and carries a manipulation fence in its floor ([§10.2](#102-leg-pricing)). Topology, depth bounds and what the `anchor` column configures: [Anchor Path Pricing §1](/docs/1-1-3-anchor-path-pricing#1-topology).

---

## 2. Units

| Symbol | Name | Base | Meaning |
|---|---|---|---|
| $B$ | `PBPS` | $10^6$ | 1 unit = 0.0001% = 0.01 bp |
| $\text{BPS}$ | `BPS` | $10^4$ | 1 unit = 0.01% = 1 bp |
| WAD | `WAD` | $10^{18}$ | prices, coverage, $Q$ |

Conversions used on the spread path: bps $\to$ PBPS is $\times\,B/\text{BPS} = \times 100$. Volatility $\sigma$ is PBPS-based ($10^4 = 1\%$), capped at `MAX_SIGMA_PBPS` $= 10^8$ (`PoolConstantsLib.sol`).

**Notation.** $\kappa$ is dispersion throughout this chapter (as in [Liquidity Shaping](/docs/1-1-2-liquidity-shaping) and [Parametrization §4.2](/docs/1-1-7-parametrization#42-vega-volatility-sensitivity)). The coverage wall is $\kappa_{\text{cov}}$ (`kappaCovBps`). The feed's confidence interval is $u$ (bps): it never shares a symbol with dispersion. On the interior leg, $F_i$ is the manipulation fence, $w_i$ the leg's mid swing, $\Sigma_i$ its curve's span, $\rho_i$ its `dispRefPbps` and $\bar w$ the protocol swing cap ([§10.2](#102-leg-pricing)).

`minFeePbps` is `uint16` in PBPS, and it is the only fee rate `getAsset` returns. It is bounded two-sidedly at **every** write path (`initAsset` and `setAssetParams`): floor `MIN_FEE_PBPS = 1` (0.01 bp), ceiling `ONE_PCT_PBPS = 10,000` PBPS (1%). `minFeePbps` is charged unconditionally on every swap, so `ONE_PCT_PBPS` is the whole guard against an owner grandfathering an extortionate floor. `SwapQuote.spreadPbps` is separately `uint16`, ceiling 65,535 $\approx$ 6.55%: that is a **field width**, not a fee policy ([§3.1](#31-definition)).

---

## 3. The spread

### 3.1. Definition

> Vocabulary, canonical: mark, mid, skew, price impact, spread, fee and coverage toll each name exactly one object, and only impact and the toll are direction-asymmetric. [Slippage & Price Impact §1.1](/docs/1-1-5-slippage-price-impact).

The spread $S$ is a **round-trip** width in PBPS. It is computed once per swap over the whole path and returned as `SwapQuote.spreadPbps` (`uint16`). A single swap is charged $S/2$, once, on the output ([§5](#5-the-fee)).

$$S = \underbrace{\textstyle\sum_i f_{\min,i} + \tfrac{\sigma_p\,\nu_p}{100\cdot\text{BPS}}}_{S_{\text{vol}}} + \underbrace{\textstyle\sum_i \tfrac{z\,\sigma_i\sqrt{\tau_i}}{\text{BPS}}}_{U_{\text{stale}}} + \underbrace{\big(\textstyle\sum_i u_i\big)\cdot\tfrac{B}{\text{BPS}}}_{U_{\text{conf}}}, \qquad \sigma_p = \sqrt{\textstyle\sum_i \sigma_i^2}$$

`Pricing._pathSpread` and `_staleTerm`. Integer arithmetic, floor division at each `/`. On an interior leg $f_{\min,i}$ is already raised to $\max(f_{\min,i}, F_i)$ before it enters the sum ([§10.2](#102-leg-pricing)); on an endpoint leg it is the stored `Asset.minFeePbps`.

No clamp on either side. The floor needs none: $\sum_i f_{\min,i}$ is the additive base of $S_{\text{vol}}$, so $S \ge \sum_i f_{\min,i}$ by construction. The only bound above is the **saturating** narrowing into `SwapQuote.spreadPbps` at 65,535 PBPS, a `uint16` field width and not a policy.

Any formula of the form $S = \min(S_{\text{raw}}, f_{\max})$, and any per-leg clamp composed into a path bound, is therefore wrong.

**Why no cap.** Written out in full in the `_pathSpread` doc comment; three claims:

1. **A cap is not trader protection.** `minAmountOut` is: exact, caller-set, per trade, and it bounds delivered output rather than one input to it. A protocol-wide fee cap is a worse instrument for the same job.
2. **A cap manufactures its own pathologies.** Composed per asset, $\sum_i f_{\max,i}$, it makes a 2-leg quote cost **more** than the same two legs traded separately, paying a splitter to route around the hub: exactly the routing invariant path composition exists to hold. Repaired per leg, it silently under-quotes an unpinned path by about 10 bp unless the staleness term is also summed per leg. A bound that needs a second bound to stay sound is not a bound.
3. **A cap binds on the tape where the defense is needed.** A stale or high-CI leg is precisely when the premium must be paid. Clamping there hands the picked-off pool a discount, and every such clamp is attacker-**timeable**: an adversary who can push `raw` over the cap, by waiting out a keeper or by choosing a high-CI moment, chooses when it binds.

**Scale.** $U_{\text{conf}} = 100\sum_i u_i$, so a single leg reporting a 500 bps CI adds exactly **50,000 PBPS** (5%) on its own, on top of the floor. Integrators must derive worst-case cost from `getSwapQuote` rather than from any config field, and enforce it with `minAmountOut`.

What survives is the floor, which is a security quantity: $\sum_i f_{\min,i}$ with $f_{\min,i} \ge 2\theta_i$ per leg ([§9](#9-why-the-path-fee-floor-sums-over-legs)), plus the interior fence folded into it. Saturation at `uint16` cannot eat that floor; the bound and its arithmetic are in [§9](#9-why-the-path-fee-floor-sums-over-legs).

### 3.2. The five inputs, and how they aggregate

All five compose over **legs**, never over endpoints, and none of them is a `max`.

| Input | Source | Aggregation (shipped) | Site |
|---|---|---|---|
| $f_{\min,p}$ | `Asset.minFeePbps`, raised to $\max(f_{\min,i}, F_i)$ on an interior leg | $\sum_i$ over legs | `_walkLegs`, floor at `_priceInteriorLeg` |
| $\sigma_p$ | `FeedData.sigmaPbps` | quadrature $\sqrt{\sum_i \sigma_i^2}$, one `sqrt` at the end | `_walkLegs`, rooted `_pathSpread` |
| $\nu_p$ | `Asset.vegaBps` | $\max(\nu_{\text{in}}, \nu_{\text{out}})$ over the **endpoints only**, a path constant | `_quotePath` |
| $u_p$ | `FeedData.confidenceBps` | $\sum_i$ over legs | `_walkLegs` |
| $U_{\text{stale}}$ | staleness excess (s) | $\sum_i$ of **per-leg surcharges** $z\sigma_i\sqrt{\tau_i}/\text{BPS}$ | `_walkLegs` |

Confidence and staleness share one accumulator (`PathAccumulator.riskPath`) because `_pathSpread` only ever consumed their sum, and the fusion is exact: $\sum_i \text{stale}_i + (\sum_i u_i)\cdot(B/\text{BPS}) \equiv \sum_i(\text{stale}_i + u_i \cdot B/\text{BPS})$, the scale being an integer constant.

Every quantity comes off the feed the leg already read (`_legMarkAndFees`): staleness and confidence are carried on the leg walk, not cached per endpoint, so a quote performs no second oracle read (worth 15.4k gas on a spoke-to-spoke quote). $\nu$ is the one endpoint-scoped input, deliberately: vega is the pool's per-asset sigma-sensitivity dial, not a per-leg risk quantity, and the endpoints are the assets actually paid in and out. That scoping has a live deploy consequence: [§11.2](#112-deploy-config-constraint-vegabps).

On a flat roster - every leg base-anchored ([Anchor Path Pricing §1.1](/docs/1-1-3-anchor-path-pricing#11-what-the-anchor-column-configures)) - the only interior node is the base, which supplies no leg profile, so a path has at most two legs. At `MAX_DEPTH = 4` it has up to eight ([§10.1](#101-routing)), and every one pays into all five aggregates.

$\sigma_i$ is the NXR-signed Parkinson volatility stored directly on chain, floored at the realized $|\Delta\text{mark}|/\text{mark}$ on each push (`FeedMathLib.markMovePbps`). Which feed supplies it depends on the leg's oracle mode; see [Oracles](/docs/3-4-oracles).

### 3.3. Volatility band

$$S_{\text{vol}} = f_{\min,p} + \frac{\sigma_p \cdot \nu_p}{100 \cdot \text{BPS}}$$

Vega has base 10,000 = 1.0x. At $\nu = 10^4$ and $\sigma = 10^4$ PBPS (1%), the volatility contribution is $10^4 \cdot 10^4 / 10^6 = 100$ PBPS = 1 bp above the floor. There is no hardcoded base: the base **is** the configured floor.

### 3.4. Staleness surcharge

$$U_{\text{stale}} = \sum_i \frac{z \cdot \sigma_i \cdot \sqrt{\tau_i}}{\text{BPS}}, \qquad \tau_i = \max\big(0,\; t_{\text{now}} - t_{\text{obs},i} - g_i\big), \qquad g_i = \min\!\big(\tfrac{\text{ttl}_i}{2},\, 30\big)$$

with $z = 100$ (`STALE_Z`) and the grace cap $30$ s (`STALE_GRACE_CAP_SECS`), both file-private constants in `Pricing.sol`. $\tau_i$ is computed in `_staleExcessOf` off the feed the leg already read, the per-leg term in `_staleTerm`, which short-circuits to 0 when $\tau_i = 0$ or $\sigma_i = 0$ (skips the `sqrt`), and the legs are summed at `_walkLegs`.

**Per leg, not coupled.** A coupled form, $z\,\sigma_p\sqrt{\max_i \tau_i}/\text{BPS}$, charges one aggregate age against the path sigma and is wrong. One keeper feeds several spokes, so an outage staleses them together - precisely the case the term exists for - and the coupled form charges two equally stale legs $\sqrt{2}/2 = 70.7\%$ of what they each owe (1414 against 2000 PBPS at $\sigma = 1\%$, $\tau = 100$ s: a 5.86 bp leak), while over-charging a fresh leg $1.41\times$ for its neighbor's lag. Pinned by the staleness regression tests (per-leg summation; a fresh leg pays no staleness premium).

The clock is $t_{\text{obs}} = \min(t_{\text{src}}, t_{\text{upd}})$, computed by `FeedMathLib.observedAt` from the attested source time and the landing time: the attested source time, capped at the landing time, so a relay cannot stamp an old signed quote fresh.

The $/\text{BPS}$ is a unit normalization, not a tuning knob. $\sigma$ is PBPS-scaled, so without it the term is $10^4\times$ too large and pins the spread at its `uint16` field ceiling the instant age crosses the grace, turning a ramp into a step. Worked: $\sigma = 10^4$ PBPS (1%), $\tau_p = 100$ s $\to 100 \cdot 10^4 \cdot 10 / 10^4 = 1000$ PBPS = 10 bp. At $\tau_p = 1800$ s: $100 \cdot 10^4 \cdot 42 / 10^4 = 4200$ PBPS = 42 bp.

The grace cap binds independently of $\text{ttl}$: at `ttl = 600` the grace is 30 s, not 300 s. Without the cap a long `ttl` buys a proportionally long unpriced window - exactly the window a delinquent keeper occupies.

Why the grace exists: the keeper snaps the mark whenever the deviation crosses an **edge-relative** push boundary, or on a heartbeat. That boundary is a configured percentage of the leg's own live edge, `edge_kappa_pct` $\times\,(f_{\min} + \sigma\nu/(100\cdot\text{BPS}) + U_{\text{stale}})$, with the static $\theta$ retained only as the fallback when the pool's `minFeePbps` is unknown to the keeper (`keepers/src/oracle/mod.rs`, `push_boundary_pbps` / `edge_pbps_with`). Shipped on Arc at `edge_kappa_pct = 50`, so the keeper pushes when $|\Delta| > 0.5\,S_{\text{leg}}$: the mark is snapped before the drift reaches the half-spread the swap is charged. Inside that contract an *old but accurate* mark (flat tape, no push owed) must not be penalized, or the pool quotes wide and loses flow for nothing. Past the grace the guarantee is void and the surcharge ramps with the unobserved drift $\sigma\sqrt{\tau}$ up to the hard TTL revert. The confidence term is deliberately excluded from the keeper's basis while `confidenceBps` on the wire is a freshness proxy rather than a dispersion measure: folding it in would invert the trigger, making a staling feed push less.

$U_{\text{stale}}$ is a graceful-degradation layer **below** a hard revert, not instead of it. `FeedMathLib.gate` reverts:

- `StaleData` at `age > ttl`
- `ZeroValue` at `mark == 0`
- `ThresholdViolation` at `confidence > MAX_CONFIDENCE_HALT_BPS`
- `FeatureDisabled(Err.Resource.FEED)` on the guardian fast-freeze bit `FEED_HALT_BIT`

### 3.5. Confidence surcharge

$$U_{\text{conf}} = \Big(\sum_i u_i\Big) \cdot \frac{B}{\text{BPS}} = 100 \sum_i u_i$$

$u_i$ is leg $i$'s feed-reported 1σ confidence interval in bps. There is no per-asset knob: 1 bp of reported CI buys 100 PBPS of spread, unconditionally. A path inherits **every** leg's uncertainty, not the worst one: a quiet leg beside a noisy one is still charged its own CI (`_walkLegs`; pinned by regression tests). Past `MAX_CONFIDENCE_HALT_BPS` = 1000 bps (10%) the swap reverts rather than quoting (`FeedMathLib.gate`, `PoolConstantsLib.sol`). Fail-closed, like the depeg band.

This term dominates the live stable book. See [§8](#8-what-the-number-users-call-spread-actually-is).

---

## 4. Theorem 1: the spread is direction-invariant

**Claim.** For any listed pair $(X, Y)$, $S(X \to Y) = S(Y \to X)$ at the same block and the same pool state, for any trade size.

**Proof.** $S$ is a function of exactly five quantities: $\sum_i f_{\min,i}$, $\sigma_p$, $\nu_p$, $\sum_i u_i$, $U_{\text{stale}}$ (`_pathSpread`). Each is built by one of two constructions:

1. A fold over the leg multiset with a **commutative, associative** operator: $\sum$ over $\{f_{\min,i}\}$, $\{u_i\}$ and the per-leg staleness surcharges, and $\sum$ of squares for $\sigma_p$ (`_walkLegs`).
2. A `max` over the **unordered pair** $\{$`cIn`, `cOut`$\}$: $\nu_p$ alone (`_quotePath`).

Reversing the swap reverses the hop order. The route between $X$ and $Y$ is the unique tree path, so the reversed route is the same edge set traversed backwards, and the per-leg profile asset is a property of the **edge** (`_executeLeg`: `profileAsset = isUpward ? from : to`, which is the child endpoint of the edge either way). The leg multiset is therefore identical. A commutative fold over an identical multiset returns an identical value; a `max` over an unordered pair likewise. Hence all five inputs are identical and $S$ is identical. $\blacksquare$

**The theorem is robust to how the aggregates are built.** $\sum$, $\sqrt{\sum(\cdot)^2}$ and $\max$ are all symmetric functions of the leg multiset, so any fold drawn from that set preserves direction invariance exactly. Any new term must be a symmetric fold.

**Measured.** Three stable crosses, both directions, one block: 997/997, 196/196, 885/885 PBPS. Zero divergence.

**Consequence.** No part of the bid/ask asymmetry can come from the spread. Everything asymmetric is in the mid (skew, [§7](#7-bid-ask-asymmetry)) or in the toll (Theorem 2, [§6.4](#64-theorem-2-the-toll-is-the-only-asymmetric-charge)).

---

## 5. The fee

### 5.1. Order of operations

`Pricing._settleQuote` (`Pricing.sol`) settles in exactly this order:

```text
spreadPbps = _pathSpread(acc)
covToll    = _covToll(cOut, currentAmount)                  // terminal asset only
currentAmount -= covToll
feeOut     = currentAmount * spreadPbps / (2 * PBPS)
(protoFee, lpFee) = splitFee(feeOut, protoSharePct)
amountOut  = currentAmount - feeOut
```

So, with $y_g$ the gross output from the leg walk:

$$\phi_{\text{toll}} = T(y_g), \qquad \phi = \Big\lfloor \frac{(y_g - \phi_{\text{toll}})\cdot S}{2B} \Big\rfloor, \qquad y' = y_g - \phi_{\text{toll}} - \phi$$

Both charges are in **output token units**. The fee base is post-toll, not gross. The fee is charged **once per swap**, not once per leg: the spread is already a path aggregate.

The multiplication precedes the halving (`* spreadPbps / (2 * PBPS)`, one division) so $S = 1$ PBPS does not truncate to zero on any economically relevant size.

### 5.2. Why there is no input-side skim

The gross output $y_g$ is priced off the **full** `amountIn`: the spline contributes price impact only, no fee. An input-side half-spread would therefore be phantom revenue, crediting the trader the full input in the gross while withholding part of that input. That is an LP-reserve drain into the treasury, not a fee, so the charge is one-sided by construction.

An integrator deriving a two-sided quote from `SwapQuote.spreadPbps` must apply $S/2$ per side, not $S$. The ABI comment at `IPool.sol` states it normatively: `protoFee + lpFee` is the whole fee, denominated in `tokenOut`, and an indexer must credit 100% of it to `tokenOut` and never to both legs.

### 5.3. Protocol split and settlement

$$\phi_p = \Big\lfloor\frac{\phi \cdot r}{100}\Big\rfloor, \qquad \phi_l = \phi - \phi_p$$

`Pricing.splitFee`, `protoSharePct` a `uint8` with base 100. Live: $r = 20$, so 20% protocol / 80% LP.

Settlement is endpoint-only (`PoolIOLib.settle`, `PoolIOLib.sol`):

```text
aIn.reserves  += amtIn
aOut.reserves -= amountOut + protoFee
protocolFees[tkOut] += protoFee
accrueLpFee(aOut, tkOut, lpFee)
```

| Quantity | Reserve effect (out leg) | Liability effect (out leg) | Coverage effect |
|---|---|---|---|
| $\phi_p$ (protocol) | $-\phi_p$ from reserves, moved to `protocolFees` | none | $c$ falls |
| $\phi_l$ (LP) | stays in reserves | $+\phi_l$ (`PoolLiquidity.accrueLpFee`) | $c$ **unchanged**, LP claim rises via `liquidityIndexWad` |
| $\phi_{\text{toll}}$ | stays in reserves | none | $c$ **rises**: this is the surplus |

The LP fee is booked as an LP **claim**, not pool surplus: `accrueLpFee` raises `liabilities` by exactly `lpFee` and raises `liquidityIndexWad` in the same step (`INDEX_REASON_FEE`), leaving $c$ unchanged. The only charge that improves coverage is the coverage toll, withheld with no matching liability credit.

Consequence for the toll's round-trip identity: $Q$ telescopes exactly only at constant $L$, and `accrueLpFee` moves $L$ mid-loop. The residual is pool-favorable (higher $L$ $\Rightarrow$ lower $c$ $\Rightarrow$ more toll), documented at `Pricing.sol`.

### 5.4. Worked example

1 ETH into a WETH/USDC pair, mark 2000 USDC/ETH, $S = 5000$ PBPS (0.5%), $\kappa_{\text{cov}} = 0$ on USDC (illustration only; policy is $\kappa > 0$ everywhere), $r = 20$.

$$y_g \approx 2000, \qquad \phi_{\text{toll}} = 0, \qquad \phi = \frac{2000 \cdot 5000}{2\cdot 10^6} = 5, \qquad y' = 1995$$

$\phi_p = 1$ USDC leaves reserves for `protocolFees`; $\phi_l = 4$ USDC stays in reserves and is credited to LP liabilities. The trader pays 25 bp, which is half the 50 bp round-trip width.

### 5.5. There is exactly one charge

The complete list of what one swap is charged:

- **One charge, the half-spread, once per swap.** A round trip therefore pays the full spread $S$, which is what makes $S$ a round-trip width and not a per-swap fee.
- **No half-spread is embedded in the executed price.** At zero size the buy and the sell branch return the identical expression, so $\text{bid}(0) = \text{ask}(0) = \text{mid}$; with centered presets $\text{mid} = \text{mark}$ exactly ([Anchor Path Pricing §3.1](/docs/1-1-3-anchor-path-pricing#31-one-pricing-law-per-edge)). The spline contributes price impact only.
- **`protoSharePct` splits that one pot**, it does not add to it ([§5.3](#53-protocol-split-and-settlement)).
- **There is no static fee.** Every term of $S$ is live.
- **The charge is output-side only.** A swap that also skimmed the input would pay the half-spread twice, which is the failure mode [§5.2](#52-why-there-is-no-input-side-skim) exists to rule out; `_settleQuote` touches `amountIn` nowhere.

### 5.6. What that costs a trader

All-in (spread plus impact) at par coverage, quiet tape and stressed tape:

| Pair | Legs | Quiet | Stressed |
|---|---:|---:|---:|
| USDT $\to$ USDC | 1 | 0.36 bp | 0.56 bp |
| USDT $\to$ DAI | 2 | 1.29 bp | 1.57 bp |
| WETH $\to$ USDC | 1 | 5.26 bp | 6.16 bp |
| WETH $\to$ USDC at $\sigma = 10\%$ | 1 | - | 10.2 bp |

These carry **no curve LVR**: the quote tracks the external mark instead of being arbed onto it ([Toxic Flow Mitigation](/docs/1-1-6-toxic-flow-mitigation)).

---

## 6. The coverage toll

The **coverage toll** is its own object, distinct from the coverage ratio it reads: a convex, charge-only levy on the output leg, computed by `Pricing._covToll` and reported as `SwapQuote.covToll`. Search for it, and price against it, under that name.

### 6.1. Definition

Let $R$ and $L$ be the **output** asset's reserves and liabilities, $c = R/L$ in WAD, and $y_g$ the gross output.

$$Q(c) = \ln c - c + 1, \qquad c_0 = \min\!\Big(\tfrac{R}{L}, 1\Big), \quad c_1 = \min\!\Big(\tfrac{R - y_g}{L}, 1\Big)$$

$$\Delta Q = Q(c_0) - Q(c_1), \qquad T(y_g) = \begin{cases} y_g & y_g \ge R \\ 0 & \Delta Q \le 0 \\ \min\!\big(\tfrac{\Delta Q \cdot \kappa_{\text{cov}} \cdot L}{\text{BPS}\cdot\text{WAD}},\; y_g\big) & \text{else}\end{cases}$$

`Pricing._covToll`, `_covQ` (`FixedPointMathLib.lnWad`). Returns 0 immediately when $\kappa_{\text{cov}} = 0$, $L = 0$ or $y_g = 0$.

$Q$ is concave with a unique maximum $Q(1) = 0$, and $Q(c) \to -\infty$ as $c \to 0$: a convex no-drain wall.

### 6.2. The three structural properties

**Charge-only.** $\Delta Q \le 0 \Rightarrow T = 0$. A coverage-restoring trade is never rebated, so no rebate ledger exists and a round trip strictly loses. This is what makes the toll safe to retain in reserves rather than express as a mark shift: a mark shift would be round-trip extractable.

**The $\min(c,1)$ clamp restricts $Q$ to its increasing branch**, so the toll prices exactly the below-peg deficit and the over-peg portion is free. Without it a drain starting over-covered bypasses the wall entirely; the argument is in [Inventory Management §6.2](/docs/1-1-1-inventory-management#6-convex-coverage-wall).

**Terminal-only, never per leg.** `_covToll` is called once, on `cOut`, in `_settleQuote`. Interior legs have $\Delta R = 0$ by construction (settlement is endpoint-only, `PoolIOLib.sol`), so $\Delta Q = 0$ on them; a per-leg toll would invent a charge for a displacement that did not occur. Stays correct at `MAX_DEPTH = 4`.

**Uncapped wall.** As $c_1 \to 0$ the toll saturates to $y_g$, so `amountOut` $\to 0$ and the drain halts gracefully rather than reverting. At $y_g \ge R$ the whole fill is tolled.

### 6.3. The marginal toll: a level shift, not a large-trade wall

Differentiate at $q \to 0^+$. With $c = R/L$ and $dc = -q/L$:

$$\Delta Q \approx -Q'(c)\,dc = \Big(\frac{1}{c} - 1\Big)\frac{q}{L}$$

$$\boxed{\;\lim_{q\to 0^+}\frac{T(q)}{q} = \frac{\kappa_{\text{cov}}}{\text{BPS}}\Big(\frac{1}{c} - 1\Big)\quad\text{for } c < 1, \qquad = 0 \text{ exactly for } c \ge 1\;}$$

The marginal rate is **strictly positive at zero size** whenever the output leg is under-covered. Dust pays it at the same rate a block trade pays on its first wei. It is a level shift keyed on the state of the terminal asset, plus convexity on top.

Measured on a **pre-Arc reference roster** with RLUSD at $c = 0.9575$ and $\kappa_{\text{cov}} = 100$ bps: $0.01 \cdot (1/0.9575 - 1) = 4.44 \times 10^{-4} = 4.44$ bp, against 4.44 bp read from the quote. RLUSD is not listed on the Arc fleet, and the live $\kappa_{\text{cov}}$ there is 600 on a stable leg, so the same coverage costs $0.06 \cdot (1/0.9575 - 1) = 26.6$ bp - six times the figure above, and 111 bp on an equity leg at 2,500. The arithmetic is what the row demonstrates; the parameters are not today's. That coverage is its own dated capture, not the snapshot tabulated at [Inventory Management §3.3](/docs/1-1-1-inventory-management#3-inventory-skew-coverage--skew). Any description of the toll as a "large trade only" convex wall is wrong: convexity is the second-order behavior, the level shift is the first-order one.

### 6.4. Theorem 2: the toll is the only asymmetric charge

Old P3 / Thm 2 ("base never walled", $\kappa_{\text{cov}}(\text{base}) = 0$ identically, sell-into-base toll-free) is **void**. It was a single-pool leftover. `_covToll` is output-only, so hub κ prices taking the hub **out**.

**Claim.** The coverage toll is charged once, on the terminal output. It is zero only when that asset's $\kappa_{\text{cov}} = 0$, or when the drain does not worsen below-peg coverage. Same-pool spoke→spoke never outputs the hub; a hop that extracts the hub does.

**Proof.** `_covToll` short-circuits when `cOut.kappaCovBps == 0` and is evaluated only on `cOut` (`_settleQuote`). `requireWallOk` does **not** reject $\kappa$ on `$.baseToken`. On $X \to \text{base} \to Y$ the terminal is $Y$, so hub κ does not fire on that same-pool cross. A swap whose output is the hub pays hub κ when $\kappa > 0$. $\blacksquare$

**Consequence, combined with Theorem 1.** Take a same-pool cross $X \leftrightarrow Y$ at one block. The spread is identical in both directions. The toll is $T_Y$ one way and $T_X$ the other. By [§6.3](#63-the-marginal-toll-a-level-shift-not-a-large-trade-wall) at small size that is

$$\frac{\kappa_{\text{cov}}}{\text{BPS}}\Big[\Big(\tfrac{1}{c_Y} - 1\Big)^+ - \Big(\tfrac{1}{c_X} - 1\Big)^+\Big]$$

A trade whose output is the hub pays $T_{\text{hub}}$ when hub $\kappa > 0$.

**Interior vs hub.** A non-base interior can carry $\kappa_{\text{cov}} > 0$ and still be tolled at zero, because it is interior ($\Delta R = 0$), not because it is unwalled. The hub is not interior on a hop that extracts it.

### 6.5. Coupled configuration

One coupling is enforced on chain, at every write:

- $\kappa_{\text{cov}} > 0 \Rightarrow$ `haircutSuppressorBps = 0` (Lemma B of the published coverage proofs), held by the single predicate `PoolConfig.requireWallOk` (`PoolConfig.sol`, reverts `InvalidInput`), called from `setAssetParams`, `setRiskConfig` and `initAsset` (which zeroes the `BPS` default when a walled asset is listed). A suppressed haircut leaves a toll-exempt withdrawal bypass: an LP could exit the deficit the wall exists to price. Two further arms bind the wall: $\kappa_{\text{cov}} > 0$ on every listed asset including the hub, enforced by `requireNeverDepletable` at every writer, and $\kappa$ cannot be stripped from an asset carrying a `FLAG_REQUIRES_WALL` preset. Canonical statement: [Invariants §I-9](/docs/1-1-8-invariants#i-9-coverage-toll-is-charge-only-and-terminal-only).

**Nothing on the depth axis pushes back against the wall.** The traverse denominator is the leg's raw reserves with a zero-guard (`depth = reserves == 0 ? 1 : reserves`), at every coverage. A denominator that grew as coverage fell would shorten the traverse on exactly the leg the wall defends, which is how a two-block round trip becomes trader-positive (worth $-4.0$ PBPS to the LP at $c = 0.60$). Coverage reaches the quote through the skew anchor and this toll, and through nothing else.

---

## 7. Bid-ask asymmetry

### 7.1. Where it comes from

The book is centered not on the mark $m$ but on the skew-loaded **mid** $r = m(1 + \delta(c))$, where $\psi$ is the `int8` inventory-skew index and $\delta$ the price displacement it buys. The spread is symmetric about $r$:

$$\text{bid} = r\Big(1 - \frac{S}{2B}\Big), \qquad \text{ask} = r\Big(1 + \frac{S}{2B}\Big)$$

The skew is piecewise-linear in the coverage ratio, clamped at $\pm 100$ and returned as an `int8` (`Pricing.computeInventorySkew`); it maps onto the spline's depth axis in `_skewToDepth`, displacing off the curve's stored density median at `BPS/200` per skew unit, so the book is quoted from the skew anchor outward and zero skew quotes the mark. It is a **fixed protocol law with no per-asset dial**, and its two arms are deliberately asymmetric rather than one slope rounded twice. Formula, slopes and saturation points: [Inventory Management §3](/docs/1-1-1-inventory-management#3-inventory-skew-coverage--skew).

Four asymmetry sources, ranked:

1. **Mid skew** $\delta(c)$: shifts the center. Coverage-worsening flow gets a worse price at the same spread. Cancels exactly on a round trip: it is a level, not a cost.
2. **Spline traverse direction**: the buy and sell legs integrate the same monotone curve over different intervals from the anchor. Size-dependent, endpoint legs only.
3. **Coverage toll** $T$: charge-only, terminal asset only, never rebated. Theorem 2.
4. **Spread**: contributes **nothing**. Theorem 1.

### 7.2. Inventory skew as a Glosten-Milgrom soft fee

The skew doubles as a toxicity-targeting soft fee in the sense of Glosten & Milgrom (1985): flow that persistently moves the maker's inventory one way is on average the informed flow, and should pay more than flow that round-trips. AIMM realizes this through the **skew slope**, not a flat spread.

$$P = m\,(1 + \delta(c)) \pm \frac{S}{2B}, \qquad \delta(c) = \frac{\psi(c)\cdot\kappa}{100\cdot B}$$

with $\kappa$ the dispersion. Because $m$ is a frozen external mark between pushes, $P$ drifts **only through realized inventory** $c$.

- Inventory-moving flow walks up the skew and pays $\delta$ on top of $f_{\min}$. That slippage is retained in reserves, so the pool captures the adverse-selection cost it would otherwise leak.
- Round-trip flow returns coverage to where it found it, $\delta$ nets to zero, and it pays only the floor.

This is why a competitive base fee can coexist with charging toxic flow: tune the defensiveness with $\kappa$ (how fast $\psi$ ramps), not with $f_{\min}$.

**Guardrail.** The construction is safe only because the drift is via realized inventory around a **frozen** mark. If the center drifted via a mutable internal accumulator that the swaps themselves push, the identical soft fee would be a flash-loan-manipulable surface. AIMM deletes that surface: the quote center is `FeedMathLib.mark()` = `mark1e18`, never a reserve- or trade-derived invariant, and the toll is charge-only so a round trip strictly loses.

---

## 8. What the number users call "spread" actually is

Composition of RLUSD $\leftrightarrow$ DAI at $S = 997$ PBPS (9.97 bp), captured on a **pre-Arc reference-roster** stable pool (neither symbol is listed on the Arc fleet; addresses: [2. Deployments](/docs/2-1-contract-addresses)). The decomposition is the point, not the roster:

RLUSD and DAI both anchor to the base, so the path is `RLUSD -> USDC -> DAI`: two legs, and the profile asset of each is the spoke. The floor is therefore $69 + 183 = 252$ PBPS. USDC's own 50 never enters, because `minFee` is per leg's profile asset and the base supplies no leg profile.

| Term | PBPS | Share | Basis |
|---|---:|---:|---|
| $f_{\min,p}$ | 252 | 25.3% | **configured**: $69_{\text{RLUSD}} + 183_{\text{DAI}}$, read off the roster |
| $S_{\text{vol}} - f_{\min,p}$ | ~2 | 0.2% | **computed**: $\sigma_p \nu_p / (100\cdot\text{BPS})$ at the quoted $\sigma$ |
| $U_{\text{conf}} + U_{\text{stale}}$ | 743 | 74.5% | **residual**: $S$ less the two rows above, not an independent reading |

**The last row is solved for, not measured.** $S = 997$ and the floor are both hard numbers (the floor is roster configuration and $S$ was read from the quote), so the surcharge total is what the identity leaves. It is reported as one figure for a second reason as well: the contract carries confidence and staleness in a single accumulator (`PathAccumulator.riskPath` holds $\sum_i(\text{stale}_i + u_i\cdot B/\text{BPS})$, and `_pathSpread` only ever consumes the sum, [§3.2](#32-the-five-inputs-and-how-they-aggregate)), so no quote splits them either. Splitting the 743 requires per-feed `confidenceBps` and mark age off chain, at the same block. The block noted below could not be re-quoted, so that split is not restated here.

**Three quarters of the quoted spread is an oracle-health readout, not a fee.** The surcharges describe how much the pool distrusts its own mark; neither is compensation the LP asked for. A pool whose keeper is healthy and whose feed reports a tight CI quotes near $f_{\min,p}$, which on this pair is 2.52 bp round trip, 1.26 bp charged.

Track the surcharge share as an operational metric, not revenue.

> Re-verification of these numbers at block 11,456,319 was not possible: every stable-pool feed was `StaleData(34600, 7200)` (age 34,600 s against a 7,200 s TTL), so `getSwapQuote` reverted rather than quoting. The pool was correctly fail-closed; the keeper was not pushing.

---

## 9. Why the path fee floor sums over legs

**The invariant.** Each leg ships $f_{\min,i} \ge 2\theta_i$, where $\theta_i$ is the keeper's configured per-feed deviation threshold in bps. The keeper machine-checks it at startup and hard-fails with no escape hatch (`keepers/src/oracle/startup.rs`, `minfee_covers_theta`): the on-chain invariant is convention only, so the gate lives in the pusher. This $\theta$ is the static config value the boot gate reads; the runtime push boundary is edge-relative and derived from the same floor ([§3.4](#34-staleness-surcharge)). Derivation: [Parametrization §11](/docs/1-1-7-parametrization#11-the-fee-floor-covers-2-theta).

**Why not $\max$.** The pick-off edge an adversary can realize is the **sum** of the per-leg thetas: the adversary picks the joint worst realization, and every mark on the path is independently allowed to sit $\theta$ away from truth. A $\max$ funds one leg and leaves the rest unfunded.

$$\max_i f_{\min,i} \quad\text{does not meet}\quad f_{\min,p} \ge 2\sum_i \theta_i$$

**Measured on the reference roster** ([2. Deployments](/docs/2-overview)). 15 spokes, 105 crosses, at the shipped floors and thetas ([2. Deployments](/docs/2-overview)):

| | |
|---|---|
| Crosses that a $\max$ rule would under-fence | **72 of 105** |
| Worst pair | (U, USDTB) |
| Its floor under a $\max$ rule | $\max(73, 78) = 78$ PBPS |
| Its requirement | $2(0.365 + 0.365)\cdot 100 = 146$ PBPS |
| Deficit | 68 PBPS = **0.68 bp per pick-off**, repeatable, size-unbounded |
| Crosses under-fenced under the shipped $\sum_i f_{\min,i}$ | **0 of 105** |

The sum rule clears every pair on the roster with no parameter change: $73 + 78 = 151 \ge 146$. A $\max$ rule measures **+20,096 PBPS** for a cross-spoke round trip with both marks walking $\theta$ against a per-leg $2\theta$ fence. The full aggregation is at [§3.2](#32-the-five-inputs-and-how-they-aggregate).

$\sigma$ composes in quadrature rather than as a sum because independent leg innovations add in variance, not in level. NXR already composes bridged pairs this way. `minFee` and `confidence` add in level because both bound a worst-case displacement, not a variance.

**The floor, the quadrature and the per-leg staleness sum are one mechanism.** A path whose floor sums but whose staleness couples charges a mixed-freshness path about 10 bp too little; the reverse pairing over-charges a fresh leg for its neighbor's lag. The sum/quadrature aggregation stands alone, with no clamp above it ([§3.1](#31-definition)).

**Overflow discipline.** The accumulators are `uint256` in memory (`Pricing.PathAccumulator`), so the leg sums cannot wrap. There is exactly one narrowing on the path, in `_pathSpread`, into `SwapQuote.spreadPbps` (`uint16`, 6.55% ceiling), and it **saturates explicitly**. At eight legs a summed floor passes `uint16` easily; an unchecked cast would turn a maximally fenced path into a near-zero fee. Pinned by a spread-composition regression test.

Saturation cannot eat the security floor. The raw spread is bounded below by the composed interior fence, which the swing cap bounds at $6 \times 10{,}051 = 60{,}306$ PBPS, so what saturates away is the $\sigma$/CI/staleness **surcharge**. That bound holds by construction and needs no runtime check to carry it; it is pinned by value in `test_the_interior_fence_ceiling_fits_the_uint16_spread`, which is where a future `MAX_DEPTH` or swing-cap change fails.

---

## 10. Multi-hop

### 10.1. Routing

**Shipped**: the multi-anchor tree, bounded at 9 nodes and **8 legs**. The route is the unique tree path, so a quote cannot depend on a routing choice. Topology, the depth arithmetic and what the `anchor` column configures are stated once at [Anchor Path Pricing §1](/docs/1-1-3-anchor-path-pricing#1-topology) and [§1.1](/docs/1-1-3-anchor-path-pricing#11-what-the-anchor-column-configures). **Every summed budget on this page is sized against 8 legs.**

Cycle safety at any depth follows from the tree structure: a closed trading cycle is a closed walk in a tree and crosses every edge equally often up and down, so per-edge safety gives cycle safety at any length. The three per-edge conditions are stated at [Anchor Path Pricing §4](/docs/1-1-3-anchor-path-pricing#4-cycle-safety).

### 10.2. Leg pricing

**Endpoint legs** carry full price impact: spline traversal plus reserve accounting (`_priceEdgeHop`, `Pricing.sol`).

**Interior legs** price at mid with inventory skew and carry **no** impact.

Skew belongs on an interior leg because it is pricing, not a charge for inventory change: an under-covered pivot is worth less to the pool, and pricing the hop at the undepressed mark hands the trader the pivot's depeg risk for free.

It cancels on a round trip **only against unchanged interior coverage**. The two crossings read the same skew level only if nothing moved the interior node's coverage in between; an interposed trade that shifts it leaves an extractable residue (+2.96 bp measured under `max` aggregation, +1.01 bp under sum).

**Shipped fence.** An interior leg's floor is $\max(f_{\min,i}, F_i)$, not bare $f_{\min,i}$, with

$$F_i = \left\lceil\frac{w_i \cdot B}{B - \bar w/2}\right\rceil, \qquad w_i = \left\lceil\frac{\Sigma_i \cdot \kappa_i}{Q \cdot \rho_i}\right\rceil, \qquad \bar w = 10{,}000\ \text{PBPS}$$

computed **per quote** (it reads the leg's live dispersion $\kappa_i$ and its curve's span $\Sigma_i$) and taken as a **ratio** over a constant low multiplier, because what the manipulation extracts is a price ratio. `Pricing._interiorMidAndFence`, `_fenceOfSwingPbps`, floor applied in `_priceInteriorLeg`; the constants are file-private to `Pricing.sol`. The swing $w_i$ is **peak-to-peak**, so $\bar w$ is a 1%-of-mark total range and $\pm 0.5\%$ one-sided; reading it as a one-sided 1% doubles every fence figure.

The fence is a fee floor and nothing else: it folds into the leg's floor and sums into `minFeePath` like any other leg floor, with no separate accumulator and nothing above it that could clamp it away. Its per-leg and composed ceilings, and why saturation cannot eat them, are in [§9](#9-why-the-path-fee-floor-sums-over-legs).

Two properties do the work, and neither reads a value an admin can write: the stored curve is **centered at the write** (`NUQuartic.set`, `NUQuartic.sol`), and a swing past the cap **reverts** rather than clamping, with the dispersion band bound to `Pricing.dispersionCap` at every write path so a live asset never reaches the revert. See [Anchor Path Pricing §3](/docs/1-1-3-anchor-path-pricing#3-leg-pricing) for the derivation, the rejected alternatives, and the five earlier fences that did not hold.

Impact must **not** be charged on an interior leg: $\Delta R_{\text{interior}} = 0$ under endpoint-only settlement, so an impact charge invents a cost for a displacement that did not occur and replaces an exact identity with a size coincidence. The revenue an interior leg owes is collected by the fee sum in [§3.2](#32-the-five-inputs-and-how-they-aggregate), which is why the sum rule is required, not optional.

Every node on the path, interior included, is checked three ways:

- **Halt bits.** `HALT_MASK` (the risk and guardian halt bits, set by `haltAsset(pool, token, src)`) is tested per hop at `_quotePath`.
- **Feed-relative depeg band.** Tested per hop by `PoolIOLib.priceBandGuardPath`, which runs `priceBandGuard` on every interior node except the base, and by `PoolIOLib.priceBandGuard` on both endpoints.
- **Base parity halt.** Tested wherever the path touches the base: `_readBasePriceOrHalt` is gated on `hop == $.baseToken` (`Pricing.sol`) and reverts `BaseDepegged` past `BASE_DEPEG_HALT_BPS` = 500 bps.

**It is the ref band, not the parity halt, that scales with depth.** Parity tests the base mark against `1e18` and nothing else, at most once per path, which is exactly why `priceBandGuardPath` skips the base rather than double-charging a cold ref-feed round trip on the one node every path transits. Neither is a parent-depeg breaker: the ref band is a same-unit agreement check between two independent attestations of one pair and cannot express a peg test ([Anchor Path Pricing §7.1](/docs/1-1-3-anchor-path-pricing#71-what-reffeedid-is-and-what-it-is-not)).

### 10.3. Denomination

**Shipped**: the feed for asset $X$ must be attested in units of `assets[X].anchor`, and `mark = FeedMathLib.mark(feed)` with no correction. At depth $\ge 2$ a base-denominated rule is dimensionally wrong, not merely imprecise: `stETH -> ETH -> USDC` would compose $\text{stETH}\cdot\text{ETH}/\text{USDC}^2$.

`OracleConfig.quoteUnit` is a `uint8`. `QUOTE_UNIT_UOA = 1` (`PoolConstantsLib.sol`) is the **instruction "divide by the base mark"**, not a unit, so `PoolConfig` rejects it on any asset that does not anchor directly to the base (`PoolConfig.sol`), and a bridged leg's `minFee` must cover $2(\theta_{\text{child}} + \theta_{\text{parent}})$. There is no re-denomination helper on the path: the division is inline in `_legMarkAndFees` (`Pricing.sol`), reusing the gated, depeg-banded base read the path already performs.

---

## 11. Shipped vs decided

| Behavior | State | Reference |
|---|---|---|
| Fee once, output-side, $S/2$, post-toll | shipped | `Pricing._settleQuote` |
| Toll charged before fee, terminal asset only | shipped | `_covToll` |
| $\kappa_{\text{cov}} > 0$ on every listed asset, the base/hub included (old P3 "base never walled" is void) | **shipped**, enforced | `PoolConfig.requireNeverDepletable` rejects $\kappa = 0$ at every writer; `requireWallOk` only enforces $\kappa > 0 \Rightarrow \text{suppressor} = 0$ |
| Direction-invariant spread (Theorem 1) | shipped | `_pathSpread` |
| $\min(c,1)$ clamp on the toll | shipped | `_covToll` |
| Sum / quadrature path aggregation | **shipped** | `_walkLegs`, `_pathSpread`, [§9](#9-why-the-path-fee-floor-sums-over-legs) |
| No cap of any kind on the composed spread | **shipped, deliberate** | [§3.1](#31-definition); trader protection is `minAmountOut` |
| Per-leg staleness surcharge, summed | **shipped** | `_walkLegs` |
| Anchor-unit marks consumed as attested, `quoteUnit` | **shipped** | [§10.3](#103-denomination) |
| `MAX_DEPTH = 4` multi-anchor tree (8 legs, 9 nodes) | **shipped**, activation gated | `AnchorTreeLib.sol`, [Anchor Path Pricing §10](/docs/1-1-3-anchor-path-pricing#10-implementation-status) |
| Interior leg priced by the same `_legMid` as the terminal leg | **shipped** | `Pricing.sol` |
| Interior-leg manipulation fence in the path floor | **shipped** | `_interiorMidAndFence`, `_priceInteriorLeg`, [§10.2](#102-leg-pricing) |
| Dispersion band bound to the preset's fence cap at the write | **shipped** | `Pricing.dispersionCap`, `PoolConfig.sanitizeDispersion` |
| Re-anchor timelocked at the base-migration tier, atomic with the oracle config | **shipped** | `requestOp(..., UPDATE_ANCHOR, ...)` / `Admin.executeAnchorUpdate` |
| Parent-depeg breaker (`absFeedId`, or a split `refFeedId`) | **not designed into any field** | [Anchor Path Pricing §7.1](/docs/1-1-3-anchor-path-pricing#71-what-reffeedid-is-and-what-it-is-not) |

### 11.1. Open items

- **Full-drain toll yields a zero-output view quote (exec-side fixed).** `_covToll` returns `grossOut` when `grossOut >= R` (`Pricing.sol`). The executing path is closed: `Pricing.swap` reverts `Err.ZeroValue` on `out == 0` **before** settlement (`Pricing.sol`; see [Slippage & Price Impact §4.1](/docs/1-1-5-slippage-price-impact)), so a fully-tolled wall drain reverts instead of consuming input for nothing. What remains is the **view-quote shape**: `getSwapQuote` returns a fully populated `SwapQuote` with `amountOut == 0` rather than surfacing the failure, so a caller that skips an explicit `amountOut > 0` check sees an executable-looking quote that can never fill.
- **Re-anchoring is timelocked and atomic with the oracle config.** It queues at the `CRITICAL` tier with the anchor and the `OracleConfig` in **one inseparable payload** (`requestOp(..., UPDATE_ANCHOR, ...)` → `Admin.executeAnchorUpdate`), because re-anchoring $X$ to $P$ while $X$'s feed is still attested in base units mis-prices the leg by the parent's price. There is no untimelocked anchor writer; the only immediate path is the guardian's `Admin.collapseAnchor`, which may move a leg toward the root only and halts it in the same write.
- **The reserve clamp binds on the delivering leg only.** `_legScaleOut` caps output at the destination's reserves only when the leg is the one delivering (`Pricing.sol`). Applied to every leg it would clamp an interior hop against a hub balance the swap never spends, and a large cross would silently quote short. The decimal rescale runs per leg regardless.

---

### 11.2. Deploy-config constraint: `vegaBps`

> Canonical statement of this hazard. [Parametrization](/docs/1-1-7-parametrization#42-vega-volatility-sensitivity) and [Invariants I-11](/docs/1-1-8-invariants#i-11-path-spread-is-bounded-below-by-the-path-fee-floor) carry the shipped default and a pointer, not a second derivation.

**Live hazard, derived against HEAD.** `vegaBps` enters the quote at exactly two places, and the two have different scopes:

| Site | Which `vegaBps` | Scope |
|---|---|---|
| `_pathSpread` volatility band, $\sigma_p \nu_p / (100\cdot\text{BPS})$ | $\nu_p = \max(\nu_{\text{in}}, \nu_{\text{out}})$ | the two **endpoints** only, applied to the whole path's $\sigma_p$ |
| `_calculateDispersion`, per leg | the **profile asset's own** `Asset.vegaBps` | that leg alone |

The per-leg dispersion is correctly scoped: each leg shapes its own curve off its own asset's dial. The volatility band is not. It multiplies the endpoint maximum against $\sigma_p = \sqrt{\sum_i \sigma_i^2}$, which includes the $\sigma$ of legs whose own vega is lower. On a composite path, an interior or far-side leg's volatility is priced at a **stranger's** sensitivity dial.

With uniform vega the composite is never worse than the split, because quadrature $\le$ sum. With heterogeneous vega that ordering can invert, and the inversion scales with $\nu_{\max}/\nu_{\text{leg}}$ across the assets on the path. The `vegaHardMinBps` / `vegaHardMaxBps` fences in `RiskFences` do not bound it in general: they run only in `Admin.setAssetParamsBounded`, the risk-steward lane, which itself refuses to execute on an unseeded fence (`maxDeltaBps == 0` reverts `NotConfigured`), and the owner lane `Admin.setAssetParams` checks only the `minFeeHardMinPbps` floor. No deploy script calls `setRiskFences`, so on Arc the fences are unseeded and the only write-path bound on $\nu$ is `PoolConfig.validateAssetParams`: $\nu \ne 0$.

**The constraint was: keep `vegaBps` uniform across every asset in a pool. The live fleet no longer does.** Nothing in the contract enforces uniformity, and `Admin._isDefensiveTighten` treats a vega **increase** as defensive, so the untimelocked tighten path can introduce heterogeneity one asset at a time. Since 2026-09-04 Arc runs $\nu$ per class — 10,000 on stables and hubs, 4,500 metals, 4,000 crypto majors, 3,500 equities, 3,000 FX — because at a uniform 10,000 eight legs' live $\sigma$ put their book past the interior swing cap ([Parametrization §5](/docs/1-1-7-parametrization#5-dispersion)).

So the hazard above is exercised, and its size is the ratio: at most $10{,}000/3{,}000 = 3.3\times$ inside one pool today. It moves the $\sigma$ premium term only — the fee floor sums per leg and dispersion reads each leg's own $\nu$ — so what it can do is make a composite path quote wider than the same two legs traded separately, which a splitter can route around. It is a competitiveness cost taken to keep every leg quotable, not a safety hole, and it must be re-derived before any depth-2 listing, where the far-side leg stops being an endpoint.

## 12. Flash loan fee

$$\phi_f = \Big\lfloor\frac{x \cdot f_f}{B}\Big\rfloor$$

`Flash.flashLoan`, `flashFeePbps` a per-pool `FeeParams` field. Ceiling `MAX_FLASH_FEE_PBPS` = 10,000 PBPS = 1% (`PoolConstantsLib.sol`). No code-level default: the raw struct zero-initialises, so an unconfigured pool charges nothing; per-deployment values live in [2. Deployments](/docs/2-overview).

Flash loans are not tolled and not spread-charged: they do not change coverage over the transaction.

---

## 13. Comparison with other AMMs

| Protocol | Fee taken in | Charged | Distribution |
|---|---|---|---|
| Uniswap V2/V3 | 100% input | per hop | one-sided LP |
| Trader Joe V2 | 100% input | per hop | one-sided LP |
| Curve V1 | 100% output | per hop | one-sided LP |
| Wombat | input, redistributed | per hop | single-asset LP |
| **AIMM** | **100% output** | **once per swap, $S/2$** | LP claim via index; toll to coverage |

| Aspect | Uniswap V3 | AIMM |
|---|---|---|
| Fee structure | fixed tiers 0.01-1% | multi-factor, $\sigma$ + CI + staleness |
| Direction dependence of the width | none | none (Theorem 1) |
| Inventory response | none | mid skew + terminal coverage toll |
| Quote center | pool reserves | external keeper mark, no write-on-swap |
| LVR mitigation | hooks only | fresh mark + $2\theta$ floor + staleness surcharge |

---

## 14. Economics

**LPs.** The LP share of the fee is credited as a liability and paid through `liquidityIndexWad`, so it is a claim, not surplus. Genuine surplus comes from the coverage toll and from retained skew slippage, both of which raise $R$ against unchanged $L$.

Surplus is not redeemable. At $c \ge 1$ withdrawal pays face only (`PoolLiquidity.applyHaircut` early return); over-coverage never pays an LP bonus. Surplus dissipates through negative-skew discounts to coverage-restoring flow, and any residual after the last LP exit stays in the pool. No salvage path, by design: index accretion would reopen an extraction surface.

**Traders.** Quotes ride a fresh external mark, so there is no hidden stale-mark tax beyond the priced $U_{\text{stale}}$. Coverage-improving trades get a better mid and pay zero toll. Cost is bounded by the caller's own `minAmountOut` ([§3.1](#31-definition)); the quote is not capped protocol-side. The one cost not obvious from a quote screen is the toll's level shift on an under-covered destination ([§6.3](#63-the-marginal-toll-a-level-shift-not-a-large-trade-wall)): it is in `SwapQuote.covToll`, and integrators should surface it separately from `spreadPbps`.

---

## 15. Implementation reference

| Function | File | Role |
|---|---|---|
| `_pathSpread` | `Pricing.sol` | $S_{\text{vol}} + U_{\text{stale}} + U_{\text{conf}}$, saturating narrow into `uint16` |
| `_staleTerm` | `Pricing.sol` | $z\sigma_i\sqrt{\tau_i}/\text{BPS}$, per leg |
| `_settleQuote` | `Pricing.sol` | toll, then fee, then split |
| `_covToll` / `_covQ` | `Pricing.sol` | convex coverage toll |
| `_legMid` | `Pricing.sol` | the one offset law, interior and terminal alike |
| `_interiorMidAndFence` | `Pricing.sol` | interior mid + its manipulation fence |
| `dispersionCap` | `Pricing.sol` | write-path half of the fence: the band ceiling |
| `_priceInteriorLeg` | `Pricing.sol` | raises the leg floor to $\max(f_{\min,i}, F_i)$ |
| `splitFee` | `Pricing.sol` | protocol / LP division |
| `_walkLegs` | `Pricing.sol` | per-leg fold: sums, quadrature |
| `swap` | `Pricing.sol` | the single state-mutating swap entry |
| `FeedMathLib.gate` | `FeedMathLib.sol` | stale / dead / uncertain / paused halts |
| `PoolIOLib.settle` | `PoolIOLib.sol` | endpoint-only settlement (there is no separate `exec`) |
| `PoolLiquidity.accrueLpFee` | `PoolLiquidity.sol` | LP fee to liabilities + index |

---

## 16. Related documentation

- [Inventory Management](/docs/1-1-1-inventory-management): coverage ratio, haircut
- [Liquidity Shaping](/docs/1-1-2-liquidity-shaping): dispersion law and the spline
- [Feed Oracle](/docs/1-2-2-internal-oracle): mark, $\sigma$, confidence, push API
- [Toxic Flow Mitigation](/docs/1-1-6-toxic-flow-mitigation): LVR/OEV budget, the $2\theta$ fence
- [Parametrization](/docs/1-1-7-parametrization): every parameter, its fence, its live value
- [Invariants](/docs/1-1-8-invariants): settlement and liquidity-floor invariants

## 17. References

- Glosten, Milgrom, *Bid, Ask and Transaction Prices in a Specialist Market with Heterogeneously Informed Traders*, JFE 14(1), 1985. [doi:10.1016/0304-405X(85)90044-3](https://doi.org/10.1016/0304-405X(85)90044-3)
- Avellaneda, Stoikov, *High-frequency trading in a limit order book*, Quantitative Finance 8(3), 2008. (Reservation price and the $\sigma\sqrt{\tau}$ inventory term.)
- [Relation between Bid-Ask Spread, Impact and Volatility in Double Auction Markets](https://www.cfm.com/wp-content/uploads/2022/12/238-2006-Relation-between-Bid-Ask-Spread-Impact-and-Volatility-in-Double-Auction-Markets.pdf)
- [Volatility, Information Feedback and Market Microstructure Noise](https://www.cb.cityu.edu.hk/ef/doc/2016%20Sofie/Papers/152_Hautsch_Volatility,%20Information%20Feedback%20and%20Market%20Microstructure%20Noise%20A%20Tale%20of%20Two%20Regimes.pdf)
- [Inventory Risk and Market Making](https://www.sciencedirect.com/science/article/abs/pii/S1059056023001533)
