The three-layer routing rule
The rule is stated verbatim as:Needs to write, or needs the exact price right now → SDK. Needs history or an aggregate → Subgraph (consumed via the API). Needs frictionless public access with no wallet and no RPC key → API.
SDK
Subgraph (via API)
API
node:http facade composing the subgraph, a small set of live RPC reads (routed through the SDK), and the engine’s telemetry sinks.Why three layers and not one
Each layer exists because the other two cannot do its job without giving something up:FairValueOracle and read through the SDK. The deterministic load stack is computed on-chain in pricing and re-evaluated client-side only via the SDK’s parity-locked CvammPricing TS port (the deployed library is delegatecall-only, so a direct eth_call reverts). The API never re-derives the fair rate or the load stack — it forwards the SDK’s row. One implementation per concern is what keeps the access layer honest.The two off-chain services
The access layer wraps the on-chain core —InflexionCore, the Stylus FairValueOracle, the cvAMM ConvexityVault — with two small Node/TS services. Both are framework-free (node:http), offline-test-friendly, and intentionally thin.
@inflexion/engine — the Path-B relayer
@inflexion/api — the public REST facade
Where the chain stays the authority
Pushing matching or settlement off-chain would reintroduce trust — and would break the no-bad-debt guarantee. So the relayer is structurally incapable of doing either:- It cannot compute a dollar premium. The final premium is position-specific and derived on-chain at
createSwapRoutedas . The relayer returns the signed quote + itsloadBps, not a dollar figure — it would need the LP’s exact geometry and an RPC read ofFairPremiumto do otherwise. - It applies only a light, non-authoritative freshness gate. Its skew window (
maxValiditySkewS = 60s) is deliberately looser than the on-chain validity window of[5, 15] s. The chain re-checks everything; the relayer just drops obvious junk so the store stays clean. - It holds no wallet and never signs. It verifies an MM’s EIP-712 signature; it never produces one.
Why Path A needs no engine at all
The engine exists only so a sophisticated MM can compete on price against the pool. Path A — the cvAMM pool — is always-on and signature-free; a pooled vault underwrites with no relayer in the loop.createSwapRouted then hands the LP the cheaper of {pool, MM quote}. The engine is pure upside: if no MM is connected, Path A still clears every position, and an expired, over-band, or under-collateralized MM quote causes routing to fall back to the pool, never revert. See routing.
End-to-end: MM → engine → chain → API
This is the flow that produces the state each layer serves. It is the canonical sequence followed end to end.MM streams a firm quote to the engine (WebSocket)
{ type: 'quote', quote, signature } over WS. The relayer verifies the EIP-712 signature + the light freshness gate, stores it as the cheapest-per-market candidate, and logs it to COMPETITION_LOG (a winner if accepted, a loser if rejected — both are logged). The quote is per-market, never per-NFT: it carries a loadBps, a [minMaxILRatioBps, maxMaxILRatioBps] band, and a capacity ceiling, but no tokenId. This is load-bearing — MaxIL makes positions fungible to an underwriter within a market.LP previews via the SDK
LpClient.previewPremium reads the live pool-load surface (RPC, or the API’s /pool/load-surface) and the cheapest MM load from the engine’s GET /quote, then best-effort POSTs /telemetry/preview (logged to DEMAND_LOG, filled: false). The preview ping carries only bucketed geometry + marketId (no PII, no tokenId, no addresses).LP buys on-chain — the chain re-derives everything
createSwapRouted on InflexionCore. The chain re-derives FairPremium (the Φ-sum from the Stylus FairValueOracle), re-checks the quote’s [5, 15] s validity window, the price band (invariant I9 / Fork 2), and the bitmap nonce (I7), then routes to the cheaper of {cvAMM pool, MM quote}. An invalid quote falls back to Path A, never reverts. Only the executed rail mutates nonce, capacity, and the collateral lock.Settlement pays the LP from the chosen underwriter
settle() pays the LP trustlessly from the chosen underwriter’s collateral. The 2026-06-05 lifecycle demonstrated both rails: Path A (cvAMM, swap #1, premium $9.70 = 0.58% of MaxIL, payout $148.64) and Path B (MM beat the pool, swap #2, MM load 1000 bps, payout $245.66 from MM collateral).The API exposes the microstructure
SwapPriced / QuoteFilled events from the redeploy block, joining the structural moat halves to the dynamic halves already captured. See the five signals.What is intentionally thin — and why
Thinness is a design constraint, not an unfinished state. Each service is kept small so the chain stays the only authority and so both degrade gracefully under absence.The engine does not match or settle
The engine does not match or settle
(market, MM), serve the cheapest live quote over GET /quote, and capture the two day-one telemetry signals. Matching and settlement are the chain’s — pushing them off-chain would reintroduce trust and break the no-bad-debt guarantee. The relayer returns a load, not a dollar premium, because the premium is position-specific and computed on-chain.The API never re-derives pricing — it forwards the SDK
The API never re-derives pricing — it forwards the SDK
@inflexion/sdk DataClient.getCurrentLoadSurface and DepositorClient.getVaultState. The Φ-sum fair rate is computed in exactly one place (the Stylus FairValueOracle, surfaced through the SDK); the API only forwards it. This is a hard rule — duplicating the pricing math in the API would create a second source of truth.The API has no wallet, no signer, and is GET-only
The API has no wallet, no signer, and is GET-only
405 { available:false, reason:'method-not-allowed', detail:'read-only API; GET only' }. The whole stack is fully testable offline — mock subgraph, injected fetch, injected telemetry reader, pre-built SDK clients.Engine scope: no auth, no rate-limit, no persistence beyond JSONL
Engine scope: no auth, no rate-limit, no persistence beyond JSONL
marketId → mm → latest quote) with an optional append-only JSONL log; multi-MM works because the store keys per MM, but there is no auth, no rate-limit, and no persistence beyond the JSONL log. The definitive Stylus ≡ Solidity ≡ off-chain digest cross-check is an integration test added with the SDK. Production hardening (auth, rate-limit, durable store) is a bounded follow-up — the architecture does not change, only the operational envelope.Everything degrades to a typed value, never an exception
Both services treat absence as a first-class, typed state — not an error path. This is what lets a UI render a “pending” or “degraded” panel instead of catching an exception.- The API response envelope is a discriminated union that never throws:
PendingReasonis a closed enum:subgraph-not-deployed,subgraph-unreachable,rich-events-pre-redeploy,telemetry-sink-absent,rpc-unavailable. Each one names precisely what unblocks the surface.- Subgraph-backed routes embed the exact query so the surface is self-documenting; if a backing is ever absent, the pending body still names precisely what data the route serves. A consumer can read exactly what data appears.
- A last-resort 500 guard wraps every handler: a stray bug returns
500 { available:false, reason:'internal' }rather than crashing the process.
[SG]) surface serves real data now via the API.The subgraph is deployed on The Graph Studio (slug inflexion-arb-sepolia) and has indexed the on-chain moat events (SwapPriced / QuoteFilled, both live on-chain) from the redeploy block 274081134 — the subgraph startBlock, where the on-chain moat dataset begins. The hosted API composes it alongside the live RPC surfaces (current load surface, vault state, pricing preview) and the telemetry halves (DEMAND_LOG, COMPETITION_LOG). Each [SG] route runs its embedded GraphQL against the subgraph; if SUBGRAPH_URL were ever unset, the same route degrades to a typed { available:false, reason:'subgraph-not-deployed', query:… } body rather than erroring. See status & caveats and the moat state.Which layer serves which surface (live status)
A quick map of what is reachable today, by layer. Backing key: [RPC] live via the SDK, [SG] subgraph (deployed and indexing), [TEL] engine telemetry sink.- 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 claims, stated correctly
The access layer carries two separate, never-merged standing disclosures. The API attaches Claim A to every payout/swap/MM surface and Claim B to every NAV/yield/pool surface; they are never combined into one statement. LP no-bad-debt (Claim A) and depositor/MM capital risk (Claim B) are distinct claims.Where to go next
The engine
SignedQuote, the WS protocol, the freshness gate, and the day-one telemetry sinks.API overview
PendingReason enum, caching, and graceful degradation.API endpoints
The subgraph
SDK overview
The data moat
421614 · deploy block 274081134 (subgraph startBlock) · deployed 2026-06-05
Core protocol
Stylus & libraries
Oracles
Uniswap
Chainlink feeds
Demo tokens / pool / LP
Lifecycle swaps
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.