Skip to main content
Inflexion reads the outside world through exactly two oracle contracts, and they do two very different jobs. OracleManager answers the question “what is the price?” — at entry, and again at expiry. VolOracle answers the question “how volatile has the price been?” — producing σref\sigma_{\text{ref}}, the only stochastic input to the fair-value Φ-sum. Everything else in the protocol is geometry, arithmetic, and accounting. The split matters because the two oracles sit on opposite sides of the no-bad-debt line:

OracleManager — the price

Pins P0P_0 at entry and PTP_T at settlement, both on-chain from Chainlink, never caller-supplied. Liveness of this oracle is part of the no-bad-debt clause — settlement must eventually succeed.

VolOracle — the volatility

Produces σref\sigma_{\text{ref}} for pricing. It is solvency-load-bearing (it sets the load and depositor NAV) but not structural: a wrong σref\sigma_{\text{ref}} can cost depositors, never the LP.
Live addresses for both contracts are in the deployments registry.

OracleManager — price at entry and at expiry

OracleManager (0x2c18147B…) is the single gate between Chainlink and the protocol. It exposes two read paths with deliberately different behavior, because entry and settlement have different threat models.
getPrice(token) reads the current Chainlink latestRoundData, health-gated (sequencer up + grace, not stale). There is no lone-spike check — it is the current price, and the LP picks their own entry block. The result pins P0P_0 on-chain, never from calldata. The same read is reused as g.livePrice for the Path-B oracle band check (I9), so the band costs zero extra oracle calls.

Why both prices are pinned on-chain

A review found that an earlier createSwap/settle once accepted a caller-supplied sqrt price. settle computed payout from the caller’s sqrtP_T, so any permissionless settler could set the payout anywhere in [0,MaxIL][0, \text{MaxIL}]; and createSwap let an LP pick the entry that sets MaxIL / premium / V0.
Limit → Why → Mitigation. Limit: a caller-supplied price would hand a permissionless settler full control of the payout, and an LP control of their own entry. Why it’s dangerous: it breaks I2 (cap correctness) and I4 (LP never profits from the swap) — the payout would no longer reflect the true price. Mitigation: both prices are now derived on-chain from Chainlink via _oracleSqrtPriceX96 at settle and at entry, decimal- and orientation-aware, with MarketConfig decimals read on-chain at registerMarket and the oracle orientation frozen across re-registration. A single wrong decimal would mis-scale every price ~10×, so decimals are never trusted from calldata.
The settlement price is therefore reproducible by anyone, and the only freedom left to a settler is which block they call settle in — which the round-at-T pin renders moot, because the price is fixed to the round bracketing TT regardless.

The settlement guard stack

getSettlementPrice walks four guards, in order, before it returns the round-at-T price. Each one fails closed (reverts and is retryable) except the last, which is the backstop that guarantees the call eventually succeeds.
1

Sequencer uptime + grace

On Arbitrum, a Chainlink L2 Sequencer Uptime Feed must report the sequencer up, and at least GRACE_PERIOD = 3600s (1h) must have elapsed since it came back. The check fails closed on a future updatedAt. This stops anyone settling on a stale price formed while the sequencer was down and users could not react.
2

Max-staleness

The round-at-T must be fresh enough: MAX_STALENESS = 90,000s (the 86,400s heartbeat + a 1h buffer). The same value is used on testnet and mainnet so the constant does not change across deploys.
3

Lone-spike sanity check

A glitched single print is rejected: the round-at-T is a “lone spike” only if it differs from both of its immediate neighbours by ≥ LONE_SPIKE_BPS = 500 (5%). A real fast move is sustained, so the next round confirms it and the check passes; only a one-round glitch with two normal neighbours is deferred.
4

Liveness backstop (Fork-1)

If the lone-spike check defers, settle is retried. Past LIVENESS_WINDOW = 86,400s (24h) the Chainlink round-at-T is accepted unconditionally — funds can never lock indefinitely. This is invariant I8.
The Uniswap v3 TWAP is advisory only — emitted as an event, never a revert condition. That is the whole point of the Fork-1 redesign: the old hard-TWAP gate could deadlock forever, so it was demoted to telemetry.

The settlement constants

The Fork-1 liveness backstop, precisely

Invariant I8 states that settle() always succeeds within expiry + LIVENESS_WINDOW + MAX_STALENESS + GRACE_PERIOD. The lone-spike check can never permanently lock funds: a glitched print defers settlement, but only until block.timestamp ≥ expiry + LIVENESS_WINDOW, after which the backstop accepts the round unconditionally. A missing neighbour round across a Chainlink phaseId boundary is read tolerantly via _tryRound (a try/catch that returns ok=false instead of reverting), so even a phase gap degrades to the backstop rather than a permanent lock.
Documented design observation (a known boundary, not a bug). Limit → Why → Mitigation. Limit: the spec-literal staleness require fires before the backstop can recover the round, so the actual usable settle window via the round-at-T is [expiry + LIVENESS_WINDOW, expiry + MAX_STALENESS) — about 1 hour wide (90,000−86,400=3,600s90{,}000 - 86{,}400 = 3{,}600\text{s}), not the full additive sum the I8 wording suggests. Why it’s acceptable: the window is symmetric for LP and MM (it preserves fairness — neither side can exploit it), and past expiry + MAX_STALENESS funds can still settle via a newer Chainlink round (Arbitrum majors tick frequently), just not via the round-at-T. Mitigation / path-forward: the behavior is surfaced explicitly in NatSpec and pinned by a dedicated boundary test — it is documented, not papered over.
I8 is verified by five fuzz tests, ~1,280 runs: testFuzz_I8_settleSucceedsAnywhereInBackstopWindow, testFuzz_I8_sustainedMoveSettlesImmediately, testFuzz_I8_loneSpikeDefersBeforeBackstop, testFuzz_I8_sequencerRecoveryRespectsGrace, and test_I8_revertsPastStaleness_boundary.

hintRoundId — supplied, then verified

settle(swapId, hintRoundId) takes a hintRoundId from the keeper, but the oracle does not trust it. getSettlementPrice verifies that the hinted round actually brackets expiry (updatedAt≤expiry<nextUpdatedAt\text{updatedAt} \le \text{expiry} < \text{nextUpdatedAt}) before using it. The hint is a gas optimization — it tells the oracle where to look in Chainlink’s round history — not an input it has to believe. The round-at-T pin is what kills the settle-timing game; the hint just makes finding that round cheap.
OracleManager reads canonical Chainlink USD price feeds (8-decimal). On Arbitrum Sepolia these are verified against the Chainlink reference-data directory (verified 2026-05-28):
The dWETH token is oracle-priced via the real Chainlink ETH/USD feed, so the live testnet markets move with actual ETH. All four feeds, plus the full address registry, are listed in the deployments reference.
Mainnet feeds differ. The Arbitrum One values for the eventual mainnet — ETH/USD 0x639Fe6ab…, the real Sequencer Uptime Feed 0xFdB631F5…, etc. — are migration references, not the live testnet deployment. Do not conflate them with the Sepolia registry.

The Sepolia sequencer caveat

Limit → Why → Mitigation. Limit: there is no Chainlink L2 Sequencer Uptime Feed on Arbitrum Sepolia — the Sequencer Uptime row above is null. Why: Sepolia is a testnet with no SLA, so Chainlink does not publish a sequencer feed there. Mitigation: OracleManager treats sequencerFeed == address(0) as skip-the-sequencer-check — safe on testnet, where a brief sequencer wobble carries no real funds risk. The sequencer gate is fully implemented and ready; it simply has no feed to read on Sepolia. It MUST be set before any mainnet deploy — this is flagged in the registry’s _sequencerNote, and is tracked as a named mainnet-TODO.
A second deliberate choice worth knowing: testnet feeds tick faster (~120s for ETH/BTC) than mainnet (86,400s heartbeat), but MAX_STALENESS is fixed at 90,000s so the same constant is correct on either chain — no per-network re-tuning of the staleness gate.

VolOracle — sigma_ref, the only stochastic input

VolOracle (0xfdEafBB3…) produces a single number, σref\sigma_{\text{ref}}, that feeds the Φ-sum pricer. It is the only stochastic input to fair value — everything else (a=Pa/P0a = P_a/P_0, b=Pb/P0b = P_b/P_0, TT, MaxIL) is geometry and time.

The construction

σref=max⁡ ⁣(σshort, σlong, floor)\sigma_{\text{ref}} = \max\!\bigl(\sigma_{\text{short}},\ \sigma_{\text{long}},\ \text{floor}\bigr) The _sigmaRef computation drives this. σshort\sigma_{\text{short}} and σlong\sigma_{\text{long}} are time-aware EWMAs of Chainlink log-returns over two halflives. Each poke folds the latest price into a per-second variance, annualizes over a 365-day year (SECONDS_PER_YEAR=31,536,000\text{SECONDS\_PER\_YEAR} = 31{,}536{,}000), and takes the conservative max against a hard floor.
Mandatory conservatism — never price off raw realized σ. Limit → Why → Mitigation. Limit: a naïve volatility oracle would read the raw realized σ, which understates risk right before a regime change — the stale-σ jump is exactly where a vol-seller’s book bleeds. Why it’s dangerous: under-pricing the load at the worst possible moment compresses depositor NAV (a depositor-money risk; see tranches). Mitigation: the max(σ_short, σ_long, floor) construction cannot collapse to a deceptively calm number before a jump — it can never fall below the slower estimate or the floor. This conservatism is required by design, not a tuning choice.

Poke mechanics

σ_ref is kept fresh by a permissionless poke rather than a dedicated keeper. The poke is folded directly into createSwap, so the vol estimate refreshes on the same transaction that prices a swap — no separate cron job, and the router pokes exactly once before pricing both rails off the resulting FairPremium.
  • No-op when too soon. poke is a no-op (and emits no event) when dt < minSampleInterval, so it is safe to fold into every create without micro-samples blowing up the r2/dtr^2/dt ratio.
  • dt is clamped to [minSampleInterval, maxSampleInterval], so one huge gap can neither divide by a tiny dt nor over-weight a single sample.
  • First poke seeds both windows at the floor, so the very first reads are conservative rather than wild.
  • sigmaComponents.binding reports which input bound σref\sigma_{\text{ref}}: 0 = σ_short, 1 = σ_long, 2 = floor — distinct from the load regime.
All windows, floor, and cadence come from the calibrated risk-parameter set (the cvAMM block) — none is hardcoded. Hardcoding any σ primitive is the exact failure the audit flagged.
On the fresh 2026-06-05 deploy, σref\sigma_{\text{ref}} was initialized to the 0.5e18 floor, which gives fairRate≈0.847\text{fairRate} \approx 0.847 at floor vol. Real dynamics accrue as the VolOracle is poked over time; until then, the floor is the conservative starting point.

Load-bearing scope — precisely what σ can and cannot break

This is the single most important thing to state correctly about VolOracle, because it sits on exactly one side of the no-bad-debt line.
A wrong or too-low σref\sigma_{\text{ref}} makes the pool under-charge load — the load stack prices off σref\sigma_{\text{ref}}, so a low reading compresses NAV. Any resulting underwriting loss hits the tranche waterfall junior-first. So a vol-oracle fault can cost depositors money. This is why the conservatism above is mandatory.
I1 is structural and oracle-independent. In FULL mode collateral = MaxIL ≥ payout for any σref\sigma_{\text{ref}} — the cap and the collateral are equal by construction, and σref\sigma_{\text{ref}} never enters the settlement math. So a wrong σ cannot violate I1. A vol-oracle fault can cost depositors, but it can never create LP bad debt in FULL.
The crisp version: the price oracle (OracleManager) liveness is part of the no-bad-debt clause — if settlement can never run, funds lock — which is precisely why the Fork-1 backstop exists. The vol oracle (VolOracle) is not in that clause at all; it governs pricing and depositor returns, never the LP guarantee.
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.

The two oracles, side by side


Where to go next

The two architectural forks

Fork-1 (the settlement-liveness backstop, I8) and Fork-2 (the oracle-anchored price band, I9) in full, with the audit history behind each.

Price pinning

Why both P0P_0 and PTP_T are derived on-chain, and the trust hole that closed.

The fair-value Φ-sum

Where σref\sigma_{\text{ref}} enters: the exact closed-form pricer it is the only stochastic input to.

sigma_ref, in depth

The EWMA construction, the floor, and the conservatism argument worked through.

The invariants

I8 (settlement liveness) and I10 (price cap), and exactly where each is enforced.

The deployment registry

Every live address — both oracles, all four Chainlink feeds, and the sequencer note.
Structures ship from day one; the full DYNAMICS (pool-vs-MM spread as a forward-vol signal) mature with multiple competing MMs and volume. The dataset begins at the single redeploy; the off-chain telemetry halves begin at the first interaction logged.What Inflexion offers today is the architecture and the first public view into the microstructure of the DeFi LP volatility-risk premium — not yet a mature dataset:
  • Structures are present day-one. All five non-circular behavioral signals are defined and queryable from launch.
  • Dynamics mature with volume. The richer signal — the pool-vs-MM spread read as a forward-vol indicator — only sharpens once multiple market makers compete and real flow accumulates.
  • The dataset begins at the single redeploy, and the off-chain telemetry halves begin at the first interaction logged. History is served live from the subgraph via the API; if a backing (subgraph URL / RPC / telemetry) is absent, surfaces degrade to a typed pending state — never an error.