pending state when a backing is
absent.
If you want the what and the why-non-circular of the signals, read
/moat/five-signals and /moat/circular-vs-behavioral.
If you want the primitives that feed them, read /moat/primitives.
This page is the plumbing.
The three-layer routing rule
Before the per-signal table, internalize the rule that decides which layer you reach for:SDK
Needs to write, or needs the exact price right now. The SDK is the only layer that
holds a signer and the only one that can answer “what dollar premium will I be charged
on THIS position if I sign in the next block?” — a fresh on-chain read plus the
position geometry.
Subgraph
Needs history or an aggregate. The subgraph is internal infrastructure — nobody
queries it directly in production except the API.
API
Needs frictionless public access with no wallet and no RPC key. A thin, cached,
read-only facade over the subgraph (+ a small set of cached live RPC reads routed
through the SDK, so pricing has exactly one implementation).
One pricing implementation, three faces. The current load surface (“price-to-beat”)
is computed in exactly one place — the SDK’s
CvammPricing TS port.
The API’s live RPC reads route through the SDK; the subgraph never reprices, it only
records what already cleared. There is no second pricing model to drift out of parity.
The Phi-sum is never reimplemented anywhere — it is read
from the on-chain FairValueOracle.The per-signal access map
This is the page’s core artefact. For each signal: theDataClient SDK method, the API
route, the subgraph entity, and the live-now flag. The API-route strings are the
SDK↔API contract named in DATA_API_ROUTES.
Read the “Live now?” column carefully — it is not binary per signal. Three of the
five signals are split across two live halves with distinct sources. Signal 1’s
current surface is live RPC while its history is served from the subgraph; Signals 2
and 4 each have an off-chain telemetry half (the dynamic / latent half that can
never be on-chain) alongside a structural half served from the subgraph. The split is
the point — it is what makes the dataset captured, not retroactive (see the I7
exclusion below).
The one live SDK exception — getCurrentLoadSurface
Every other DataClient history method is API-backed (served from the subgraph via the
hosted API). getCurrentLoadSurface is the single exception: it hits live RPC
directly to compute the current pool load surface — the price-to-beat, with no subgraph
in the path. It is the live-RPC half of Signal 1.
1
One multicall per market, over public getters
Per market, a single multicall reads:
InflexionCore.markets(marketId)→ market config;FairValueOracle.fairPremium(token, a, b, T, maxIL)→(premium, fairRate, σ_ref)— the Phi-sum, read on-chain, never reimplemented;ConvexityVault.inventory()→(total, locked, free, util, conc);InflexionCore.loadParams()(read once, shared across markets).
2
Finish the load stack client-side (parity-locked)
The load stack is completed client-side with the parity-locked
CvammPricing TS port, thenThis is exactly the on-chain _pricePathAFromFair path. The client-side
re-evaluation is permitted only because the deployed CvammPricing library is
delegatecall-only — a direct eth_call reverts — so the SDK keeps a byte-equal TS
mirror asserted in math.parity.test.ts. The Phi-sum is never mirrored;
only the deterministic pure load transform is.3
Per-market graceful degradation, inline
An unknown/inactive market or a reverting oracle yields an inlined degraded
SurfaceRow (oracle-degraded / market-unknown) — never a thrown call. A market that
supplies no geometry uses a neutral reference geometry, because the load % is
geometry-independent; only the dollar fairPremium/poolPremium scale with a/b/maxIL.data-client.ts
Why
getCurrentLoadSurface is the baseline, not a behavioral signal. What it
serves is the Path-A mechanical pool load — the deterministic price-to-beat. That leg
is circular by itself (inverting it recovers our own
load stack). Its job in Signal 1 and Signal 2 is to be the transparent yardstick
the behavioral MM load is measured against — the subtraction mmLoad − poolLoad is
exactly what removes the circular component. The historical evolution of this same
surface is getLoadSurfaceHistory (API-backed, served live from the subgraph).The two telemetry halves that are live now
Signals 2 and 4 each have a half that is already capturing, served from off-chain engine telemetry rather than the chain or the subgraph. This isTelemetrySink, writing
two append-only JSONL sinks from the first interaction. The writer is best-effort: a
telemetry failure never breaks a quote ack, a preview, or a /quote request.
DEMAND_LOG → Signal 4 (latent half)
Written on every
GET /quote and every POST /telemetry/preview ping. Schema
DemandRecord is coarse-bucketed and PII-free — { ts, marketId, widthBucket, distanceBucket, durationBucket, previewedPremium?, filled:false (ALWAYS — this is the latent half), source }. No raw geometry, no tokenId, no addresses — the SDK
computes the buckets client-side and POSTs only the labels. The SDK
LpClient.previewPremium fires this fire-and-forget; it never blocks
or fails the preview.COMPETITION_LOG → Signal 2 (dynamic half)
Written for every inbound WS quote — winners AND losers/stale/withdrawn-equivalent.
Schema
CompetitionRecord: { ts, marketId, mm, loadBps, validUntil, accepted, reason? }. Without it, the engine keeps only the latest stored quote per (market, MM) — the
competing field (who else quoted, how wide, who widened/withdrew) is lost.Why two halves can never be on-chain
This is the structural reason the telemetry sinks exist — and why capturing them now is non-negotiable rather than a nice-to-have.Graceful degradation everywhere
Degradation is not an edge case in the access layer — it is the default contract at every layer. Nothing throws; everything degrades to a typed, named state. This is what lets the docs, the app, and any integrator render the dataset’s current maturity instead of showing an error.- API layer
- SDK layer
- Subgraph layer
Every API endpoint returns a discriminated union and never throws:
- Live RPC + telemetry routes return real data NOW.
- Subgraph-backed routes return real data NOW, served from the deployed subgraph via
the hosted API. If
SUBGRAPH_URLwere unset, they would degrade to a typedpendingbody with the exact GraphQL query embedded, so the UI can show “coming from<route>” and the route wires up with zero re-derivation.
Live. Every history/aggregate surface — Signal 1’s history, Signal 3 entirely, the
structural halves of Signals 2 and 4, and Signal 5 — now serves real data, indexed by the
deployed subgraph and served through the hosted API.How. The on-chain moat events (
SwapPriced, QuoteFilled) and
CvammPricing.loadComponents shipped in the 2026-06-05 redeploy
and emit every field the indexer needs. The redeploy block 274081134 is the subgraph
startBlock — the dataset’s genesis — and the indexer has caught up from there, including
the lifecycle swaps. The live RPC (current load surface, σ_ref) and telemetry
(latent demand, quote competition) surfaces serve real data alongside it. Both the
per-MARKET dimension (the API /markets route returns per-market aggregates) and the
GEOMETRY-keyed aggregates (BucketAggregate, GeometryDemandBucket) are populated, so
every moat signal is reachable today. See /moat/state-and-limits
and /reference/status-caveats.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.
Live-now summary
A one-glance read of what serves real data today versus what degrades to a typedpending state:
The pattern, restated. Anything that needs the price right now or who quoted what
right now is live (SDK live RPC + engine telemetry). Anything that needs history or an
aggregate distribution is served live from the subgraph via the hosted API — and would
degrade to a typed
pending state, with the query embedded and never an error, only if a
backing were absent. The dataset’s structures are all reachable today; its dynamics
(regimes, forward-vol distributions, dispersion) mature with more MMs and flow.Where to go next
The five signals
Each behavioral signal, defined precisely, with its non-circularity verdict, sourcing,
and maturity caveat.
The on-chain primitives
SwapPriced, QuoteFilled, the load-breakdown library, and why two signals can never
be on-chain (I7).The DataClient
The SDK surface for the moat — method signatures, degradation envelopes, and the live
load-surface read.
State and limits
The framed account of what is live, what is pending, and the path forward for each
surface.