There are five signals — and exactly one circular idea that is deliberately NOT one
of them. “Infer implied vol by inverting
fairRate” is dropped as circular: the
inversion charged/MaxIL = fairRate(σ_ref)·(1+load) returns only the protocol’s own
σ_ref and load stack, nothing external. The five below are behavioral — they extract free actor
choices the protocol does not compute, measured against the transparent mechanical
baseline. See circular vs behavioral for the full
worked argument.The shape every signal shares
Before the five, internalize the pattern they all share. Four of the five separate a behavioral leg — an actor’s free choice — from a mechanical baseline — the protocol’s transparent price-to-beat — and the subtraction is what isolates the actor’s view and removes the circular component:totalLoad = f(σ_ref, util, conc) is a deterministic
function of protocol state; inverting it just recovers our own formula. It is kept as
the price-to-beat that the behavioral MM load is measured against. Two corollaries recur in every signal below:
- The mechanical pool load is the yardstick, not the signal. Its job is to be the transparent baseline the behavioral MM load is subtracted from. The subtraction is precisely what removes the circular component.
- Cap-bound fills carry ZERO load information and MUST be excluded from Signals 1,
2, and 3. When
premium == MaxILthe load was truncated by the cap — the number you observe is the cap, not the actor’s load. This is why theSwapPricedevent carries an explicitcappedAtMaxILboolean, enforced in the indexer.
loadBps or the LP’s
geometry/duration choice — measured against the transparent mechanical baseline, not
derived from it.
The coordinate system: geometry buckets
Every geometry-bucketed signal (1, 3, 4) keys on the same width × distance-to-edge × duration triple, chosen to mirror the off-chain telemetry buckets so the realized (on-chain) and latent (off-chain) halves share one coordinate system the API can join on:
Geometry is not stored in any event — it is recovered off-chain by decoding the
swap’s
tokenId via NonfungiblePositionManager.positions(tokenId) at the
SwapCreated block (range ticks → ), plus expiry − createdAt for duration. That MaxIL is
the unit that makes positions fungible — premium quoted as % of MaxIL is range-agnostic —
is exactly why bucketing the load by geometry is informative rather than noisy.
Signal 1 — Realized clearing LOAD over a transparent σ_ref
bucketed by width × distance-to-edge × durationVerdict
Non-circular: PARTIAL — Path B only.
Maturity
Structural at launch; a regime needs ≥3 MMs.
getLPStructuralIV precisely to avoid the misleading “IV” connotation.
Precise definition. Per geometry bucket, the distribution of two legs:
- POOL load
SwapPriced.totalLoadWad— the mechanical load stack (the baseline / price-to-beat), itself the I10-clamped sum . - MM load
QuoteFilled.loadBps— the behavioral actor choice (the real signal), normalised againstSwapPriced.fairPremiumandSwapPriced.sigmaRefWadat each fill.
QuoteFilled.loadBps (the load) + SwapPriced.sigmaRefWad /
fairPremium (the baseline + reference vol) + NPM.positions(tokenId) at the
SwapCreated block (width / distance) + expiry − createdAt (duration). The subgraph
joins all of these on swapId → tokenId into the BucketAggregate entity, keyed
${width}-${distance}-${duration}, accumulating sumMMLoadBps, sumPoolLoadWad,
sumSigmaRefWad, and totalNonCappedFills over non-capped fills only; the API
derives means as sum / count because the store has no median operator. The current (non-historical)
load surface is served live now by the SDK getCurrentLoadSurface multicall — the one
live-RPC exception, computing the price-to-beat client-side via the parity-locked
CvammPricing TS port (see the SDK port).
Exclude cap-bound fills. Filter
SwapPriced.cappedAtMaxIL == true — a cap-bound
fill priced against the cap, not against the risk, and would poison a load mean.Signal 2 — Pool-vs-MM load SPREAD + MM win-rate / win-depth
Verdict
Non-circular: YES — as a SPREAD.
Maturity
Structural at launch; dynamic only with ≥3 MMs.
- aggressive undercut ⇒ the market sees vol cheaper than the backward-looking σ_ref ⇒ expects vol to fall;
- MMs retreat above the pool ⇒ they expect vol to rise.
Swap entity stores
spreadWad = poolLoadWad − mmLoadBps(WAD) (meaningful only on a Path-B win),
BucketAggregate.sumSpreadWad accumulates it per bucket, and winDepth = premiumA − premiumB is carried from the routed SwapRouted event.
How it is sourced. On a Path-B win, SwapPriced carries the losing pool quote’s
totalLoadWad and fairPremium (the price-to-beat the MM beat), and QuoteFilled.loadBps
carries the winning MM load — so the spread is in two joined events with no archive
call. Per-MM win-rate is
tracked on the MarketMaker entity as cumulativeWinCount / cumulativeQuoteFillCount.
The event-order subtlety (load-bearing). On every Path-B create, the contract emits
QuoteFilled before SwapPriced (the priced event is emitted after the
_executePathB call). So the spread is
computed in handleSwapPriced (the later handler), because only there are all three
values available at once: the pool totalLoadWad, the authoritative cappedAtMaxIL,
and the swap’s persisted mmLoadBps. Computing it in handleQuoteFilled would read a
not-yet-written poolLoadWad ⇒ spread always null.COMPETITION_LOG, written for every inbound WS quote — winners AND
losers / stale / withdrawn (schema CompetitionRecord: { ts, marketId, mm, loadBps, validUntil, accepted, reason? }); see
the telemetry pipeline. This data is unreconstructable
retroactively, which is exactly why the sink captures it from the first interaction.
Signal 3 — TERM STRUCTURE of convexity
the slope of load across 7 / 30 / 90d markets, per rangeVerdict
Non-circular: PARTIAL — watch the Path-A subtlety.
Maturity
Structure day-one; slope-as-signal matures with volume.
marketId, so the same geometry yields a different market and a different price
per duration). How the load evolves with maturity per range is the
LP-convexity-premium term structure. The slope is the signal.
Precise definition. For a fixed (width, distance) range, the curve of
QuoteFilled.loadBps across the three duration markets; its slope is the MM’s term
view. On the subgraph, the 7/30/90d rows of one (width, distance) pair in
BucketAggregate give the slope directly.
How it is sourced. QuoteFilled.loadBps per fill + duration (expiry − createdAt)
- geometry (
NPM.positions), bucketed by range;SwapPriced.fairPremium/sigmaRefWadanchor each point; no archive call onceSwapPricedlands. The duration ladder is one query againstBucketAggregatefiltered to a(widthBucket, distanceBucket)pair. Exclude cap-bound fills.
Signal 4 — MONEYNESS / DEMAND SKEW by geometry
Verdict
Non-circular: PARTIAL — realized is on-chain, latent is off-chain.
Maturity
Structural; leading-indicator quality needs flow + telemetry.
- Realized (on-chain)
- Latent (off-chain telemetry)
SwapCreated(swapId, lp, mm, tokenId, V0, …) + NPM.positions(tokenId) + duration,
aggregated per geometry bucket — fully on-chain, no new event, the decode is
subgraph work. The subgraph entity is GeometryDemandBucket:
realizedFillCount, realizedV0, firstSeen, lastSeen, keyed by the same geometry
triple.It is separate from BucketAggregate by design: demand skew counts every
create — including capped fills, because demand is demand — whereas BucketAggregate
excludes capped fills (load info only). Different inclusion rules ⇒ different entity.Signal 5 — NET CONVEXITY / GAMMA SUPPLY
protocol-wideVerdict
Non-circular: YES — as a supply/quantity gauge.
Maturity
Meaningful from a handful of swaps — the most launch-robust of the five.
liquidity / amount{0,1}Entry /
ticks, fixed by invariant I6) — weighted by the realized load it was sold at (Path-B
behavioral; Path-A mechanical baseline). Plus
and the demand rate per market:
How it is sourced. Off-chain compute over the subgraph-tracked open set
(SwapCreated opens, SwapSettled closes; Swap.isActive is open-set membership). The
subgraph maintains the active-swap set with each swap’s geometry; the API / GreeksEngine
sums Greeks over it — not a new event. Two surfaces:
- the
ProtocolStatesingleton (id = 'global') holds the live counters —activeSwapCount, totalActiveV0, totalActiveMaxIL, cumulativeSwaps, cumulativeSettled— bumped onSwapCreated, decremented onSwapSettledwith floor-at-zero guards; - the
NetGammaSnapshottime series holdsactiveSwapCount, totalV0, totalMaxILplus the API-written-back GreeksaggGammaWad,aggVegaWad, andvolumeWeightedLoadWad(null until the GreeksEngine computes them).
Indexer note.
NetGammaSnapshot is schema-ready and API-populated, not
indexer-populated — no mapping handler currently writes a row; the live active-set
counters are maintained on ProtocolState instead, and ProtocolState.nextNetGammaBucket
exists to drive a future bucket roll. The current snapshot
(via ProtocolState) is live now from the deployed subgraph via the API; the Greeks
series is back-filled by the API.The five signals at a glance
The decision-table source for these verdicts, and the full proof of why “invert
fairRate” is the one excluded idea, is on
circular vs behavioral.
How a consumer reaches each signal
The three-layer routing rule: needs to write or needs the exact price now → SDK; needs history or an aggregate → subgraph (consumed via the API); needs frictionless public access → API. Per-signal access:
For the full access map and the SDK methods see
the access map and the data client.
State of the dataset
The static structures ship now; the full dynamics require competition and flow.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.
The subgraph is deployed and indexing — so history and aggregate surfaces are live.The subgraph is deployed on The Graph Studio (slug
inflexion-arb-sepolia) and has
indexed on-chain history from the redeploy block 274081134 (including the lifecycle
swaps). Every history / aggregate moat surface is served live via the hosted API at
https://inflexion-backend.onrender.com — no longer a typed pending state. The
per-MARKET dimension is indexed too: the API /markets route returns per-market
aggregates, so the old per-market backfill gap is resolved. The GEOMETRY-keyed
aggregates — BucketAggregate (Signals 1/2/3) and GeometryDemandBucket (Signal 4) —
are fully populated, as is the current load surface (served via the SDK) and the
telemetry halves. If SUBGRAPH_URL were ever unset, each surface degrades gracefully to
a typed pending body — never an error — but that is a design property, not the current
state. On the free hosting tier the backend sleeps after ~15 min idle, so the first
request after a quiet period incurs a cold start.Go deeper
Circular vs behavioral
The non-circularity decision table and the worked proof of why inverting
fairRate
is circular — and the only idea excluded.On-chain primitives
The two moat events (
SwapPriced, QuoteFilled) and the CvammPricing.loadComponents
breakdown library.Telemetry pipeline
The off-chain
DEMAND_LOG / COMPETITION_LOG sinks — the latent and dynamic halves
of Signals 4 and 2.The access map
The three-layer routing rule and the full per-signal SDK / API / subgraph table.
The moat thesis
The full argument for why the flow is a defensible dataset, and who consumes it.
State and limits
The exact state of the dataset and the complete set of known limits.