MaxIL. Two contracts hold that collateral, one per underwriting rail, and a single settlement core draws on whichever one opened the swap:
ConvexityVault
UnderwriterVault
locked ≤ deposited by construction.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.
core address once and then freezes it one-way (setCore → freezeCore). Only the frozen core (the InflexionCore state machine) can call lockCollateral or releaseAndDistribute; there is no admin path to redirect a vault after freeze. See immutability & custody.ConvexityVault — the pooled, dual-tranche cvAMM
TheConvexityVault is Path A: a pooled, passive, signature-free underwriter that quotes algorithmically on-chain off the published FairPremium. 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: 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). 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.
Senior — convexity savings account
Junior — the vol seller
Accounting
The vault keeps the two tranches fully separate, plus the locked-capital bookkeeping that drives both pricing and run defense:freeAssets is what can still back a new swap. As it shrinks, the 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 everylockCollateral (reverting SeniorProtectionBreached):
Why this is the whole game: every payout is ≤ its MaxIL, which equals its locked amount, so
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.
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.Lock, settle, accrue — the three core operations
lockCollateral(marketId, amount) — at swap creation
core inside _executePathA when an LP opens a Path-A swap. Two requirements:freeAssets ≥ amountelseInsufficientFree— the vault must have uncommitted capital.totalLocked + amount ≤ juniorAssetselseSeniorProtectionBreached— the senior-protection invariant above.
releaseAndDistribute(marketId, lp, payout, collateral) — at settlement
core inside settle when the counterparty is the vault (s.mm == address(convexityVault)). It:- Requires
payout ≤ lockedAmount(PayoutExceedsCollateral) andlockedAmount ≤ lockedByMarket[marketId]— defensive re-checks of the cap. - Unlocks the collateral (reverses the
totalLocked/sumLockedSqupdates). - Applies the loss junior-first:
The
minis defense-in-depth — by the senior-protection invariant junior always covers it, soseniorLossis 0 in practice. - Transfers
payoutto the LP and emitsJuniorLoss.
collateral − payout — the residual MaxIL that was never needed.accruePremium(amount) — at swap creation (premium split)
seniorPremiumShareBps is immutable, set from the calibrated risk-parameter set, never hardcoded.Inventory, utilization, and concentration
The vault exposes three live risk reads that feed the on-chain load stack for Path-A pricing. They are not just telemetry — they actively price marginal capacity:Inventory — freeAssets vs totalLocked
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.Utilization — u = totalLocked / totalAssets
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.Concentration — normalized coverage HHI
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.LoadParams — no PARTIAL or cvAMM primitive is ever hardcoded. The full pricing math lives on the load-stack page and the I10 clamp is detailed under the 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:Cooldown-gated withdrawals
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.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.Depositor capital is not guaranteed
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) wins a swap with a firm signed quote, the collateral comes from that MM’s own isolated pool in theUnderwriterVault. There is no pooling and no tranching here — one MM, one balance, one set of locks.
The accounting is deliberately minimal:
Invariant I5 — locked ≤ deposited, by construction
The vault’s whole safety story is one inequality, true by construction on both operations:lockCollateral(mm, amount)
availableBalance(mm) ≥ amount. Post-lock, locked[mm]' = locked[mm] + amount ≤ deposited[mm]. An MM can never lock more than it has deposited.releaseAndDistribute(mm, lp, payout, collateral)
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.totalLocked ≤ juniorAssets. Together they are invariant I5: 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.
MaxIL. There is no senior tranche to protect because there are no third-party depositors in an MM’s pool.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 and routing.
The settlement core sees neither
The key safety property is thatsettle 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:
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.
Live addresses
Network: Arbitrum Sepolia · chainId421614 · deploy block 274081134 (subgraph startBlock) · deployed 2026-06-05
Core protocol
Stylus & libraries
Oracles
Uniswap
Chainlink feeds
Demo tokens / pool / LP
Lifecycle swaps
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.