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 , 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 at entry and 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 for pricing. It is solvency-load-bearing
(it sets the load and depositor NAV) but not structural: a wrong
can cost depositors, never the LP.
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.
- Entry — getPrice
- Settlement — getSettlementPrice
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
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 earliercreateSwap/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 ; and createSwap let an
LP pick the entry that sets MaxIL / premium / V0.
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 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 settlement constants
The Fork-1 liveness backstop, precisely
Invariant I8 states thatsettle() 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.
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 () 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.
The Chainlink feeds
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
A second deliberate choice worth knowing: testnet feeds tick faster (~120s for ETH/BTC) than mainnet (86,400s heartbeat), butMAX_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, , that feeds the Φ-sum pricer.
It is the only stochastic input to fair value — everything else
(, , , MaxIL) is geometry and time.
The construction
The_sigmaRef computation drives this. and
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
(), and takes the
conservative max against a hard floor.
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.
pokeis a no-op (and emits no event) whendt < minSampleInterval, so it is safe to fold into every create without micro-samples blowing up the ratio. dtis clamped to[minSampleInterval, maxSampleInterval], so one huge gap can neither divide by a tinydtnor over-weight a single sample.- First poke seeds both windows at the floor, so the very first reads are conservative rather than wild.
sigmaComponents.bindingreports which input bound :0 = σ_short,1 = σ_long,2 = floor— distinct from the load regime.
On the fresh 2026-06-05 deploy, was initialized to the 0.5e18
floor, which gives 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 aboutVolOracle, because it
sits on exactly one side of the no-bad-debt line.
What σ_ref IS load-bearing for: the I10 cap and depositor solvency
What σ_ref IS load-bearing for: the I10 cap and depositor solvency
A wrong or too-low makes the pool under-charge load — the
load stack prices off , 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.
What σ_ref is NOT load-bearing for: the FULL no-bad-debt invariant (I1)
What σ_ref is NOT load-bearing for: the FULL no-bad-debt invariant (I1)
I1 is structural and oracle-independent. In FULL mode
collateral = MaxIL ≥ payout for any — the cap and the
collateral are equal by construction, and 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.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.
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 and are derived on-chain, and the trust hole that closed.
The fair-value Φ-sum
Where 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
pendingstate — never an error.