---
title: "Configuration parameters"
description: "Every AIMM parameter: its units, its formula, its fence, and its live value"
audience: tech
type: reference
status: live
lang: en
updated: "2026-09-04"
publish: true
---
# Configuration parameters

Every knob a pool carries is listed here with its units, the formula it feeds, the bound that constrains it, and the value the reference deployment ships. It is also the map of who may write what: four lanes with different authority and different latency, and a fence set that decides which of them can move a parameter without a timelock. Quantities that are not tunable are marked as such; several of the dials operators expect to find are protocol constants.

Values are the shipped reference roster, and they are a **worked example of one deployment, not a fleet-wide statement**: the fence arithmetic is legible on them, and every chain carries its own roster. Per-deployment values, including the chains still being stood up: [2. Deployments](/docs/2-overview). Units: PBPS ($10^6$), BPS ($10^4$), WAD ($10^{18}$), or raw.

## 1. Contents

- [Who may set what](#2-who-may-set-what)
- [Asset configuration](#3-asset-configuration)
- [Sensitivities: vega, haircut](#4-sensitivities)
- [Dispersion](#5-dispersion)
- [Risk configuration](#6-risk-configuration)
- [Oracle configuration](#7-oracle-configuration)
- [Fee parameters](#8-fee-parameters)
- [Pricing curve presets](#9-pricing-curve-presets)
- [The fence set](#10-the-fence-set)
- [The fee floor covers 2 theta](#11-the-fee-floor-covers-2-theta)
- [Reference parameter table](#12-reference-parameter-table)
- [Units reference](#13-units-reference)
- [Known gaps](#14-known-gaps)

---

## 2. Who may set what

Four lanes write parameters, each with its own authority and bounds. Every parameter belongs to exactly one primary lane.

| Lane | Authority | Bound | Latency |
|---|---|---|---|
| **Owner, immediate** | `_onlyAdmin` | `isDefensiveTighten` must hold, plus the armed `minFeeHardMinPbps` floor | 0 |
| **Owner, timelocked** | `_onlyAdmin` | none beyond validation | 1-7 days by op |
| **Steward, bounded** | risk keeper key | `RiskFences` hard bounds + a relative step clamp | 0 |
| **Keeper, off-chain** | keeper config only | never written on chain | n/a |

**Owner-immediate is not unbounded.** `Admin.setAssetParams` applies a write immediately only if `bootstrapSealed[pool]` is false, or if `Admin._isDefensiveTighten` returns true:

- `minLiquidity` unchanged
- `minFeePbps` non-decreasing
- `vegaBps` non-decreasing
- `haircutSuppressorBps` unchanged

Anything else queues. Independently, an armed `minFeeHardMinPbps` fence floors even the owner path: lowering below a fence requires two transactions, `setRiskFences` first.

**The instant lane needs no ceiling of its own.** The two-sided `minFeePbps` bound of §3.1 applies on every write path whichever lane the write arrives on, so it binds the untimelocked lane as tightly as the timelocked one. Raising it would need a lane-specific bound shipped with it.

`vegaBps` is direction-only in that predicate because it is magnitude-bounded downstream: its two outputs are the $\sigma$-scaled spread term, bounded by the uint16 `spreadPbps` field, and `_calculateDispersion`, bounded by the protocol-wide `MAX_DISPERSION_PBPS` = 900000 before it reaches the price. A raise widens the quoted band against the same $\sigma$, which is defensive on both.

**Never auto-moved.** `minLiquidity` is owner-only: `setAssetParamsBounded` reverts outright on any change to it. `haircutSuppressorBps` is steward-writable but is always relatively clamped and never exempted by the tighten predicate (§4.3).

**Timelocked operations.** All are queued by the generic `Admin.requestOp(pool, opType, subject, payload)` and cancelled by `Admin.cancelTimelock(pool, opType, subject)`; only the execute side is named per op. `opType` is `IPool.OpType`, `subject` is the left-padded token address for token-keyed ops, the preset id for `UPDATE_CURVE`, and ignored for the three pool-wide ops (`MIGRATE_BASE_TOKEN`, `UPDATE_TREASURY`, `UPDATE_FEES`). Delays are deploy-time data: a packed `Constants.Tier` schedule handed to the `AccessControl` constructor, read via `Constants.delayOf`. `Constants.PROD_DELAYS` is CRITICAL 7d, HIGH 3d, BASE 2d, LOW 1d, UPGRADE 7d, ROTATION 7d, FACTORY 14d.

| `opType` | Tier | Delay | Execute | Effect |
|---|---|---|---|---|
| `ADD_ASSET` | LOW | 1 hour | `executeAddAsset` | list a new leg |
| `UPDATE_PROFILE` | LOW | 1 hour | `executeUpdateProfile` | repoint `presetId` and the dispersion band |
| `UPDATE_CURVE` | LOW | 1 hour | `executeSetCurve` | install or refit a shared preset (propagates to every asset pointing at it) |
| `UPDATE_RISK` | LOW | 1 hour | `executeUpdateRiskConfig` | `RiskConfig`, i.e. `flags` and `kappaCovBps` |
| `UPDATE_FEES` | LOW | 1 hour | `executeUpdateFeeParams` | `protoSharePct`, `flashFeePbps` |
| `UPDATE_ORACLE` | BASE | 2 days | `executeOracleUpdate` | `OracleConfig` |
| `UPDATE_HOOK` | HIGH | 3 days | `executeSetAssetHook` | per-asset yield hook target and flags |
| `UPDATE_TREASURY` | HIGH | 3 days | `executeTreasuryUpdate` | fee-sink address |
| `UPDATE_ANCHOR` | CRITICAL | 7 days | `executeAnchorUpdate` | re-anchor, atomic with the oracle config |
| `MIGRATE_BASE_TOKEN` | CRITICAL | 7 days | `executeBaseMigration` | base token |

`UPDATE_ASSET_PARAMS` is queued only by `setAssetParams` itself and is not requestable through `requestOp`; it lands via `executeSetAssetParams`.

Immediate owner functions: `addAsset` and `setCurve` (pre-seal only), `setAssetParams`, `setAssetParamsBounded`, `setRiskFences`, `haltAsset` / `unhaltAsset` (bit 0 owner-or-guardian, bit 6 guardian), `batchRiskOp`, `setFlowCooldown`, `collapseAnchor`, `sealBootstrap`, `collectProtocolFees`.

Re-anchoring is never instant: a re-anchor is a re-rooting, so `UPDATE_ANCHOR` queues at the `CRITICAL` base-migration tier and carries the oracle config in the same inseparable payload. The only instant anchor move is the guardian's `collapseAnchor`, which may move a leg **toward the root** only, and halts it in the same call.

---

## 3. Asset configuration

`IPool.Asset`, stored at `PoolStorage.assets[token]`, written by `Admin.addAsset` / `setAssetParams`. Three storage slots. The packing is a quote-path decision: a quote reads exactly two cold `Asset` slots per leg, and the whole pricing shape (band, preset, fee floor, vega, decimals) arrives in one of them. Slot 2 has 16 free bits, the budget for any field a quote would read.

| Field | Type | Units | Meaning |
|---|---|---|---|
| `reserves` | uint128 | token wei | tokens the pool holds; also the traverse depth denominator |
| `liabilities` | uint128 | token wei | LP claim, raised by deposits and LP fees |
| `anchor` | address | - | parent in the anchor tree; 0 is not a sentinel, every listed leg has one |
| `minLiquidity` | uint96 | token wei | executable-reserve floor; swaps below it revert |
| `liquidityIndexWad` | uint96 | WAD | share-to-value index; `LIQUIDITY_INDEX_INIT_WAD = 1e18` |
| `minDispersionPbps` | uint32 | PBPS | dispersion floor and additive base; checked at the write under the preset's `dispersionCap`. There is no per-asset ceiling field (§5) |
| `presetId` | uint16 | pointer | index into the pool's shared curve table; 0 is refused at config, not a fallback |
| `minFeePbps` | uint16 | PBPS | per-leg spread floor at $\sigma = 0$ |
| `vegaBps` | uint16 | base 10,000 | volatility sensitivity |
| `haircutSuppressorBps` | uint16 | base 20,000 | withdrawal-haircut damper; forced to 0 on a $\kappa$-walled leg |
| `decimals` | uint8 | - | bound to the token at listing, not configurable; `decimals != 0` is the not-configured sentinel |
| `deadSeedPow10` | uint8 | power of ten | dead-share seed in the token's own unit; 0 = decimals default, bounded at `decimals + 3` |
| `flags` | uint16 | bitfield | feature and halt bits (§6.1) |
| `kappaCovBps` | uint16 | bps | convex coverage-wall strength (§6.2) |

That is the whole struct. The last two are the `RiskConfig` pair: `IPool.RiskConfig` is an ABI and
memory type only, and both of its fields are **stored here, in `Asset` slot 2**, not in a mapping of
their own. The inventory skew takes no per-asset field ([Inventory Management §3](/docs/1-1-1-inventory-management#3-inventory-skew-coverage--skew)), and the peg used by `INTERNAL` mode is a constant in the synthetic feed rather than a stored field (§7).

### 3.1. Config-time validation

Enforced on every write path (`PoolConfig.sol`):

| Rule | Rationale |
|---|---|
| `minFeePbps >= MIN_FEE_PBPS` (= 1) | 1 PBPS = 0.01 bp is the finest on-chain quantum |
| `minFeePbps <= ONE_PCT_PBPS` (= 10,000) | global write bound; replaces the per-asset ceiling and the instant-lane one at once (§2) |
| `haircutSuppressorBps < 20000` | at 20,000 the haircut zeroes and an under-covered leg withdraws at face |
| $\kappa_{\text{cov}} > 0 \Rightarrow$ `haircutSuppressorBps == 0` | otherwise a toll-exempt withdrawal bypass stays open (Lemma B of the published coverage proofs) |
| every listed asset including the hub: $\kappa_{\text{cov}} > 0$ | κ=0 is forbidden; `_covToll` is output-only so hub κ prices taking the hub out |
| $\kappa_{\text{cov}} = 0 \Rightarrow$ the asset's preset carries no `FLAG_REQUIRES_WALL` | stripping $\kappa$ would strand a wall-gated preset's concentrated tip with no drain defense; the gate is bidirectional, not assign-only |
| an asset may not be listed without a curve | one pricing law per edge; there is no shapeless fallback |
| `minDispersionPbps <= 900000` (`MAX_DISPERSION_PBPS`) and non-zero → default 1000 | keeps `_calculateDispersion`'s floor meaningful and the clamp branch well-defined (`PoolConfig.sanitizeDispersion`) |
| `minDispersionPbps <= Pricing.dispersionCap(preset)` | checked, never clamped: a floor past the cap configures a route outage at every σ, zero included, because an interior leg's swing would fail closed (§5) |

### 3.2. Liquidity floor

`minLiquidity` is enforced on **executable** liquidity ($R - R_{\text{invested}}$), pre-outflow, against `amountOut + protoFee + minLiquidity`:

```solidity
uint256 need = q.amountOut + q.protoFee + aOut.minLiquidity;
uint256 liq  = aOut.reserves > inv ? aOut.reserves - inv : 0;  // post-recall
if (liq < need) revert Err.InsufficientAmount(liq, need);
```

A fully hook-invested asset can hold $R \ge$ `minLiquidity` and still revert every swap. Full form: [Invariants I-14](/docs/1-1-8-invariants#i-14-executable-liquidity-floor-not-raw-reserves).

### 3.3. Depeg bounds live on the oracle config, not on the asset

A leg's depeg protection is **feed-relative**: `OracleConfig.refFeedId` + `refBandBps` measure the leg's mark against an independent reference feed and halt the swap past the band ([§7](#7-oracle-configuration)). A static per-asset price bound would be a number against a moving unit of account: a governance treadmill to keep correct, and a swap DoS when it is wrong. The discretionary "stop quoting this leg" call is the **guardian halt**: instant, owner-reversible, and not a number anyone has to keep current.

---

## 4. Sensitivities

### 4.1. Inventory skew: not a parameter

`Pricing.computeInventorySkew` takes `(reserves, liabilities)` and nothing else, and returns an `int8` in $[-100, +100]$ by both clamp and type. There is no slope to tune and no saturation point to declare: the two arm slopes and both clamps are protocol constants, so no write can reprice a live book by moving them, and the asymmetry between the arms is the round-trip conservation bound rather than an oversight. Formula: [Inventory Management §3.1](/docs/1-1-1-inventory-management#3-inventory-skew-coverage--skew).

**Why those constants.** Writing the saturation points as $[1/k, k]$, continuity requires $k \le 2$ and round-trip conservation requires $k \ge 2$, so $k = 2$ is the unique admissible value and there is no dial to expose. The two bounds, the measured leak across a discontinuous clamp and the real-tape evidence that the clamp region is unreachable are at [Inventory Management §3.2.1](/docs/1-1-1-inventory-management#321-why-saturation-sits-at-12-and-2).

**The skew anchor.** $\psi$ maps onto the spline as $x_0 = \mathrm{clamp}(x^* + \psi\cdot\text{BPS}/200,\ 0,\ \text{BPS})$, where $x^*$ is the curve's stored density median (§9), not the domain midpoint. The median enters as an offset at the $\text{BPS}/200$ slope and leaves `computeInventorySkew` untouched, because conservation binds the slope on both arms.

### 4.2. Vega: volatility sensitivity

One parameter, two outputs, both clamped. Base 10,000 = 1.0x.

**Dispersion** (`Pricing._calculateDispersion`):

$\kappa = \min\!\Big(\kappa_{\min} + \frac{\sigma\,\nu}{\text{BPS}},\; \texttt{MAX\_DISPERSION\_PBPS}\Big)$

**Spread** (`Pricing._pathSpread`):

$$S_{\text{vol}} = \sum_i f_{\min,i} + \frac{\sigma_p\,\nu_p}{100\cdot\text{BPS}}, \qquad \sigma_p = \sqrt{\textstyle\sum_i \sigma_i^2}$$

The divisors differ by $10\times$ deliberately: the same $\sigma$ moves dispersion a tenth as fast as it moves the spread. In `_pathSpread`, $\nu_p = \max(\nu_{\text{in}}, \nu_{\text{out}})$ over the **endpoints only**, and it is the one risk input that is not composed per leg (every other one, $\sigma$ in quadrature and the fee, CI and staleness terms by sum, is); in `_calculateDispersion` it is the profile asset's own $\nu$. Endpoint-max is correct there because $\nu$ is the pool's own per-asset sensitivity dial, not a per-leg risk quantity, and the endpoints are the assets actually paid in and out.

There is no hardcoded base in either formula. The dispersion base is the per-asset `minDispersionPbps`, the spread base is the path fee floor. Hardcoding a 1000-PBPS dispersion base would make tight stable bands (1-6 bp) unreachable at $\sigma \approx 0$.

> **Live on Arc** (block 60,255,318, 2026-09-04): $\nu$ is **per class**, not uniform. Stables and
> every pool's hub leg 10,000 (1.0x); PYUSD and FX 3,000; crypto majors 4,000; metals 4,500;
> equities 3,500. The cut is the swing cap's, not a taste change: dispersion carries $\sigma\nu/\text{BPS}$
> against a 1 %-of-mark ceiling (§5), and at $\nu = 10{,}000$ eight legs' live $\sigma$ already put
> their book over it. The same field also carries the spread's $\sigma$ slope at a $10\times$ smaller
> divisor, so the cut takes the volatility premium down with it; that conflict is one field serving
> two consumers and is not resolvable by any value (§4.2.1).

#### 4.2.1. Deploy constraint: keep vega uniform across assets

**The constraint was: move every leg's `vegaBps` together, or not at all. The live fleet no longer satisfies it.** Nothing in the contract enforces uniformity, and the untimelocked owner lane can introduce heterogeneity one asset at a time because a vega raise counts as defensive. The two scopes that make heterogeneity mispricing, the inversion it causes and its bound are derived once at [Spread & Fees §11.2](/docs/1-1-4-spread-fees#112-deploy-config-constraint-vegabps); the fence window that is the only thing bounding it is §10.1 here.

> **Live on Arc:** $\nu$ is per class (§4.2), so the endpoint-max scoping is exercised. The widest ratio inside one pool is $10{,}000 / 3{,}000 = 3.3\times$ (a stable leg against an FX leg in the crypto core), which is the bound on how far a composite path's $\sigma$ premium can be priced at a stranger's dial. It moves the premium term only: the fee floor sums per leg and is unaffected, and dispersion reads each leg's own $\nu$. Accepted deliberately, because the alternative was leaving eight legs over the interior swing cap. Re-check it before any depth-2 listing, where the far-side leg is no longer an endpoint.

### 4.3. Haircut suppressor

`PoolLiquidity.applyHaircut`, base 20,000. Linear, no power law:

$$h(c) = (1 - c)\cdot\Big(1 - \frac{\eta}{20{,}000}\Big) \quad\text{for } c < 1, \qquad h = 0 \text{ for } c \ge 1$$

Implementation: `factor = WAD - suppression*WAD/20000` (no `>= 20000 ? 0` branch - that range reverts at the writer), `haircutRatio = deficit*factor/1e18` (clamped at 1e18), `haircut = ceil(amount * haircutRatio / 1e18)`.

| $\eta$ | Factor | Effect |
|---|---|---|
| 0 | 1.0 | full linear haircut; **required on every $\kappa$-walled leg** |
| 10,000 | 0.5 | half haircut; the `initAsset` default |
| 15,000 | 0.25 | quarter |
| 19,999 | 0.00005 | maximum configurable |
| $\ge$ 20,000 | n/a | reverts `InvalidInput` (at `HAIRCUT_SUPPRESSOR_FULL_BPS`) |

The writers reject that range and `initAsset` seeds BPS, so the zeroing branch was unreachable; out-of-range stored state now underflows instead of silently zeroing the haircut. Fail closed, not open.

Worked: $c = 0.8$, $\eta = 10{,}000 \Rightarrow h = 0.2 \cdot 0.5 = 10\%$.

The haircut is one-sided. At $c \ge 1$ withdrawal pays face only; over-coverage surplus never pays an LP bonus.

> **Live on Arc:** $\eta = 0$ on every quoting leg of all four pools — full linear haircut fleet-wide. It is not a choice: `requireNeverDepletable` refuses $\kappa_{\text{cov}} = 0$ at every writer and `requireWallOk` then forces $\eta = 0$ wherever $\kappa_{\text{cov}} \ne 0$, so on a listed leg $\eta > 0$ is unrepresentable. The one non-zero value on chain sits on a halted leg carried from before the wall was armed.

`haircutSuppressorBps` is deliberately excluded from the steward's tighten predicate and is **always** relatively clamped: a drop is "tighten" for the pool but realizes LP loss on the spot, so exempting it would let the lower-trust steward zero it in one unbounded call while the owner lane queues 24 h. On the owner-immediate lane it is excluded outright: any change to it queues.

### 4.4. No directional term

Every spread term is symmetric: no momentum or trend surcharge, and nothing in the fee model reads the direction of the last trade. Residual staleness is priced by the symmetric $U_{\text{stale}}$ ([Spread & Fees §3.4](/docs/1-1-4-spread-fees#34-staleness-surcharge)).

---

## 5. Dispersion

Dispersion is the price half-width over which liquidity is deployed, in PBPS. It rises with $\sigma$ off the per-asset fitted floor $\kappa_{\min}$ = `minDispersionPbps`, one-for-one at $\nu = 10{,}000$ and at $\nu/\text{BPS}$ of that elsewhere (live: 0.30x to 1.0x by class, §4.2), and is clamped above by the protocol constant `MAX_DISPERSION_PBPS`, a read-path ceiling, not a per-asset field. Law and constant: [Liquidity Shaping §6.2](/docs/1-1-2-liquidity-shaping#62-live-dispersion-formula).

**The preset's fence cap bounds the floor at the write.** `Pricing.dispersionCap` is the widest dispersion whose interior mid swing the manipulation fence can bound, $\text{cap}\cdot\text{dispRef}\cdot Q/\text{span}$ with $\text{cap} = \texttt{INTERIOR\_SWING\_CAP\_PBPS} = 10{,}000$ PBPS, and `PoolConfig.sanitizeDispersion` checks `minDispersionPbps` against it at both write paths (`initAsset`, `setProfile`; revert, not clamp). There is no unset-preset case: an asset cannot be listed without a curve. Shipped caps: **5000** PBPS for presets 1, 2 and 5, **2500** for preset 3, **1000** for preset 4. Silently narrowing `minDispersionPbps` would move the asset's own quiet-tape quote, so a floor above the cap is a config error (`InvalidInput`). No shipped `minDispersionPbps` exceeds its cap (widest is KRW1 at 3740 under 5000). A σ-driven κ above the cap is not prevented - it fails closed on the interior swing (`Err.Overflow`) instead of ever mispricing.

**The swing is peak-to-peak**, so `INTERIOR_SWING_CAP_PBPS` = 10,000 PBPS is a 1%-of-mark total range and a $\pm 0.5\%$ one-sided displacement; reading it as a one-sided 1% doubles every fence figure. The fence, its constants and its ceilings: [Anchor Path Pricing §3.2](/docs/1-1-3-anchor-path-pricing#32-the-interior-fence).

The swing-cap check changes no configured value: it refuses only floors that could never quote safely. Above the floor, σ itself drives κ toward the protocol ceiling - at $\nu = 10000$ a ratio-2 stable (floor 161, cap 5000) hits its shape cap near $\sigma \approx 0.48\%$ and fails closed there, and a class carrying a lower $\nu$ buys headroom in exactly that ratio, which is what the live per-class cut is for (§4.2); the $\sigma$, CI and staleness terms of the spread keep widening regardless. `setCurve`'s "may not deepen a live preset" rule is exactly $\text{span}/\text{dispRef}$ non-increasing on a centered curve, so the cap is non-decreasing across any legal refit and no asset needs re-walking when a preset moves.

The deployed band is not a taste parameter: the risk keeper enforces a **hard floor** on every band it emits:

$$\kappa_{\min} \;\ge\; \theta + q_{99}(\delta) + \frac{f_{\min}}{100} \quad\text{(bp)}$$

$\theta$ is how far the mark may walk before a push triggers; $\delta$ is the excursion past $\theta$ measured over one push interval, so $q_{99}(\delta)$ is its 99th percentile, how far the mark actually gets before the push lands; $f_{\min}$ is charged inside the band and therefore consumes band width. Below this sum the price leaves the active range inside one push interval and the LP carries the LVR.

The floor is expressed in `minDispersionPbps` PBPS by multiplying by the preset's own scale, its `dispRefPbps` divided by its half-swing in bp, which is **not** always 100: preset 4 (half-swing 5 bp, `dispRefPbps` 100) is 20 PBPS per bp, preset 5 (half-swing 5 bp, `dispRefPbps` 500) is 100 (keeper fence).

A second, weaker gate, $S_{\text{dep}} \ge 2\theta$, is also checked by the keeper fence, but it binds nothing once $f_{\min} \ge 2\theta$ holds, because `minFee` alone already covers it.

The damped step function raises every emitted band to this floor, so a tightened gate can never stall the pool short of target: it is a floor on the step, not a test the step might fail.

> **Live on Arc `minDispersionPbps`:** stables 158-241, FX 1,472-3,740, crypto majors 1,842-2,034,
> metals **1,500 on both** (XAUT was 3,601 and PAXG 1,336 — the spread between two claims on the same
> metal was a thin-venue artefact, not two different books), equities **1,950** (was 2,500). Hub legs
> 200. The metal and equity cuts re-split the 1 %-of-mark book budget between the quiet-tape floor
> and the $\sigma$ slope, which is the other half of the vega cut in §4.2: floor down and slope down
> together, so every leg lands under the cap with at least $1.9\times$ headroom on its live $\sigma$
> instead of the $0.34\times$ XAUT carried. Per-leg values in [§12](#12-reference-parameter-table).

---

## 6. Risk configuration

`IPool.RiskConfig`, written by the timelocked `UPDATE_RISK` op (`executeUpdateRiskConfig`). Two fields, one word:

| Field | Type | Units | Meaning | Live |
|---|---|---|---|---|
| `flags` | uint16 | bitfield | feature and halt bits | `0x06` |
| `kappaCovBps` | uint16 | bps | convex coverage-wall strength | must be > 0 on every listed asset ([§3.1](#31-config-time-validation)); any live zero is a pending-`UPDATE_RISK` timelock artifact - see [2. Deployments](/docs/2-overview) |

Coverage reaches the quote through the fixed skew law (§4.1) and through $\kappa_{\text{cov}}$ (§6.2); neither takes anything else from this struct.

### 6.1. Flags

| Bit | Hex | Flag | Meaning |
|---|---|---|---|
| 0 | `0x0001` | `HALT_RISK_BIT` | per-asset risk halt (owner or guardian) |
| 1 | `0x0002` | `SWAP_ENABLED_BIT` | swaps |
| 2 | `0x0004` | `LIABILITY_SWAP_ENABLED_BIT` | liability swaps |
| 3 | `0x0008` | reserved | **unused**, the symbol does not exist. Do not set |
| 4 | `0x0010` | `FLASH_ENABLED_BIT` | flash loans |
| 5 | `0x0020` | reserved | **unused**, the symbol does not exist. Fee-on-transfer is handled unconditionally by balance-delta measurement in `PoolIOLib.pull`. Do not set |
| 6 | `0x0040` | `HALT_GUARDIAN_BIT` | guardian emergency halt |
| 7 | `0x0080` | reserved | **unused**, the symbol does not exist. Do not set |

`HALT_MASK = HALT_RISK_BIT | HALT_GUARDIAN_BIT = 0x0041`, checked at every value-moving gate including interior-hop transit, so the two halt bits can never silently diverge. There is one halt lever, `haltAsset(pool, token, src)` / `unhaltAsset(pool, token, src)`, and `src` selects which bit it moves: clearing one source never clears a halt another source set.

Common values: `0x02` swap only, `0x06` swap + liability swap (**the shipped reference value**), `0x16` swap + liability swap + flash. `0x07` sets `HALT_RISK_BIT`: it is a bricked config, not "swap + flash + liability". Never use it.

### 6.2. Coverage wall

Coverage reaches the quote through two charges and never through the depth axis. The traverse denominator is the leg's own raw reserves with a zero-guard and nothing else, so it is independent of $c$ and of $L$:

```solidity
uint256 depth = reserves == 0 ? 1 : uint256(reserves);
```

Inflating that denominator on an under-covered leg would shrink the spline interval a given size traverses and so reduce the impact charged on the trade draining it. The two charges are the skew anchor (§4.1) and the wall below.

$\kappa_{\text{cov}}$ (`kappaCovBps`, bps) scales the convex coverage toll $T$ ([Spread & Fees §6](/docs/1-1-4-spread-fees#6-the-coverage-toll)). Its practical meaning is the **marginal rate at zero size**:

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

At the class values live on Arc (block 60,255,318, 2026-09-04) — stables 600, FX 1,500, crypto
majors 2,000, metals 1,800, equities 2,500 bps, each pool's hub leg at the maximum of its own
spokes:

| $c$ | 600 (stable) | 1,500 (FX) | 1,800 (metal) | 2,000 (crypto) | 2,500 (equity) |
|---:|---:|---:|---:|---:|---:|
| 0.99 | 6.1 bp | 15.2 bp | 18.2 bp | 20.2 bp | 25.3 bp |
| 0.98 | 12.2 | 30.6 | 36.7 | 40.8 | 51.0 |
| 0.96 | 25.0 | 62.5 | 75.0 | 83.3 | 104.2 |
| 0.90 | 66.7 | 166.7 | 200.0 | 222.2 | 277.8 |
| 0.80 | 150.0 | 375.0 | 450.0 | 500.0 | 625.0 |
| 0.50 | 600.0 | 1,500.0 | 1,800.0 | 2,000.0 | 2,500.0 |
| $\ge 1$ | 0 | 0 | 0 | 0 | 0 |

This is a **level shift**, not a large-trade-only wall: dust pays the same first-order rate. Convexity is the second-order term on top.

**How the ladder is sized, and why it is not sized on recovery.** `_covToll` reads the *drained*
leg's $\kappa_{\text{cov}}$ and nothing else, so an arbitrageur facing a mark error $e$ (bps) drains
until the marginal toll equals it, i.e. to the equilibrium coverage

$$c_{\text{eq}} = \frac{\kappa_{\text{cov}}}{\kappa_{\text{cov}} + e}$$

Write $\rho(c) = c(-\ln c - 1 + c)/(1-c)^2$ for the fraction of that extraction the toll recovers.
$\rho$ is a function of $c$ alone — $\kappa_{\text{cov}}$ cancels — and it rises monotonically to
$\tfrac12$: $\rho(0.5) = 38.6\,\%$, $\rho(0.8) = 46.3\,\%$, $\rho(0.9) = 48.2\,\%$,
$\rho(0.99) = 49.8\,\%$. **The toll can never recover more than half the loss it prices, at any
$\kappa_{\text{cov}}$.** So a recovery target is not a design input, and a low recovery ratio is not
evidence the wall is under-sized. What $\kappa_{\text{cov}}$ actually buys is $c_{\text{eq}}$, and
therefore the residual: net LP loss $= e\,(1 - c_{\text{eq}})\,(1 - \rho(c_{\text{eq}}))$ bps of $L$.
The ladder is sized on that residual and capped from above by honest flow — the toll on a
1 %-of-leg trade starting exactly at the peg must not exceed the leg's own half-spread. The upper
bound is what binds on every class, so the shipped values are that bound, rounded. Two worked
points: a 3 % mark gap on a crypto leg cost 92 bps of $L$ at the previous $\kappa_{\text{cov}} = 300$
and costs 20.5 bps at 2,000; a 5 % gap on an equity leg cost 136 bps at 600 and costs 44 bps at
2,500.

**Sequencing.** The marginal toll rises with $\kappa_{\text{cov}}$, so raising it makes an
already-under-covered leg more expensive to drain — correct behaviour, and refilling stays free, but
it makes such a leg one-way until it is re-covered. Re-cover first, then raise.

$\kappa_{\text{cov}} = 0$ costs zero gas (`_covToll` returns immediately) and **is forbidden** on every listed asset including the hub. Old "volatiles run 0" is void.

**Know what κ=0 turns off.** The buy-side traverse saturates: past the domain clip marginal price impact is exactly zero, so the curve alone can never stop a drain ([Slippage & Price Impact §2.5](/docs/1-1-5-slippage-price-impact#25-the-buy-side-traverse-saturates-and-depth-cannot-stop-a-drain)). A leg left at $\kappa_{\text{cov}} = 0$ has no outflow defense beyond `minLiquidity` plus the reserve clamp - and `initAsset` writes `minLiquidity = 0` unless the deploy config raises it. Where the wall is on it dominates: a stable leg at $c = 0.80$ charges 150 bps of marginal toll at the live $\kappa_{\text{cov}} = 600$, against a spread of a few bps.

Coverage restoration takes no parameter at all. The withdrawal haircut writes the deficit down on every exit out of an under-covered leg, burning liabilities at full face while paying out less, so $c$ rises on the event that matters, with no clock, no flag and no governance-set rate ([Inventory Management §5](/docs/1-1-1-inventory-management#5-withdrawal-haircut)). The one writer that can lower `liquidityIndexWad` is `Pool.hookWriteDown`, which realizes an actual loss on invested reserves.

---

## 7. Oracle configuration

`IPool.OracleConfig`, written by the timelocked `UPDATE_ORACLE` op (`executeOracleUpdate`, BASE tier, 2 days).

| Field | Type | Meaning |
|---|---|---|
| `primary` | address | `ExternalOracle` carrying the mark |
| `feedId` | bytes32 | feed id on `primary` |
| `refFeedId` | bytes32 | reference feed for the feed-relative depeg band. There is no absolute per-asset band to fall back to: `PoolConfig.requireExternalSpokeBound` rejects an unarmed band on every non-base leg |
| `refPrimary` | address | oracle carrying the reference feed |
| `refBandBps` | uint16 | symmetric tolerance (200 = $\pm$2%); 0 disarms the guard and is legal on the base only, which carries the parity halt instead |
| `mode` | uint8 | 0 = `EXTERNAL` (recommended: any `IOracle`), 1 = `INTERNAL` (cash-collateral $1.0$ peg) |
| `quoteUnit` | uint8 | 0 = `QUOTE_UNIT_ANCHOR` (the norm), 1 = `QUOTE_UNIT_UOA` (unit-of-account bridge) |

Under `QUOTE_UNIT_ANCHOR` the feed is already attested in the asset's own anchor's units and the pool re-denominates nothing, which is the only dimensionally sound option past depth 1 ([Anchor Path Pricing §6](/docs/1-1-3-anchor-path-pricing#6-marks-and-denomination)). `QUOTE_UNIT_UOA` is the unit-of-account bridge, for legs whose mark can only be signed as `<TOKEN>-USD`; the pool divides out the base's own USD price at consumption, and it is legal only while the asset anchors **directly** to the base, enforced at config time.

`mode` selects where the mark comes from; the two modes are specified in [Oracles](/docs/3-4-oracles), and every shipped leg runs `EXTERNAL`. The bound worth stating on a parameter page is `INTERNAL` eligibility, enforced at config:

- `primary` and `feedId` must be live.
- `refFeedId` and `refBandBps` must be armed.
- `refBandBps` must be at most `MAX_STABLE_DEPEG_BAND_BPS` = 50, so a loosely or variably pegged unit (rebaser, FX, yield-bearing) cannot fit the band and is rejected.

There is no `Asset.pegB64` field: the peg is a constant in the synthetic feed, not stored per asset.

**Global oracle halts** (`FeedMathLib.gate`), all fail-closed:

| Condition | Constant | Error |
|---|---|---|
| guardian fast-freeze bit set | `FEED_HALT_BIT` | `FeatureDisabled(Err.Resource.FEED)` |
| `age > ttl` | per feed | `StaleData` |
| mark = 0 | - | `ZeroValue` |
| `confidence > 1000` bps | `MAX_CONFIDENCE_HALT_BPS` | `ThresholdViolation` |
| base mark off parity by > 500 bps | `BASE_DEPEG_HALT_BPS` | `BaseDepegged` |

Per-push acceptance band on the writer side, canonical statement: [Oracles §8.3](/docs/3-4-oracles#83-deviation-bounds). Kept facts: $\text{band} = \text{maxDeviation} + \min(6\sigma\sqrt{dt_{source}/1800}/100,\ 9\cdot\text{maxDeviation}) \le 10\cdot\text{maxDeviation}$; $Z = 6$ (6σ sanity cap), $T = 1800$ s (NXR Parkinson window), $X = 9$; $\sigma$ = stored prior (never the incoming push's own, else a signer inflates its own band); stored $\sigma$ floored at realized move, hence the cap (uncapped self-widens ~6x per max-band push).

> **Live `refBandBps`:** 150 bps on the 15 stable spokes (ref = `USDC-USD`), 200-300 on volatiles, 250 on FX legs. `quoteUnit` = 1 (`QUOTE_UNIT_UOA`) on every leg except USDC, WETH, WBTC, cbBTC and BNB, which is legal only because every leg on this roster anchors directly to the base (§12).

---

## 8. Fee parameters

`IPool.FeeParams`, per pool, written by the timelocked `UPDATE_FEES` op (`executeUpdateFeeParams`).

| Field | Type | Units | Meaning | Live |
|---|---|---|---|---|
| `protoSharePct` | uint8 | base 100 | protocol's share of the fee pot | 20 |
| `flashFeePbps` | uint16 | PBPS | flash-loan rate, ceiling `MAX_FLASH_FEE_PBPS` = 10,000 (1%) | 100 (0.01%) |

Both read from `getFeeParams()` on the live stable pool at block 11,456,319: `(20, 100)`. There is no code-level default for either; the raw struct zero-initialises, so a pool deployed without an explicit `FeeParams` write charges no flash fee and sends nothing to the protocol.

Full fee model: [Spread & Fees](/docs/1-1-4-spread-fees). In the order the contract evaluates it (`Pricing._pathSpread`, `_settleQuote`):

$$S = \operatorname{sat}_{\text{uint16}}\!\Big(\sum_i f_{\min,i} + \frac{\sigma_p\nu_p}{100\cdot\text{BPS}} + \sum_i \frac{100\,\sigma_i\sqrt{\tau_i}}{\text{BPS}} + \sum_i 100\,u_i\Big), \qquad \sigma_p = \sqrt{\textstyle\sum_i \sigma_i^2}$$

$$\phi_{\text{toll}} = T(y_g), \qquad \phi = \frac{(y_g - \phi_{\text{toll}})\,S}{2\cdot 10^6}, \qquad \phi_p = \frac{\phi\cdot r}{100}$$

Both charges are on the output token. The toll is subtracted first. There is no input-side skim.

Every risk aggregate **sums** over legs (the staleness and CI terms per leg off that leg's own age and $\sigma$), except $\sigma$ itself, which composes in quadrature because leg innovations are independent. A `max` under-fences every multi-leg path: with a per-leg fence of $2\theta$ and both marks walking $\theta$, a cross-spoke round trip charged $\max$ instead of $\sum$ pays the attacker the difference.

Two normalizations decide the shape of the ramp:

- the staleness term is divided by `BPS` (without it the term is $10^4\times$ too large and the spread steps straight to its field ceiling)
- the premium-free grace is $\min(\text{ttl}/2, 30)$ seconds, so a `ttl = 600` feed gets 30 s of grace, not 300 s

The 30 s cap is `Pricing.STALE_GRACE_CAP_SECS`. It was sized to V1's per-instance `maxRelayLagSecs`; the live V4 has no such field — its only past bound is the constant `MAX_RECON_AGE` = 6 h — so the cap is now a standalone number, and nothing on chain ties the two together. Without it a `ttl = 600` feed quotes no staleness premium for its first 300 s.

### 8.1. The spread is bounded below, not above

$S$ composes as a plain sum over legs, and the only bound on the result is the `uint16` saturation above, which is a field width rather than a policy. That is deliberate: a spread that a $\sigma$, confidence or staleness term drives wide is the honest price of that risk, and quoting a capped number instead sells an underpriced quote on exactly the tape where the premium is needed. A single leg reporting a 500 bps confidence adds its full 50,000 PBPS on its own, since $U_{\text{conf}} = 100\sum_i u_i$.

Trader protection is `minAmountOut`: exact, caller-set and per trade. It is also the only form that composes, since capping each leg's contribution would make a 2-leg trade cost more than the same two legs traded separately and pay a splitter to route around the hub.

Saturation cannot eat into the interior fence, which is why it is safe as the sole upper bound. Each interior fence is at most $\lceil \text{cap}\cdot\text{PBPS}/(\text{PBPS} - \text{cap}/2)\rceil = 10{,}051$ PBPS, and the widest path the tree admits carries $\texttt{MAX\_PATH\_LENGTH} - 3 = 6$ interior legs, so the composed fence is at most 60,306 < 65,535 (`test_the_interior_fence_ceiling_fits_the_uint16_spread`). $S$ is bounded below by that composed fence, so what saturates away is the $\sigma$, CI and staleness **premium**, never a security floor.

---

## 9. Pricing curve presets

`NUQuartic.Curve`, a per-pool shared preset table. Assets point in via `Asset.presetId`.

| Field | Type | Meaning | Constraint |
|---|---|---|---|
| `presetId` | uint16 | pointer into the table | 0 = no shape, refused at assign; there is no fallback quote |
| `wQ` | int256[] | control weights, PBPS times Q | nondecreasing, non-flat |
| `interior` | uint256[] | interior knots on the depth axis | strictly increasing in (0, 10000), $\le$ 14 segments |
| `dispRefPbps` | uint16 | reference dispersion of the fit (PBPS) | non-zero; quotes y-scale by `dispersion/dispRefPbps` |
| `flags` | uint8 | bit 0 `FLAG_REQUIRES_WALL` | assignable only if $\kappa_{\text{cov}} > 0$ |
| `median` | uint16 | **derived, not an input**: density median $x^*$, the curve's own zero | computed by `NUQuartic.set` from the stored segments and packed into the header; `_skewToDepth` anchors zero skew on it |

Axes: x is cumulative depth $[0, 10000]$ = 0-100%, y is price offset from the mark, scaled linearly by `dispersion/dispRefPbps`. Basis: clamped quartic I-spline, monotone by $\Delta w \ge 0$, $C^2$ at every knot, exact $O(1)$ range integration via stored prefix integrals.

Install validation (`NUQuartic._validate`):

- weights nondecreasing
- `wQ[last] > wQ[0]` (a flat curve is no price discovery)
- knots strictly increasing inside the open interval
- segments $\le$ 14
- `dispRefPbps != 0`

### 9.1. The shipped table

Five entries, all on the same interior knots `[1314, 8686]` ($m = 3$ spans) and all quartic I-splines. They carry **three** distinct `wQ` control polygons, A, B and C, whose half-swing at the reference dispersion is 100, 200 and 500 PBPS (1, 2 and 5 bp):

| `presetId` | `wQ` | half-swing @ `dispRefPbps` | `dispRefPbps` | `flags` | Carries |
|---:|---|---:|---:|---:|---|
| 1 | A | 100 PBPS (1 bp) | 100 | 0 | USDC, the base of every pool |
| 2 | A | 100 PBPS (1 bp) | 100 | 1 (`FLAG_REQUIRES_WALL`) | most walled stable spokes |
| 3 | B | 200 PBPS (2 bp) | 100 | 1 | wider stable spokes |
| 4 | C | 500 PBPS (5 bp) | 100 | 1 | the widest stable spokes |
| 5 | C | 500 PBPS (5 bp) | 500 | 0 | every volatile and FX leg |

`wQ` A = `[-100000000000, -90053719349, -38039704575, 0, 38039704575, 90053719349, 100000000000]`; B and C are the same family refitted at 2x and 5x half-swing, each rounded on its own (see below). The reference roster carries all five presets across 28 legs; Arc ships four of them (preset 3 unused) across 26 symbols. Per-parameter tables in [§12](#12-reference-parameter-table).

**The three polygons are independent fits, not scalar multiples of each other**, each 1-2 ULP off the exact multiple. The residual is far below quote granularity, but the shipped vectors are canonical on-chain state, so a vector regenerated by rescaling another fails parity against both the chain and the research parity vectors. Never rescale one entry to synthesize another: refit, and diff against the shipped values. Exact integers, the per-relation residuals and the shape derivation: [Liquidity Shaping §4](/docs/1-1-2-liquidity-shaping#4-preset-central-normal-plateau).

### 9.2. Why the table exists: a quantized density codebook

An asset's observed depth density is fitted off chain and then mapped to the **nearest entry already in the table**. `Asset.presetId` is a pointer, so re-pointing a leg at a different entry as its measured density drifts is cheap and frequent (`UPDATE_PROFILE`, one asset), while writing a curve is expensive and rare (`UPDATE_CURVE`, and a refit propagates to every asset pointing at it). Splitting the two is what lets shaping track the tape without a governance action per leg.

The cost of the quantization is the residual between an asset's own fit and its nearest entry. The continuous `dispersion / dispRefPbps` y-scale absorbs the **scale** part of that residual at quote time, so the table only has to span **shape**: five entries over three polygons span the shape space every listed asset falls into.

An entry carrying `FLAG_REQUIRES_WALL` is assignable only to an asset with $\kappa_{\text{cov}} > 0$ (`PoolConfig.validatePresetAssign`), and the same coupling is checked off chain before a plan is broadcast (keeper pre-submit fence): an un-walled trimmed tail is a hard price cliff that an informed trader arbs.

---

## 10. The fence set

Risk parameters deliberately carry **no timelock** on the steward path: adaptivity is the point. The fences *are* the safety net, three layers deep.

### 10.1. Layer 1: on-chain hard fences (`IAdmin.RiskFences`)

Owner-set, per (pool, token). `setAssetParamsBounded` **fails closed** when `maxDeltaBps == 0`, so an unseeded leg is not steward-writable. Seven fields, one storage slot, covering the parameters the steward lane can write:

| Fence | Meaning | Seeded value (risk-fence seeding script) |
|---|---|---|
| `minFeeHardMinPbps` | absolute floor; **also floors the owner path** | 25 (stable class) / 200 (volatile class) |
| `minFeeHardMaxPbps` | absolute ceiling on `minFeePbps` | 1,000 / 6,000 |
| `vegaHardMinBps` / `vegaHardMaxBps` | vega window | 5,000 / 20,000 |
| `haircutSuppressorHardMinBps` / `haircutSuppressorHardMaxBps` | haircut window; the floor is what stops a repeated-call ratchet to zero, which realizes LP loss | 0 / 0 on a walled leg, 5,000 / 10,000 otherwise |
| `maxDeltaBps` | relative step clamp | 2,500 ($\pm$25%) |

`setRiskFences` itself validates `maxDeltaBps` in $(0, \text{BPS}]$, `minFeeHardMinPbps` non-zero and below `minFeeHardMaxPbps`, and `vegaHardMinBps <= vegaHardMaxBps`.

"Stable class" in the seed script means live `minFee <= 1000` PBPS, so USDC and USDT get the stable window even in the volatile pool; EURC gets the volatile window in both pools it appears in.

The fences **bracket** the shipped parameters, they do not encode a wish. `minFeeHardMinPbps` sits deliberately *below* each leg's live `minFee`: a fence at the leg's own floor would leave no room to move down after a tape refit and would re-brick the keeper on any leg that has to. The $2\theta$ invariant is enforced by the keeper's startup gate and the coupling gates, **not** by `minFeeHardMinPbps`.

### 10.2. Layer 2: on-chain relative clamp

$$|v_{\text{new}} - v_{\text{old}}| \cdot 10^4 \le v_{\text{old}} \cdot \text{maxDeltaBps}$$

`Admin._relOk`, mirrored by the keeper's pre-submit fence. A steward can never move a parameter off zero: the owner must seed it first.

The clamp is skipped when the whole bundle **tightens**, which is defined as more defensive in every dimension simultaneously:

$$\text{minFee}\uparrow \ \wedge\ \nu\uparrow$$

`haircutSuppressorBps` is excluded from that exemption and is always clamped ([§4.3](#43-haircut-suppressor)). `minLiquidity` is not steward-writable at all: any change reverts.

### 10.3. Layer 3: off-chain coupling gates

Invariants the chain does **not** enforce, checked pre-submit so a violating plan is never broadcast:

| Gate | Rule | Why the chain cannot check it |
|---|---|---|
| **theta coverage** | $f_{\min} \ge 200\,\theta$ (PBPS) | $\theta$ exists only in keeper config |
| **halfwidth** | `minDispersionPbps` $\ge$ ($\theta$ + $q_{99}$ drift + $f_{\min}$) times the cell scale | needs the measured drift distribution |
| **vega headroom** | `minDispersionPbps + vegaHardMax·σ_push_max/1e4 <= dispersionCap(preset)` | the chain cannot know the keeper's worst-case σ print; a floor the fence cannot hold configures a σ-triggered outage |
| **wall coupling** | `FLAG_REQUIRES_WALL` preset $\Rightarrow$ $\kappa_{\text{cov}} > 0$ | the flag lives on the curve, the wall on `RiskConfig` |

### 10.4. What is *not* fenced

| Parameter | Status | Bound |
|---|---|---|
| `minDispersionPbps` | **no steward fence** | non-zero (0 → 1000 default), the keeper's own halfwidth + vega-headroom floor, and a hard revert if it exceeds the preset's `dispersionCap` (§5) |
| σ-driven κ ceiling | nothing to fence | clamped in `_calculateDispersion` at a protocol constant (§5); not a writable field |
| `presetId` | no fence | timelocked (`UPDATE_PROFILE`), and `FLAG_REQUIRES_WALL` gated |
| `kappaCovBps` | no fence | timelocked, plus the haircut and wall-gated-preset couplings (§3.1) |
| `minLiquidity` | not steward-writable | owner lane only; any steward change reverts |
| `protoSharePct`, `flashFeePbps` | no fence beyond `MAX_FLASH_FEE_PBPS` | timelocked |
| `anchor` | no fence | timelocked at the `CRITICAL` base-migration tier, atomic with the oracle config (`UPDATE_ANCHOR`); guardian `collapseAnchor` may move a leg toward the root only, and halts it |
| `vegaBps` **spread** across assets | **no fence at all** | each asset's vega is fenced individually; nothing relates two assets' values, and a large gap between two endpoints of a live route carries a bounded routing cost (§4.2.1) |
| the inventory skew | nothing to fence | it takes no parameter (§4.1) |

The fenced set is exactly what the untimelocked steward lane can move; everything else already pays a timelock.

---

## 11. The fee floor covers 2 theta

### 11.1. Derivation

The keeper pushes a new mark when $|p_{\text{true}} - \text{mark}|/\text{mark}$ exceeds $\theta$ bps, or on a heartbeat. While it is live, the stale gap is therefore bounded by $\theta$, independently of volatility or elapsed time.

An informed trader extracting that gap must round-trip: buy at the stale mark, sell back at truth. Each swap is charged $S/2$ on its output, so the round trip pays $S$. The extraction is unprofitable exactly when

$$S \ge 2\theta \ \text{(bps)} \qquad\Longleftrightarrow\qquad f_{\min} \ge 200\,\theta \ \text{(PBPS)}$$

The **floor** must carry it, not the runtime spread. $\sigma$, confidence and staleness all add to $S$, but all three go to zero in exactly the calm conditions where the arb is cheapest to run. A fence built on them funds nothing when it matters.

### 11.2. Enforcement

The predicate is one comparison:

```text
minfee_pbps >= 200 * theta_bps
```

Applied at three points:

1. **Startup, hard fail.** At boot the oracle keeper reads `getAsset(token)` for every configured pool times feed and refuses to start if any listed leg violates it. The keeper will not push into a pool it would make extractable.
2. **Pre-submit.** Any risk plan emitting a violating `minFee` is rejected before broadcast.
3. **Parameter generation.** The shipped roster is generated with `S_dep = max(q70, 2θ, feeQ99)`.

The boundary is pinned by tests: `minfee_covers_theta(50, 0.25)` passes, `(49, 0.25)` fails, `(1000, 5.0)` passes.

### 11.3. Live compliance

Every leg on the live Arc fleet clears its own fence, and most of the classes that used to sit
exactly on it no longer do: the stable floors were raised off the fence in the 2026-09-04 retune, and
FX moved from 1,000 to 1,200 PBPS against the same 1,000 requirement.

**Do not read a per-leg fee floor off this page.** `minFeePbps` is mid-change: a second and tighter
floor is being applied leg by leg, derived from the leg's own measured $\sigma$ rather than from
$\theta$, and it is **partially applied on chain today**. Most legs carry the new value; the
stocks-pool legs and PAXG still carry their previous ones, pending an owner decision between two
remedies. Both floors are cleared everywhere either way, so nothing is under-fenced in the interim —
but a specific number quoted here would be wrong within the week. Read `getAsset(token).minFeePbps`
off the chain, or `GET /v1/assets/pools`.

The zero-margin hazard the fence table used to carry still stands as a rule: where a leg sits exactly
at $2\theta$, any $\theta$ increase must be paired with a `minFee` increase in the same change, or the
keeper hard-fails at its next restart.

---

## 12. Reference parameter table

### Live on Arc

The class ladder actually on chain, read at block 60,255,318 (2026-09-04). Each pool's hub leg
carries the **maximum $\kappa_{\text{cov}}$ of its own spokes**, so a hub is never the cheapest leg to
drain in the pool it anchors: stable core 600, FX 1,500, crypto 2,000, stocks 2,500.

| Class | $\kappa_{\text{cov}}$ (bps) | $\nu$ | `minDispersionPbps` | $\eta$ |
|---|---:|---:|---:|---:|
| Stable spokes | 600 | 10,000 | 158 - 241 | 0 |
| Hub leg (per pool) | max of that pool's spokes | 10,000 | 200 | 0 |
| FX | 1,500 | 3,000 | 1,472 - 3,740 | 0 |
| Crypto majors | 2,000 | 4,000 | 1,842 - 2,034 | 0 |
| Metals | 1,800 | 4,500 | 1,500 | 0 |
| Equities | 2,500 | 3,500 | 1,950 | 0 |

`minFeePbps` is deliberately omitted: it is mid-change and partially applied (§11.3). EURC is priced
as FX in both cores it sits in. Curve presets are unchanged by the retune. This table is a snapshot,
not a record: `deployments/arc-risk-params.json` states intent and has diverged from the chain twice,
so trust a chain read.

### The pre-Arc reference roster

The roster below is retained as a worked example of the fence arithmetic, not as live config. Every row carries $\nu = 10{,}000$, `flags` `0x06`, and `anchor = ""`, i.e. **base-anchored**: flatness is a config choice, not a contract limit - `AnchorTreeLib.MAX_DEPTH = 4` is the enforced capability (§12.1).

Fee and dispersion columns are PBPS; $2\theta$ is the fee-floor requirement. The `maxDisp` column is the value the generator emits: `sanitizeDispersion` binds it down to the preset's `dispersionCap` (5000 for presets 1, 2 and 5; 2500 for preset 3; 1000 for preset 4), so any row above its cap lands on chain as the cap (§5).

| Sym | Class | Preset | `minFee` | $2\theta$ | `minDisp` | `maxDisp` | $\kappa_{\text{cov}}$ | $\eta$ |
|---|---|---:|---:|---:|---:|---:|---:|---:|
| USDC | stable (base) | 1 | 50 | 50 | 200 | 2000 | 0 | 10000 |
| USDT | stable | 2 | 61 | 51.4 | 161 | 6000 | 100 | 0 |
| USDE | stable | 3 | 106 | 73.0 | 311 | 5000 | 100 | 0 |
| USDS | stable | 2 | 65 | 51.8 | 229 | 8000 | 100 | 0 |
| DAI | stable | 3 | 183 | 104.8 | 559 | 8000 | 100 | 0 |
| USD1 | stable | 2 | 80 | 64.4 | 241 | 5000 | 100 | 0 |
| USDG | stable | 4 | 496 | 73.0 | 73 | 8000 | 100 | 0 |
| PYUSD | stable | 4 | 226 | 173.2 | 158 | 8000 | 100 | 0 |
| RLUSD | stable | 2 | 69 | 57.6 | 178 | 8000 | 100 | 0 |
| USDF | stable | 2 | 80 | 73.0 | 319 | 8000 | 100 | 0 |
| U | stable | 2 | 73 | 73.0 | 216 | 8000 | 100 | 0 |
| GHO | stable | 2 | 67 | 53.6 | 222 | 8000 | 100 | 0 |
| TUSD | stable | 3 | 116 | 91.6 | 264 | 8000 | 100 | 0 |
| USDTB | stable | 2 | 78 | 73.0 | 470 | 8000 | 100 | 0 |
| FDUSD | stable | 2 | 63 | 53.4 | 172 | 8000 | 100 | 0 |
| AUSD | stable | 2 | 111 | 50.0 | 850 | 8000 | 100 | 0 |
| WETH | volatile | 5 | 1032 | 1000 | 2034 | 500000 | 0 | 10000 |
| WBTC | volatile | 5 | 1015 | 1000 | 1887 | 500000 | 0 | 10000 |
| cbBTC | volatile | 5 | 1015 | 1000 | 1887 | 500000 | 0 | 10000 |
| BNB | volatile | 5 | 1011 | 1000 | 1842 | 500000 | 0 | 10000 |
| XAUT | volatile | 5 | 1138 | 1000 | 3601 | 500000 | 0 | 10000 |
| PAXG | volatile | 5 | 2003 | 1000 | 1336 | 500000 | 0 | 10000 |
| EURC | volatile | 5 | 1017 | 1000 | 1566 | 500000 | 0 | 10000 |
| QCAD | fx | 5 | 1000 | 1000 | 1472 | 100000 | 0 | 10000 |
| AUDF | fx | 5 | 1000 | 1000 | 2493 | 100000 | 0 | 10000 |
| BRLA | fx | 5 | 1061 | 1000 | 1000 | 100000 | 0 | 10000 |
| JPYC | fx | 5 | 1000 | 1000 | 2050 | 100000 | 0 | 10000 |
| KRW1 | fx | 5 | 1003 | 1000 | 3740 | 100000 | 0 | 10000 |

Pool membership: stable pool = the 16 stable rows; volatile pool = USDC, USDT, WETH, WBTC, cbBTC, BNB, XAUT, PAXG, EURC; FX pool = USDC, EURC, QCAD, AUDF, BRLA, JPYC, KRW1. USDC is the base of all three. Rows carrying $\kappa_{\text{cov}} = 0$ or $\eta = 10{,}000$ predate the wall and are unrepresentable today: every listed asset including the hub must have $\kappa_{\text{cov}} > 0$, and that forces $\eta = 0$ (§4.3). Live values are in the table above, not here.

In that roster the stable book quoted a 0.5-5 bp floor with a 1 % coverage wall and a full exit haircut, and the volatile book a 10-20 bp floor with no wall and a half haircut. Neither half of the volatile row is reachable now: the wall is mandatory and the half haircut is unrepresentable beside it (§4.3, §6.2). `minFee` is the only fee rate a leg carries (§8.1). `minDispersionPbps` is the deployed half-width, so USDG at 73 PBPS on preset 4 (20 PBPS/bp) is quoting a 3.65 bp support, while AUSD at 850 on preset 2 (100 PBPS/bp) is quoting 8.5 bp.

### 12.1. Topology configuration

The `anchor` column of a pool's risk-param file is the sole expression of that pool's topology: an empty column keeps every leg base-anchored, and filling a cell deepens that pool's tree and puts the interior fence in the path of every route through it. What the depth bound is, what a deep edge requires and what gates the first fill are stated once at [Anchor Path Pricing §1.1](/docs/1-1-3-anchor-path-pricing#11-what-the-anchor-column-configures).

The parameter consequence is what belongs here: a non-base anchor gives that pair its own edge, its own feed, its own $\sigma$ and its own fee floor, so several fee tiers coexist inside one pool instead of everything being priced off the base mark. Every sum-over-legs budget in this reference is sized against the full depth bound; nothing special-cases a shallower tree.

The interior fence's second-order margin has market tape only once deep routes run; until then it is pinned by tests.

---

## 13. Units reference

| Quantity | Base | Precision | Example |
|---|---|---|---|
| Fees (`minFeePbps`, `flashFeePbps`) | $10^6$ | 0.0001% | 100 = 1 bp, 10,000 = 1% |
| Volatility (`sigmaPbps`) | $10^6$ = 100% | 0.0001% | 10,000 = 1%; cap $10^8$ |
| Dispersion (`minDispersionPbps`, κ) | $10^6$ | 0.0001% | 1000 = 0.1%; ceiling 900,000 (`MAX_DISPERSION_PBPS`) |
| Coverage wall (`kappaCovBps`), confidence, `refBandBps` | $10^4$ | 0.01% | 100 = 1% |
| Multiplier ($\nu$) | $10^4$ | 0.01% | 10,000 = 1.0x |
| Haircut suppressor ($\eta$) | $2\times 10^4$ | - | range 0-19,999; 20,000 is the rejected DISABLE sentinel |
| Protocol share (`protoSharePct`) | 100 | 1% | 20 = 20% |
| Prices | WAD $10^{18}$ | 18 dp | 1e18 mark from the oracle |
| Path spread (`SwapQuote.spreadPbps`) | $10^6$ | 0.0001% | `uint16`, saturating at 65,535 = 6.55% |

### 13.1. Why PBPS

Standard BPS ($10^4$) cannot express a stablecoin fee below 1 bp:

| Fee | Standard | PBPS | In bp |
|---|---|---|---|
| 0.001% | inexpressible | 10 | 0.1 |
| 0.005% | inexpressible | 50 | 0.5 |
| 0.01% | 1 | 100 | 1 |
| 0.0001% | inexpressible | **1** | **0.01** (`MIN_FEE_PBPS`) |

The live stable floors, 50-496 PBPS, are 0.5-5 bp: every one of them is inexpressible in standard BPS without rounding to 0 or 1 bp, and the difference between a 0.5 bp and a 1 bp floor on a stable book is the difference between winning and losing the flow.

True 0.001 bp would need fractional PBPS. `minFeePbps` is integer PBPS, so 1 PBPS = 0.01 bp is the finest representable floor.

---

## 14. Known gaps

| Gap | Impact | Reference |
|---|---|---|
| **`RiskFences` can be left unseeded on a fresh deployment.** Until they are seeded, `maxDeltaBps == 0` and `setAssetParamsBounded` fails closed on every leg, so the risk keeper cannot act at all. | risk adaptation stays offline; parameters are frozen at their deploy values until an owner transaction seeds the fences | `Admin.setAssetParamsBounded` |
| **Seeding-script addresses must match the target deployment.** Its `STABLE_POOL`/`VOLATILE_POOL`/`FX_POOL` and token addresses are constants; if they predate the deployment being patched, running it as-is reverts `preview()` with "not listed in pool". | refresh them from the deployment's address records ([2. Deployments](/docs/2-overview)) before use | the risk-fence seeding script |
| **Impact conservation is exact only at par.** The traverse denominator is reserves, so it is $c\cdot L$ while the skew step is denominated on $L$, and no single pair of arm slopes conserves at every coverage. The residual is trader-pays (LP-safe): $+2$ PBPS at 1% of depth, $+29$ at 25%. | a round trip off par over-charges slightly; the LP is never the loser | [Inventory Management §3.2](/docs/1-1-1-inventory-management#32-the-two-arms-are-not-symmetric). Full fix (traverse redenominated on $L$) scoped, **not built** |
| **`vegaBps` uniformity across assets has no fence at all.** Each asset's vega is fenced individually and nothing relates them, while `_pathSpread` prices the whole path's $\sigma_p$ at the endpoint maximum. The skew scales with $\nu_{\max}/\nu_{\text{leg}}$, bounded only by the fence window. The owner-immediate lane can introduce heterogeneity, since a vega raise counts as defensive. | inert while vega stays uniform across assets; live the moment anyone tunes vega per asset | [§4.2.1](#421-deploy-constraint-keep-vega-uniform-across-assets) |
| **The interior fence is unexercised until deep routes run.** On a flat roster (every leg base-anchored) no route carries an interior leg. | the fence's second-order margin has tape only once deep routes run; until then it is pinned by tests only | [§12.1](#121-topology-configuration) |

---

## 15. Related documentation

- [Spread & Fees](/docs/1-1-4-spread-fees): the fee model and both theorems
- [Toxic Flow Mitigation](/docs/1-1-6-toxic-flow-mitigation): the adverse-selection budget and the $2\theta$ derivation
- [Inventory Management](/docs/1-1-1-inventory-management): coverage and haircut mechanics
- [Liquidity Shaping](/docs/1-1-2-liquidity-shaping): the spline and the dispersion law
- [Invariants](/docs/1-1-8-invariants): settlement and liquidity-floor invariants
- [Feed Oracle](/docs/1-2-2-internal-oracle): mark, $\sigma$, confidence, push API
