Skip to main content
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:

ConvexityVault

The pooled, always-on Path A underwriter. Dual-tranche SENIOR / JUNIOR USDC, fungible across all 9 marketIds of a pair. The floor of liquidity.

UnderwriterVault

Per-MM collateral for Path B. One isolated pool per market maker; invariant I5 keeps locked ≤ deposited by construction.
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.
Each vault wires its 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

The ConvexityVault 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

Low-yield, structurally protected from underwriting loss while junior capital buffers. Takes a small, configurable share of every premium.

Junior — the vol seller

High-APY, first-loss. Absorbs all underwriting loss before senior is ever touched, and earns the bulk of the premium for doing so.
For the depositor-facing product framing of these two tranches, see Tranches.

Accounting

The vault keeps the two tranches fully separate, plus the locked-capital bookkeeping that drives both pricing and run defense: Two derived quantities matter everywhere downstream: totalAssets=seniorAssets+juniorAssetsfreeAssets=totalAssets−totalLocked\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’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):   totalLocked≤juniorAssets  \boxed{\;\texttt{totalLocked} \le \texttt{juniorAssets}\;} Why this is the whole game: every payout is ≤ its MaxIL, which equals its locked amount, so ∑payouts  ≤  totalLocked  ≤  juniorAssets.\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.
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.
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 for the strict separation of the LP guarantee from depositor risk.
This pool-side invariant is the cvAMM half of I5 (vault solvency) and is reviewed in full on the invariants page.

Lock, settle, accrue — the three core operations

1

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:sumLockedSq+=amt2+2⋅x⋅amt(where x=lockedByMarket[marketId] before the add)\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.
2

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: juniorLoss=min⁡(payout, juniorAssets),seniorLoss=payout−juniorLoss\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.
3

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:toSenior=amount⋅seniorPremiumShareBps104,toJunior=amount−toSenior\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.
The premium split (accruePremium) and the loss waterfall (releaseAndDistribute) are orthogonal to settle semantics and to the I10 price cap. 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.

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:
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.
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.
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.
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 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:
1

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.
2

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.
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.

Depositor capital is not guaranteed

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.
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 the UnderwriterVault. There is no pooling and no tranching here — one MM, one balance, one set of locks. The accounting is deliberately minimal: availableBalance(mm)=deposited[mm]−locked[mm]\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:
1

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.
2

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.
  locked[mm]≤deposited[mm](invariant I5)  \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: 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.
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.

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 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:
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.
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.

Live addresses

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

Core protocol

Stylus & libraries

Oracles

Uniswap

Demo tokens / pool / LP

Lifecycle swaps

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.

Full deployment registry

Every address, with decimals, units, and the live create→settle lifecycle that exercised both vaults on the 2026-06-05 redeploy.

Tranches (depositor product)

The senior/junior product framing for depositors — yield, risk, and the first-loss waterfall.

The invariants

I1–I10 in full, including I5 (vault solvency) and the senior-protection guarantee.

The two rails

One settlement core, two upstream underwriters — and why the difference is upstream of settle.

The load stack

How utilization and concentration price marginal pool capacity, under the I10 cap.