> ## Documentation Index
> Fetch the complete documentation index at: https://inflexion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# The vaults

> ConvexityVault (dual-tranche cvAMM, lock/settle/accrue, run defense) and UnderwriterVault (per-MM collateral) — where underwriter capital lives, how the senior-protection invariant holds, and why depositor capital is never guaranteed.

Every Inflexion swap is backed by **pre-locked USDC collateral** equal to its `MaxIL`. Two contracts hold that collateral, one per underwriting rail, and a single settlement core draws on whichever one opened the swap:

<CardGroup cols={2}>
  <Card title="ConvexityVault" icon="layer-group" href="#convexityvault-the-pooled-dual-tranche-cvamm">
    The pooled, always-on **Path A** underwriter. Dual-tranche SENIOR / JUNIOR USDC, fungible across all 9 marketIds of a pair. The floor of liquidity.
  </Card>

  <Card title="UnderwriterVault" icon="vault" href="#underwritervault-per-mm-collateral-path-b">
    Per-MM collateral for **Path B**. One isolated pool per market maker; invariant **I5** keeps `locked ≤ deposited` by construction.
  </Card>
</CardGroup>

Both vaults share one structural property that makes the whole system tractable: because coverage is `min(realized_IL, MaxIL)` and each swap locks exactly `MaxIL`, **every payout is bounded by its own locked amount**. The vaults never need to find money they did not already lock. What differs between them is the *accounting around* that locked amount — pooled and tranched on Path A, isolated per signer on Path B.

<Note>
  Each vault wires its `core` address **once** and then freezes it one-way (`setCore` → `freezeCore`). Only the frozen `core` (the [`InflexionCore`](/protocol/lifecycle) state machine) can call `lockCollateral` or `releaseAndDistribute`; there is no admin path to redirect a vault after freeze. See [immutability & custody](/security/immutability-custody).
</Note>

***

## ConvexityVault — the pooled, dual-tranche cvAMM

The `ConvexityVault` is **Path A**: a pooled, passive, signature-free underwriter that quotes algorithmically on-chain off the published [FairPremium](/concepts/fair-value). One vault backs an entire pair — for ETH/USDC that is **3 fee tiers × 3 durations = 9 marketIds**, all underwritten by the same fungible USDC. It is the [centrepiece pillar](/concepts/three-pillars): the *floor of liquidity* that guarantees a price exists even with zero market makers online.

It is **dual-tranche SENIOR / JUNIOR from launch** (deployed, see [deployments](/reference/deployments)). The two tranches are two different products on the same capital — and that distinction is load-bearing for both the economics and the safety properties.

<CardGroup cols={2}>
  <Card title="Senior — convexity savings account" icon="piggy-bank">
    Low-yield, structurally protected from **underwriting** loss while junior capital buffers. Takes a small, configurable share of every premium.
  </Card>

  <Card title="Junior — the vol seller" icon="chart-line">
    High-APY, **first-loss**. Absorbs all underwriting loss before senior is ever touched, and earns the bulk of the premium for doing so.
  </Card>
</CardGroup>

For the depositor-facing product framing of these two tranches, see [Tranches](/concepts/tranches).

### Accounting

The vault keeps the two tranches fully separate, plus the locked-capital bookkeeping that drives both pricing and run defense:

| Quantity | Meaning |
| - | - |
| `seniorAssets` / `juniorAssets` | USDC backing each tranche |
| `seniorShares` / `juniorShares` | ERC-4626-style share supply per tranche |
| `seniorBalanceOf` / `juniorBalanceOf` | per-depositor share balances |
| `totalLocked` | USDC committed to live swaps across all markets |
| `lockedByMarket[marketId]` | committed per market (used for the settle-side bound) |
| `sumLockedSq` | incremental `Σ lockedByMarket²` — the O(1) HHI accumulator |

Two derived quantities matter everywhere downstream:

$$
\texttt{totalAssets} = \texttt{seniorAssets} + \texttt{juniorAssets}
\qquad
\texttt{freeAssets} = \texttt{totalAssets} - \texttt{totalLocked}
$$

`freeAssets` is what can still back a new swap. As it shrinks, the [load stack](/math/load-stack)'s `util_skew` rises — the same locked/free accounting that defends against a run also prices marginal capacity up *before* over-commitment.

### The senior-protection invariant

This is the single most important line in the vault. Enforced at **every** `lockCollateral` (reverting `SeniorProtectionBreached`):

$$
\boxed{\;\texttt{totalLocked} \le \texttt{juniorAssets}\;}
$$

Why this is the whole game: every payout is `≤` its `MaxIL`, which equals its locked amount, so

$$
\sum \texttt{payouts} \;\le\; \texttt{totalLocked} \;\le\; \texttt{juniorAssets}.
$$

Therefore the **junior-first-loss waterfall absorbs all underwriting loss before senior is ever touched.** Senior capital is structurally shielded from ordinary underwriting losses for as long as the junior buffer covers what is locked.

<Info>
  This constraint is the **adaptive code form** of the `u ≤ 1 − sf` rule, but keyed to the *actual* junior buffer rather than a fixed ratio — strictly safer. With the P1.13 target tranche ratio `sf = 0.60`, junior `= 1 − sf = 0.40`, so `totalLocked ≤ juniorAssets` is exactly `u ≤ 0.40 = 1 − sf`. Note `sf` is a UX/incentive *target*, not the hard cap — the hard cap is the live `juniorAssets` figure itself.
</Info>

<Warning>
  **Limit:** senior `P(loss) = 0` is a *calibration* result, not a structural guarantee. **Why:** the invariant protects senior only from *underwriting* loss while `u ≤ 1 − sf`; it cannot protect either tranche from systemic failure (USDC depeg, oracle/settlement fault, contract bug). **Mitigation:** capital-not-guaranteed is the umbrella claim for **both** tranches — see the disclosure below and [the no-bad-debt page](/concepts/no-bad-debt) for the strict separation of the LP guarantee from depositor risk.
</Warning>

This pool-side invariant is the cvAMM half of [**I5**](/security/invariants) (vault solvency) and is reviewed in full on the [invariants page](/security/invariants).

### Lock, settle, accrue — the three core operations

<Steps>
  <Step title="lockCollateral(marketId, amount) — at swap creation">
    Called by `core` inside `_executePathA` when an LP opens a Path-A swap. Two requirements:

    * `freeAssets ≥ amount` else `InsufficientFree` — the vault must have uncommitted capital.
    * `totalLocked + amount ≤ juniorAssets` else `SeniorProtectionBreached` — the senior-protection invariant above.

    On success it commits the capital and updates the HHI accumulator incrementally:

    $$
    \texttt{sumLockedSq} \mathrel{+}= \texttt{amt}^2 + 2\cdot x \cdot \texttt{amt}
    \quad\text{(where } x = \texttt{lockedByMarket[marketId]} \text{ before the add)}
    $$

    This keeps the concentration measure O(1) per lock — no loop over markets.
  </Step>

  <Step title="releaseAndDistribute(marketId, lp, payout, collateral) — at settlement">
    Called by `core` inside `settle` when the counterparty is the vault (`s.mm == address(convexityVault)`). It:

    1. Requires `payout ≤ lockedAmount` (`PayoutExceedsCollateral`) and `lockedAmount ≤ lockedByMarket[marketId]` — defensive re-checks of the cap.
    2. Unlocks the collateral (reverses the `totalLocked` / `sumLockedSq` updates).
    3. Applies the loss **junior-first**:
       $$
       \texttt{juniorLoss} = \min(\texttt{payout},\ \texttt{juniorAssets}),
       \qquad
       \texttt{seniorLoss} = \texttt{payout} - \texttt{juniorLoss}
       $$
       The `min` is defense-in-depth — by the senior-protection invariant junior always covers it, so `seniorLoss` is 0 in practice.
    4. Transfers `payout` to the LP and emits `JuniorLoss`.

    The counterparty (the pool) keeps `collateral − payout` — the residual `MaxIL` that was never needed.
  </Step>

  <Step title="accruePremium(amount) — at swap creation (premium split)">
    99% of every Path-A premium flows here (the other 1% goes to treasury). The vault splits it across the two tranches:

    $$
    \texttt{toSenior} = \texttt{amount} \cdot \frac{\texttt{seniorPremiumShareBps}}{10^4},
    \qquad
    \texttt{toJunior} = \texttt{amount} - \texttt{toSenior}
    $$

    Senior takes a small, fixed slice (the "convexity savings account" yield); junior takes the remainder (the high-APY vol-seller return). `seniorPremiumShareBps` is **immutable**, set from the calibrated risk-parameter set, never hardcoded.
  </Step>
</Steps>

<Tip>
  The premium split (`accruePremium`) and the loss waterfall (`releaseAndDistribute`) are **orthogonal to settle semantics and to the [I10 price cap](/security/invariants)**. Neither the tranche split nor the `totalLocked ≤ juniorAssets` constraint changes how a payout is computed — they only decide *who in the pool* earns the premium and bears the loss.
</Tip>

### Inventory, utilization, and concentration

The vault exposes three live risk reads that feed the on-chain [load stack](/math/load-stack) for Path-A pricing. They are not just telemetry — they actively price marginal capacity:

<AccordionGroup>
  <Accordion title="Inventory — freeAssets vs totalLocked">
    `freeAssets = totalAssets − totalLocked` is the headroom for new swaps. The vault never locks beyond `juniorAssets`, so true headroom for a lock is `min(freeAssets, juniorAssets − totalLocked)`. Inventory is the raw input to utilization.
  </Accordion>

  <Accordion title="Utilization — u = totalLocked / totalAssets">
    `util_skew(u)` is flat below a knee, then convex and capped above it (`CvammPricing.utilSkewWad`). As the pool nears full commitment, the marginal price rises so capacity is rationed by price *before* the pool over-commits. This is the same accounting that the run defense relies on — the pricing dial and the safety dial are one mechanism.
  </Accordion>

  <Accordion title="Concentration — normalized coverage HHI">
    `dispersion_skew(h)` keys off the normalized Herfindahl index `h` of coverage across markets (`CvammPricing.dispSkewWad`). It is the honest single-pair concentration analogue: many positions bunched at one edge all hit `MaxIL` together. The HHI is computed O(1) from the incremental `sumLockedSq` accumulator (`ConvexityVault.concentrationWad`) — no per-market loop at quote time.
  </Accordion>
</AccordionGroup>

All three skew curves (knees, slopes, powers, caps) and the base load come from the calibrated risk-parameter set via `LoadParams` — **no PARTIAL or cvAMM primitive is ever hardcoded**. The full pricing math lives on the [load-stack page](/math/load-stack) and the I10 clamp is detailed under [the invariants](/security/invariants).

### Run defense and the withdrawal cooldown

Deposits are instant. Withdrawals are deliberately not, because instant withdrawals on a vault that has *locked* capital against live swaps would let a depositor exit ahead of an in-flight loss — the classic run. Two mechanisms prevent it:

<Steps>
  <Step title="Cooldown-gated withdrawals">
    A depositor calls `requestWithdrawal`, waits `withdrawalCooldown`, then calls `withdraw`. The delay (sourced from the calibrated risk-parameter set, not hardcoded) removes the timing advantage of front-running a settlement.
  </Step>

  <Step title="Junior cannot be drawn below totalLocked">
    `withdraw` reverts with `JuniorBelowLocked` if it would push `juniorAssets` under `totalLocked`. Locked collateral cannot be withdrawn out from under a live swap — this is what makes the senior-protection invariant hold *through* a withdrawal, not just at lock time.
  </Step>
</Steps>

<Warning>
  **Limit:** a withdrawal cooldown is friction for depositors who want instant liquidity. **Why:** instant withdrawals against locked capital are a solvency hole — a depositor could exit before a known loss is distributed, dumping it on those who stayed. **Mitigation:** the cooldown is finite and parameterised from the quant model; deposits remain instant; and locked capital is the *only* thing pinned (free capital still rotates), so the live yield is unaffected for committed depositors. The same accounting also blocks routing locked collateral to utilization-gated venues — see [the no-rehypothecation rule](/security/trust-model).
</Warning>

### Depositor capital is not guaranteed

<Warning>
  **Depositor capital is NOT guaranteed: the junior tranche is first-loss; the senior tranche is structurally protected from underwriting loss only, not systemic tail.**

  Underwriting capital — whether supplied to the pooled cvAMM vault (Path A) or posted directly by a market maker (Path B) — is **at risk**:

  * **Junior tranche — first-loss.** Junior depositors absorb underwriting losses first and buffer the senior tranche.
  * **Senior tranche — protected from underwriting loss, exposed to the systemic tail.** The senior tranche is structurally shielded from ordinary underwriting loss while junior capital buffers, but it still takes the systemic tail once the junior buffer is exhausted.

  No-bad-debt is a guarantee to the **LP being paid** (see the FULL-mode payout clause), **not** a guarantee that depositor or MM capital is preserved.
</Warning>

The numbers for the P1.13 single-asset bare-pool model (`u = 0.40`): 3-year CAGR 122% / 50% / 247% (median / p10 / p90), P(losing month) 26.5%, worst month −26.8%, P(3y drawdown > 50%) 2.7%. **Senior P(loss) = 0** holds *only* while `u ≤ 1 − sf`; **junior worst case −67%.** Junior is genuinely a first-loss vol-seller position — that is the source of its yield, and the reason senior can be the calm tranche.

***

## UnderwriterVault — per-MM collateral (Path B)

When a [market maker (Path B)](/protocol/path-b-mm) wins a swap with a firm signed quote, the collateral comes from **that MM's own isolated pool** in the `UnderwriterVault`. There is no pooling and no tranching here — one MM, one balance, one set of locks.

The accounting is deliberately minimal:

$$
\texttt{availableBalance(mm)} = \texttt{deposited[mm]} - \texttt{locked[mm]}
$$

### Invariant I5 — locked ≤ deposited, by construction

The vault's whole safety story is one inequality, true by construction on both operations:

<Steps>
  <Step title="lockCollateral(mm, amount)">
    Requires `availableBalance(mm) ≥ amount`. Post-lock, `locked[mm]' = locked[mm] + amount ≤ deposited[mm]`. An MM can never lock more than it has deposited.
  </Step>

  <Step title="releaseAndDistribute(mm, lp, payout, collateral)">
    Requires `payout ≤ lockedAmount ≤ locked[mm]`. The LP receives `payout` from the MM's own pool; the MM keeps `collateral − payout`. Because the release only ever unlocks what was locked, `deposited' − locked' ≥ 0` is preserved.
  </Step>
</Steps>

$$
\boxed{\;\texttt{locked[mm]} \le \texttt{deposited[mm]} \quad \text{(invariant \textbf{I5})}\;}
$$

This is the Path-B counterpart of the pool's `totalLocked ≤ juniorAssets`. Together they are [invariant I5](/security/invariants): every payout draws on capital the counterparty already posted, never on protocol funds or other users'. A `CapitalLow` event fires when an MM's available balance drops below 20% of its deposit, as an off-chain top-up signal.

<Note>
  Path B has **no tranches and no premium split inside the vault** — 99% of the premium goes straight to the MM, 1% to treasury at creation. The MM is its own first-loss layer: it priced the load, posted the collateral, and keeps the residual `MaxIL`. There is no senior tranche to protect because there are no third-party depositors in an MM's pool.
</Note>

***

## How the two vaults compare

The vaults differ only in the accounting *around* the locked collateral. The amount locked (`MaxIL`), the payout formula (`min(realized_IL, MaxIL)`), and the settle path are identical regardless of which vault opened the swap — see [the two rails](/concepts/two-rails) and [routing](/protocol/routing).

| | **ConvexityVault** (Path A) | **UnderwriterVault** (Path B) |
| - | - | - |
| Counterparty | the pool itself | a single MM |
| Structure | dual-tranche SENIOR / JUNIOR, pooled | one isolated balance per MM |
| Capital scope | fungible across all 9 marketIds of a pair | per-MM, per-deposit |
| Lock check | `free ≥ amt` **and** `totalLocked + amt ≤ juniorAssets` | `available ≥ amt` |
| Solvency invariant | `totalLocked ≤ juniorAssets` (pool side of I5) | `locked ≤ deposited` (I5) |
| Premium handling | 99% split senior/junior via `accruePremium`, 1% treasury | 99% to MM, 1% treasury (no split) |
| Loss handling | junior-first waterfall, senior protected | MM bears its own loss directly |
| Withdrawal | cooldown-gated; junior can't go below `totalLocked` | MM-managed deposit/withdraw |
| Role | floor of liquidity (always quotes) | ceiling of price (wins only by beating the pool) |

***

## The settlement core sees neither

The key safety property is that `settle` does **not** branch on vault internals. It computes IL from the stored `L`, caps it at `MaxIL`, and then dispatches to whichever vault is the counterparty:

```solidity theme={null}
// settle(): dispatch by rail — the only place the two vaults diverge at settlement
if (s.mm == address(convexityVault)) {
    convexityVault.releaseAndDistribute(s.marketId, s.lp, payout, s.collateral);
} else {
    underwriterVault.releaseAndDistribute(s.mm, s.lp, payout, s.collateral);
}
```

In both branches the LP receives `payout` and the counterparty keeps `collateral − payout`. The no-bad-debt guarantee is enforced *here*, at the cap, before either vault is called — so it holds identically for both rails.

<Warning>
  **LPs are always paid with no bad debt in FULL mode — qualified: capped payoff + solvent USDC + oracle/settlement liveness.**

  The no-bad-debt guarantee is **exact only** under the full clause:

  * **FULL collateralization** — the underwriter has posted exactly `MaxIL` per swap, locked for the life of the position.
  * **Capped payoff** — the payout is `min(realized_IL, MaxIL)`. The cap is load-bearing: it is what makes `payout ≤ collateral` hold by construction.
  * **Solvent USDC** — the collateral numeraire (`dUSDC`) holds its peg and remains redeemable.
  * **Oracle / settlement liveness** — the price oracle stays live and `settle()` runs within the protocol's settlement window.
  * **No rehypothecation breach** — locked collateral stays instantly liquid; it is never routed to utilization-gated venues.

  Stated without these qualifiers, the guarantee is **not** correct.
</Warning>

***

## Live addresses

**Network:** Arbitrum Sepolia · **chainId** `421614` · **deploy block** `274081134` (subgraph startBlock) · **deployed** 2026-06-05

## Core protocol

| Contract | Address | Role |
| - | - | - |
| InflexionCore | `0xC19865cF8403F59B8Eca835833aFEe3Aa8DA4848` | Main entry point — quotes, createSwap, settlement |
| ILVault | `0x9f7615Aca943832977CEf3ac1862fD48B87b7664` | Holds LP premiums and protocol IL accounting |
| UnderwriterVault | `0x4Fb459F3393D206c2b7faD7f0fC9C35a78348D64` | Market-maker (Path B) collateral deposits |
| ConvexityVault | `0xDE2fFeBA2E6A18f3A53D43EC0fCCD299158eC30d` | Pooled cvAMM (Path A) underwriter collateral |
| Treasury | `0x96455C9b00D530BD0629b71B674298440328b1Dd` | Protocol fee recipient (also deployer) |

## Stylus & libraries

| Contract | Address | Role |
| - | - | - |
| FairValueOracle (Stylus) | `0x98a6aa75108b70fc0794bc3b87efe0ae99d5d52c` | Rust/WASM fair-premium pricing (set-once init to live VolOracle) |
| ILMath | `0x7e90362bc6Df9cb5faA13952e07853ab16c77bd2` | Core impermanent-loss math |
| TickMath (library) | `0xbf02bbc82e0fb1a4b9828bb90fc9dd9e97578965` | Tick ↔ sqrtPrice conversions (delegatecall) |
| CvammPricing (library) | `0x4a053d29a55a64172140f9ebbc27c321c0ba2b53` | Path-A pool pricing components (delegatecall) |
| SwapMath (library) | `0xf7be9745b6768d06a18c386c6db5c8cb065ba314` | Swap math helpers (delegatecall) |
| QuoteVerification (library) | `0x74819eed322f3e1a2dc10bb0d3cae50078d90807` | EIP-712 quote signature verification (delegatecall) |

## Oracles

| Contract | Address | Role |
| - | - | - |
| OracleManager | `0x2c18147B6ec75dcb330d9A48B6B96a4d1a8b529b` | Chainlink feed router, staleness & sequencer checks |
| VolOracle | `0xfdEafBB381192FC5337499d041eaead04d565Ed9` | Realized-volatility reference (sigma\_ref) |

## Uniswap

| Contract | Address | Role |
| - | - | - |
| UniswapV3Factory | `0x248AB79Bbb9bC29bB72f7Cd42F17e054Fc40188e` | v3 pool factory |
| NonfungiblePositionManager | `0x6b2937Bde17889EDCf8fbD8dE31C3C2a70Bc4d65` | LP position NFTs |
| SwapRouter02 | `0x101F443B4d1b059569D643917553c771E1b9663E` | Swap routing |
| QuoterV2 | `0x2779a0CC1c3e0E44D2542EC3e79e3864Ae93Ef0B` | Off-chain swap quoting |

## Chainlink feeds

| Contract | Address | Role |
| - | - | - |
| ETH / USD | `0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165` | Price feed (8 dec, \~120s heartbeat) |
| BTC / USD | `0x56a43EB56Da12C0dc1D972ACb089c06a5dEF8e69` | Price feed (8 dec, \~120s heartbeat) |
| ARB / USD | `0xD1092a65338d049DB68D7Be6bD89d17a0929945e` | Price feed (8 dec, 86400s heartbeat) |
| USDC / USD | `0x0153002d20B96532C639313c2d54c3dA09109309` | Price feed (8 dec, 86400s heartbeat) |

## Demo tokens / pool / LP

| Contract | Address | Role |
| - | - | - |
| WETH9 | `0x980B62Da83eFf3D4576C647993b0c1D7faf17c73` | Canonical Arbitrum Sepolia WETH (18 dec) |
| USDC (Circle) | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` | Circle native testnet USDC (6 dec) |
| dWETH (demo) | `0xA8C07E1B245B346c5D1910c5055Efe67bF9E7D1D` | Mock WETH, oracle-priced via Chainlink ETH/USD (18 dec) |
| dUSDC (demo) | `0xB89630Dc6e020ae2A84aE72b7d9EEDBDfb2C544d` | Mock USDC numeraire (6 dec) |
| Demo pool (dWETH/dUSDC, fee 500) | `0xfE1Eb4D5796a350B13852F822B3E6a9fDbb858a5` | Seeded v3 pool, tokenId 3218 \~\$100k unprotected LP |

## Lifecycle swaps

| Contract | Address | Role |
| - | - | - |
| Market maker (demo signer) | `0xe632B215120dD43A719458487bA3e03638fec953` | Path-B underwriter; deposited 5,000 dUSDC, own collateral paid settlement |

Live create→settle lifecycle (2026-06-05, short 300s fee-500 market `0xb8bbd684f213d5833886ade7b531a6949d85522249881a2b5d46a5cc76e439c2`):

* **Path A (cvAMM convexity)** — swapId 1, LP tokenId 3220 · V0 \$270,531.28 · MaxIL \$1,669.24 · premium \$9.70 (0.58% of MaxIL) · settled payout \$148.64 from ConvexityVault.
* **Path B (MM routing)** — swapId 2, LP tokenId 3221 · MM beat cvAMM (\$8.93 vs \$13.80) · MaxIL \$3,215.65 · MM collateral locked \$3,215.65 · settled payout \$245.66 from MM collateral.

<Card title="Full deployment registry" icon="book" href="/reference/deployments">
  Every address, with decimals, units, and the live create→settle lifecycle that exercised both vaults on the 2026-06-05 redeploy.
</Card>

## Related

<CardGroup cols={2}>
  <Card title="Tranches (depositor product)" icon="layer-group" href="/concepts/tranches">
    The senior/junior product framing for depositors — yield, risk, and the first-loss waterfall.
  </Card>

  <Card title="The invariants" icon="shield-check" href="/security/invariants">
    I1–I10 in full, including I5 (vault solvency) and the senior-protection guarantee.
  </Card>

  <Card title="The two rails" icon="route" href="/concepts/two-rails">
    One settlement core, two upstream underwriters — and why the difference is upstream of settle.
  </Card>

  <Card title="The load stack" icon="sigma" href="/math/load-stack">
    How utilization and concentration price marginal pool capacity, under the I10 cap.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.