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. Units: PBPS (), BPS (), WAD (), or raw.
1. Contents
- Who may set what
- Asset configuration
- Sensitivities: vega, haircut
- Dispersion
- Risk configuration
- Oracle configuration
- Fee parameters
- Pricing curve presets
- The fence set
- The fee floor covers 2 theta
- Reference parameter table
- Units reference
- 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:
minLiquidityunchangedminFeePbpsnon-decreasingvegaBpsnon-decreasinghaircutSuppressorBpsunchanged
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 -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 , 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 |
vegaBps | uint16 | base 10,000 | volatility sensitivity |
haircutSuppressorBps | uint16 | base 20,000 | withdrawal-haircut damper; forced to 0 on a -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), 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 |
haircutSuppressorBps == 0 | otherwise a toll-exempt withdrawal bypass stays open (Lemma B of the published coverage proofs) |
| every listed asset including the hub: | κ=0 is forbidden; _covToll is output-only so hub κ prices taking the hub out |
the asset’s preset carries no FLAG_REQUIRES_WALL | stripping 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 (), pre-outflow, against amountOut + protoFee + minLiquidity:
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 minLiquidity and still revert every swap. Full form: Invariants I-14.
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). 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 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.
Why those constants. Writing the saturation points as , continuity requires and round-trip conservation requires , so 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.
The skew anchor. maps onto the spline as , where is the curve’s stored density median (§9), not the domain midpoint. The median enters as an offset at the 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):
Spread (Pricing._pathSpread):
The divisors differ by deliberately: the same moves dispersion a tenth as fast as it moves the spread. In _pathSpread, over the endpoints only, and it is the one risk input that is not composed per leg (every other one, in quadrature and the fee, CI and staleness terms by sum, is); in _calculateDispersion it is the profile asset’s own . Endpoint-max is correct there because 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 .
Live on Arc (block 60,255,318, 2026-09-04): 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 against a 1 %-of-mark ceiling (§5), and at eight legs’ live already put their book over it. The same field also carries the spread’s slope at a 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; the fence window that is the only thing bounding it is §10.1 here.
Live on Arc: is per class (§4.2), so the endpoint-max scoping is exercised. The widest ratio inside one pool is (a stable leg against an FX leg in the crypto core), which is the bound on how far a composite path’s 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 . 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:
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).
| Factor | Effect | |
|---|---|---|
| 0 | 1.0 | full linear haircut; required on every -walled leg |
| 10,000 | 0.5 | half haircut; the initAsset default |
| 15,000 | 0.25 | quarter |
| 19,999 | 0.00005 | maximum configurable |
| 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: , .
The haircut is one-sided. At withdrawal pays face only; over-coverage surplus never pays an LP bonus.
Live on Arc: on every quoting leg of all four pools — full linear haircut fleet-wide. It is not a choice:
requireNeverDepletablerefuses at every writer andrequireWallOkthen forces wherever , so on a listed leg 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 (Spread & Fees §3.4).
5. Dispersion
Dispersion is the price half-width over which liquidity is deployed, in PBPS. It rises with off the per-asset fitted floor = minDispersionPbps, one-for-one at and at 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.
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, with 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 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.
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 a ratio-2 stable (floor 161, cap 5000) hits its shape cap near and fails closed there, and a class carrying a lower buys headroom in exactly that ratio, which is what the live per-class cut is for (§4.2); the , CI and staleness terms of the spread keep widening regardless. setCurve’s “may not deepen a live preset” rule is exactly 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:
is how far the mark may walk before a push triggers; is the excursion past measured over one push interval, so is its 99th percentile, how far the mark actually gets before the push lands; 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, , is also checked by the keeper fence, but it binds nothing once 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 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 headroom on its live instead of the XAUT carried. Per-leg values in §12.
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); any live zero is a pending-UPDATE_RISK timelock artifact - see 2. Deployments |
Coverage reaches the quote through the fixed skew law (§4.1) and through (§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 and of :
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.
(kappaCovBps, bps) scales the convex coverage toll (Spread & Fees §6). Its practical meaning is the marginal rate at zero size:
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:
| 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 |
| 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 and nothing else, so an arbitrageur facing a mark error (bps) drains
until the marginal toll equals it, i.e. to the equilibrium coverage
Write for the fraction of that extraction the toll recovers. is a function of alone — cancels — and it rises monotonically to : , , , . The toll can never recover more than half the loss it prices, at any . So a recovery target is not a design input, and a low recovery ratio is not evidence the wall is under-sized. What actually buys is , and therefore the residual: net LP loss bps of . 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 at the previous 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 , 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.
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). A leg left at 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 charges 150 bps of marginal toll at the live , 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 rises on the event that matters, with no clock, no flag and no governance-set rate (Inventory Management §5). 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 = 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 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). 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, and every shipped leg runs EXTERNAL. The bound worth stating on a parameter page is INTERNAL eligibility, enforced at config:
primaryandfeedIdmust be live.refFeedIdandrefBandBpsmust be armed.refBandBpsmust be at mostMAX_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. Kept facts: ; (6σ sanity cap), s (NXR Parkinson window), ; = stored prior (never the incoming push’s own, else a signer inflates its own band); stored 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. In the order the contract evaluates it (Pricing._pathSpread, _settleQuote):
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 ), except itself, which composes in quadrature because leg innovations are independent. A max under-fences every multi-leg path: with a per-leg fence of and both marks walking , a cross-spoke round trip charged instead of 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 too large and the spread steps straight to its field ceiling) - the premium-free grace is seconds, so a
ttl = 600feed 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
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 , 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 .
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 PBPS, and the widest path the tree admits carries interior legs, so the composed fence is at most 60,306 < 65,535 (test_the_interior_fence_ceiling_fits_the_uint16_spread). is bounded below by that composed fence, so what saturates away is the , 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), 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 |
median | uint16 | derived, not an input: density median , 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-100%, y is price offset from the mark, scaled linearly by dispersion/dispRefPbps. Basis: clamped quartic I-spline, monotone by , at every knot, exact 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 14
dispRefPbps != 0
9.1. The shipped table
Five entries, all on the same interior knots [1314, 8686] ( 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.
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.
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 (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 (25%) |
setRiskFences itself validates maxDeltaBps in , 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 invariant is enforced by the keeper’s startup gate and the coupling gates, not by minFeeHardMinPbps.
10.2. Layer 2: on-chain relative clamp
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:
haircutSuppressorBps is excluded from that exemption and is always clamped (§4.3). 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 | (PBPS) | exists only in keeper config |
| halfwidth | minDispersionPbps ( + drift + ) 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 | 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 exceeds bps, or on a heartbeat. While it is live, the stale gap is therefore bounded by , 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 on its output, so the round trip pays . The extraction is unprofitable exactly when
The floor must carry it, not the runtime spread. , confidence and staleness all add to , 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:
minfee_pbps >= 200 * theta_bpsApplied at three points:
- 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. - Pre-submit. Any risk plan emitting a violating
minFeeis rejected before broadcast. - 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 rather than from
, 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 , any 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 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 | (bps) | minDispersionPbps | ||
|---|---|---|---|---|
| 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 , 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; 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 | minDisp | maxDisp | |||
|---|---|---|---|---|---|---|---|---|
| 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 or predate the wall and are unrepresentable today: every listed asset including the hub must have , and that forces (§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.
The parameter consequence is what belongs here: a non-base anchor gives that pair its own edge, its own feed, its own 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) | 0.0001% | 100 = 1 bp, 10,000 = 1% | |
Volatility (sigmaPbps) | = 100% | 0.0001% | 10,000 = 1%; cap |
Dispersion (minDispersionPbps, κ) | 0.0001% | 1000 = 0.1%; ceiling 900,000 (MAX_DISPERSION_PBPS) | |
Coverage wall (kappaCovBps), confidence, refBandBps | 0.01% | 100 = 1% | |
| Multiplier () | 0.01% | 10,000 = 1.0x | |
| Haircut suppressor () | - | range 0-19,999; 20,000 is the rejected DISABLE sentinel | |
Protocol share (protoSharePct) | 100 | 1% | 20 = 20% |
| Prices | WAD | 18 dp | 1e18 mark from the oracle |
Path spread (SwapQuote.spreadPbps) | 0.0001% | uint16, saturating at 65,535 = 6.55% |
13.1. Why PBPS
Standard BPS () 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) before use | the risk-fence seeding script |
| Impact conservation is exact only at par. The traverse denominator is reserves, so it is while the skew step is denominated on , and no single pair of arm slopes conserves at every coverage. The residual is trader-pays (LP-safe): PBPS at 1% of depth, at 25%. | a round trip off par over-charges slightly; the LP is never the loser | Inventory Management §3.2. Full fix (traverse redenominated on ) 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 at the endpoint maximum. The skew scales with , 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 |
| 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 |
15. Related documentation
- Spread & Fees: the fee model and both theorems
- Toxic Flow Mitigation: the adverse-selection budget and the derivation
- Inventory Management: coverage and haircut mechanics
- Liquidity Shaping: the spline and the dispersion law
- Invariants: settlement and liquidity-floor invariants
- Feed Oracle: mark, , confidence, push API