Skip to main content
@inflexion/engine is the off-chain service that powers the optional Path-B (market-maker) rail. It is deliberately thin: a framework-free node:http process whose entire job is to relay firm, signed MM quotes to LPs and to capture the two off-chain halves of the data moat. It is pure upside — if no MM is ever connected, the cvAMM pool (Path A) still clears every position with no relayer in the loop.

Streams firm quotes

MMs push EIP-712 signed quotes over WebSocket; the relayer verifies, freshness-gates, and stores the latest per (market, MM).

Serves the price-to-beat

GET /quote returns the cheapest currently-valid quote (lowest loadBps) — a load, never a dollar premium.

Captures day-one signals

Latent demand (Signal 4) and quote competition (Signal 2) are logged to append-only JSONL — they are unreconstructable retroactively.

What it is — and what it deliberately is NOT

The engine surrounds the on-chain core; it is never in the trust path. The chain is the authority for matching, pricing, and settlement.
The relayer never settles, never matches custody, and never holds a wallet.
  • Limit: the engine returns a signed quote + its loadBps, not a dollar premium, and it has no signer, no key, and no settlement path.
  • Why: the final premium is position-specific and is derived on-chain at createSwapRouted as FairPremium · (1 + loadBps). The relayer cannot compute the dollar premium without the LP’s exact position geometry plus an RPC read of FairPremium. Pushing matching or settlement off-chain would reintroduce trust.
  • Mitigation (by construction): keeping match/settle on-chain is precisely what makes the no-bad-debt guarantee hold. The engine is a convenience + data surface only.
The three things it does do:
1

Accept and verify firm quotes

Take EIP-712 signed quotes streamed by an MM over WebSocket, verify the signature plus a light freshness gate, and store the latest per (market, MM).
2

Serve the cheapest live quote

Answer GET /quote?marketId=… with the lowest-loadBps quote that is not expired — the price-to-beat the LP sees before going on-chain.
3

Capture the two day-one moat signals

Append latent demand (Signal 4) and quote competition (Signal 2) to JSONL sinks that are otherwise impossible to reconstruct after the fact.
Why Path A needs no engine at all. Path A — the cvAMM pool — is always-on and signature-free; a pooled vault underwrites with no relayer involved. The engine exists only so a sophisticated MM can compete on price against the pool. createSwapRouted then gives the LP the cheaper of {pool, MM quote}, and falls back to the pool whenever no MM is connected.

Configuration — environment variables

The process reads its config from env and starts the relayer: VERIFYING_CONTRACT and CHAIN_ID default from the deployment registry, never hardcoded: the engine reads the on-chain address registry directly. The engine cannot import @inflexion/sdk — the SDK depends on the engine, which would be a cycle.
DEMAND_LOG and COMPETITION_LOG must be set from the very first deploy.
  • Limit: if either sink is unconfigured, that signal is silently not captured.
  • Why: these two signals are unreconstructable retroactively — unfilled or previewed interest never reaches the chain (invariant I7: an unchosen quote touches no nonce/capacity), so it leaves no on-chain trace by design. If it is not logged now, it is gone forever.
  • Mitigation: configure both sinks on day one, long before any /data endpoint, subgraph, or API exists to serve them. See the telemetry sinks.
A stale hardcoded core would sign quotes against a dead EIP-712 domain, and on-chain createSwapRouted would then reject every Path-B fill. Reading the live registry is what keeps the off-chain domain in lockstep with the deployed InflexionCore. The current default resolves through the deployment registry — never inline an address.
Startup logs the four surfaces; SIGINT/SIGTERM trigger a graceful close() that shuts the WS server, then the HTTP server.

The on-the-wire unit — SignedQuote

The SignedQuote module is the single source of truth for how a Path-B MM signs a quote and how anyone verifies it off-chain. The field order, types, and the domain ("Inflexion", "1") must match InflexionCore.SignedQuote / SIGNED_QUOTE_TYPEHASH exactly, or on-chain verification in createSwap/createSwapRouted rejects the signature. Both the SDK and the MM bot import this module. The EIP-712 domain is { name: 'Inflexion', version: '1', chainId, verifyingContract }. The 13 quote fields, in struct/field/signing order: The canonical type-string is mirrored in SIGNED_QUOTE_TYPE_STRING and is byte-identical to InflexionCore.SIGNED_QUOTE_TYPEHASH’s pre-image.
The quote is PER-MARKET, not per-NFT — and that is load-bearing. MaxIL is pure geometry, frozen at creation, and L-independent in the fair-rate sense, which makes positions fungible to an underwriter within a market. So an MM quote is a load + a MaxIL-ratio band + capacity — loadBps + [minMaxILRatioBps, maxMaxILRatioBps]
  • maxNotionalV0 — never tied to one position. The SignedQuote carries no tokenId.

Signing and verifying

The four helpers:
Hex
The EIP-712 digest _hashTypedDataV4(hashQuote(q)) that the contract computes.
{ quote, signature }
Produces the signed envelope.
Address
Recovers the EOA signer from a { quote, signature } envelope.
boolean
True iff the signature recovers to quote.mm, case-insensitively. This is the EOA path; EIP-1271 contract signers are verified on-chain only.
Wire codec. bigint is not JSON-serializable, so the WS protocol and the JSONL logs encode every uint128/uint64/uint256 field as a decimal string. encodeQuote(q) produces the QuoteWire (bigints → strings); decodeQuote(w) parses them back. The small uint16/uint8 fields stay JS number (always < 2^53, safe).

On-chain bounds the off-chain quote must respect

The relayer applies only a light freshness gate; the chain re-checks everything. The authoritative bounds (from InflexionCore) a quote must satisfy or createSwapRouted reverts / falls back to Path A:
VALIDITY_MIN_S = 5, VALIDITY_MAX_S = 15, with validUntil bounded on-chain to now + [5, 15] s. The MM bot uses TTL = 10s and re-issues every 5s, comfortably inside the window.Why so short: firm GTD quotes with a tiny TTL plus an oracle-anchored band are the no-last-look design — the MM cannot pick off stale prices, and a stale quote simply voids itself.
PRICE_BAND_MIN_BPS = 25 (0.25%), PRICE_BAND_MAX_BPS = 500 (5%). At createSwap the contract reverts iff ∣absBps(Plive,quotePrice)∣>priceBandBps\lvert\text{absBps}(P_{\text{live}}, \text{quotePrice})\rvert > \text{priceBandBps} (invariant I9 / Fork 2). The band check reuses the same oracle read that pinned P0, so it is non-circular.
During routing, an expired / over-band / under-collateralised quote causes _tryQuote-style logic to fall back to Path A, never revert. Only the executed rail mutates nonce/capacity/lock (I7).

The WebSocket protocol (MM → relayer)

The WS server is attached to the same HTTP server (new WebSocketServer({ server: http })). The MM streams messages; the relayer replies per message. Inbound shape:
Handling runs a strict ladder:
1

Parse

Non-JSON → { "type": "error", "error": "invalid json" }.
2

Shape check

Missing type:'quote' / quote / signature → { "type": "error", "error": "expected {type:'quote', quote, signature}" }.
3

Address check

quote.mm not a valid address → { "type": "error", "error": "bad mm address" }.
4

Freshness gate

freshnessOk rejects if validUntil ≤ now (already expired) or validUntil > now + maxValiditySkewS (dated too far out; default maxValiditySkewS = 60s). On failure: logs the quote as a loser and replies { "type": "rejected", "reason": "stale-or-far-validUntil" }.
5

Signature verify

verifyQuote (EIP-712 recover). On failure: logs as loser, replies { "type": "rejected", "reason": "bad-signature" }.
6

Accept

Logs as winner/candidate, stores the latest per (market, MM), replies { "type": "ack", "marketId": "0x...", "loadBps": 500 }.
Reply taxonomy: ack (accepted), rejected (freshness or signature failure, with a reason), error (malformed message). Logging is best-effort and never blocks the ack.
The freshness gate is intentionally non-authoritative — “the chain re-checks everything.” The relayer’s maxValiditySkewS = 60s is looser than the on-chain [5, 15]s window; that is deliberate. The relayer’s job is only to drop obvious junk and keep the store clean, while the chain is the real gate (see on-chain bounds).

HTTP endpoints (relayer)

CORS is open on every route (Access-Control-Allow-Origin: *, Allow-Methods: GET, POST, OPTIONS, Allow-Headers: content-type); preflight OPTIONS returns 204. Why CORS-open: the browser dApp is a different origin and must both read GET /quote (the SDK’s previewPremium) and publish POST /quote (browser-side MM signing).

GET /health

Sink + market status:
markets = number of markets currently holding ≥1 quote (store.marketCount()); telemetry.* reflects whether each sink path is configured.

GET /quote?marketId=0x… — the cheapest live quote

The taker-side read:
  • marketId must be a bytes32 hex (/^0x[0-9a-fA-F]{64}$/); else 400 { "error": "marketId (bytes32 hex) required" }.
  • Logs the request as latent demand before answering (Signal 4, best-effort, never blocks).
  • Looks up store.best(marketId, now) = the cheapest currently-valid quote (lowest loadBps, not expired).
  • No live quote → 404 { "error": "no live quote for market", "marketId": "0x…" }.
  • Hit → 200:
The note is the canonical reminder that the relayer returns a load, not a dollar premium.

POST /quote — browser-friendly publish

Same verify + freshness + store as the WS path, for browser MMs that cannot hold a persistent socket. Body is read up to 8192 bytes:
Outcomes:
The HTTP path uses 422 for verify/freshness rejections versus the WS path’s { type:'rejected' } message; both record the same telemetry reason.

POST /telemetry/preview — the Signal-4 demand ping

The SDK’s previewPremium best-effort POST. Body is read up to 4096 bytes and carries only bucketed geometry + marketId — no PII:
  • valid marketId → 202 { "ok": true, "logged": true } — logged:false when no sink is configured.
  • missing/invalid marketId → 400 { "ok": false, "reason": "marketId (bytes32 hex) required" }.
It always 202-acks fast and never blocks the preview — a telemetry failure is reported as logged:false, never an exception. Unmatched routes → 404 { "error": "not found" }.

The day-one telemetry sinks

The core WHY — the data moat. On-chain we only ever observe realized fills. Everything an LP priced but did not buy (Signal 4 — latent demand) and every MM quote that lost or was withdrawn (Signal 2 — the dynamic quote-competition half) leaves no on-chain trace by design — invariant I7: an unchosen quote touches no nonce/capacity. If it is not logged now, it is gone forever. These two signals must be captured from the very first interaction, long before any /data endpoint, subgraph, or API exists to serve them.
Telemetry writes are best-effort and never throw.
  • Limit: a full disk, a bad path, or a missing sink means a write can silently fail.
  • Why: a telemetry write must never break a quote ack, a preview, or a GET /quote request — graceful degradation is a first-class property.
  • Mitigation: the writer (appendJsonl) is wrapped so it never throws; on failure the surface reports logged:false rather than erroring, and the request completes normally. Storage is append-only JSONL (one JSON object per line), mirroring the accepted-quote log in the quote store.
Privacy-preserving geometry buckets — the SDK computes these client-side and POSTs only the labels; the logs hold no raw geometry, no tokenId, no addresses: Unknown/invalid input coerces to 'unknown' (asWidthBucket etc.).

Demand log (Signal 4) — DEMAND_LOG → DemandRecord

Written on every GET /quote request and every POST /telemetry/preview ping. filled is always false at write time — this log is exactly the demand the chain never sees. Realized fills are reconstructed later from on-chain SwapCreated/SwapPriced.
  • logQuoteRequest(marketId, now) — emitted by GET /quote; buckets are 'unknown' (a bare lookup carries no geometry), source: 'quote-request'.
  • ingestPreview(input, now) — emitted by POST /telemetry/preview; validates and normalizes the loosely-typed body, source: 'preview'. normalizeMarketId requires bytes32 hex; normalizePremium coerces a string/number/bigint into a bigint-safe decimal string or drops it. Returns { ok, logged, reason? }.

Competition log (Signal 2) — COMPETITION_LOG → CompetitionRecord

Written for every inbound WS quote — winners AND losers (logQuote), called from both the WS handler and POST /quote.
Why log losers too. Only ever logging the latest stored quote (as the quote store does) loses the competition — who else quoted, how wide, who widened or withdrew under stress. The chain only sees the realized fill, so the full competing field is captured only here. accepted marks whether the relayer stored it as a candidate (signature + freshness valid); a rejected quote still competed and is still logged.
get demandEnabled / get competitionEnabled report whether each sink path is configured, and are surfaced in /health.
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.

The quote store

An in-memory map marketId(lc) → mm(lc) → { env, receivedAt } — the latest quote per (market, MM), with an optional append-only JSONL log (QUOTE_LOG):
  • put(env, now) — store/replace the latest for its (market, MM); if logPath is set, append { t, quote, signature } as JSONL.
  • best(marketId, nowSec) — the cheapest currently-valid quote = lowest loadBps among quotes whose validUntil > now. This is the price-to-beat the LP sees; the on-chain router still re-derives everything.
  • marketCount() — number of markets with ≥1 quote.
Engine scope — what is intentionally not built yet.
  • Limit: no auth, no rate-limit, and no persistence beyond the JSONL log; the definitive Stylus≡Solidity≡off-chain digest cross-check is a dedicated integration test.
  • Why: the engine is a thin convenience surface, not the trust path — the chain re-verifies the signature, the validity window, and the price band on every fill, so abuse of the relayer cannot produce an invalid on-chain swap.
  • Mitigation: the store keys per MM, so multi-MM already works; the JSONL log gives durable telemetry; and the digest cross-check is scheduled as a dedicated integration test rather than left implicit.

The example MM bot

The example MM bot is a reference bot that signs a fresh SignedQuote on a loop and streams it over WS, demonstrating the firm-quote / short-TTL model: each quote is GTD (validUntil = now + ttl) and simply re-issued; the MM “cancels” by not refreshing — and the on-chain oracle band voids any stale quote.
  • TTL_S = 10, REISSUE_MS = 5000 — re-issue every 5s, each valid 10s, always inside the on-chain [5, 15]s window.
  • Env: MM_PRIVATE_KEY (required), MARKET_ID (required), ENGINE_WS (default ws://localhost:8787), CHAIN_ID (421614), VERIFYING_CONTRACT, QUOTE_PRICE, LOAD_BPS (default 500 = 5%).
  • Default quote: minMaxILRatioBps=0, maxMaxILRatioBps=10000 (accept any MaxIL ratio), priceBandBps=100 (1%, inside [25,500]), model=FULL, maxNotionalV0 = 2^128−1 (unbounded capacity), monotonically incrementing nonce.
The bot is the canonical reference for how to wire an MM against the relayer. For the SDK client that wraps signing + streaming for you, see the MM client and quote signing.

Worked examples — curl + wscat

How the engine fits the end-to-end flow

The relayer is step 1 (and part of step 2) of the create→settle lifecycle:
1

MM streams a firm quote

The MM pushes {type:'quote', quote, signature} over WS. The relayer verifies EIP-712 + freshness, stores it as the cheapest-per-market candidate, and logs it to COMPETITION_LOG as a winner.
2

LP previews

The SDK’s previewPremium reads the live pool-load surface (via RPC or the API /pool/load-surface) and GET /quote (the cheapest MM load), then best-effort POSTs /telemetry/preview (logged to DEMAND_LOG).
3

LP buys on-chain

The LP calls createSwapRouted on InflexionCore. The chain re-derives FairPremium (the Φ-sum from the Stylus FairValueOracle), re-checks the validity window [5,15]s, the price band (I9), the nonce (I7), and routes to the cheaper of {cvAMM pool, MM quote}; an invalid quote falls back to Path A, never reverts.
4

Settlement

The protocol pays the LP min(realized_IL, MaxIL) trustlessly from the chosen underwriter’s collateral. The relayer is never in this path.
The 2026-06-05 lifecycle ran 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 two separate claims

The engine relays quotes and captures demand; the on-chain core enforces the guarantees. Routing through the engine changes who underwrites and at what price, never whether the LP is paid:
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.
And the capital-at-risk disclosure applies to whoever ends up on the other side — the pool’s depositors on Path A, or the market maker on Path B:
Depositor capital is NOT guaranteed: the junior tranche is first-loss; the senior tranche is structurally protected from underwriting loss only, not systemic tail.Underwriting capital — whether supplied to the pooled cvAMM vault (Path A) or posted directly by a market maker (Path B) — is at risk:
  • Junior tranche — first-loss. Junior depositors absorb underwriting losses first and buffer the senior tranche.
  • Senior tranche — protected from underwriting loss, exposed to the systemic tail. The senior tranche is structurally shielded from ordinary underwriting loss while junior capital buffers, but it still takes the systemic tail once the junior buffer is exhausted.
No-bad-debt is a guarantee to the LP being paid (see the FULL-mode payout clause), not a guarantee that depositor or MM capital is preserved.
Network: Arbitrum Sepolia · chainId 421614 · deploy block 274081134 (subgraph startBlock) · deployed 2026-06-05

Core protocol

Stylus & libraries

Oracles

Uniswap

Demo tokens / pool / LP

Lifecycle swaps

Live create→settle lifecycle (2026-06-05, short 300s fee-500 market 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.

Where to go next

Path B — the MM rail

Firm EIP-712 quotes, no last-look, and the three on-chain protections.

createSwapRouted

How the chain prices both rails off one FairPremium and never reverts on a bad quote.

Quote signing

The EIP-712 type-string, digest, and the Stylus≡Solidity≡off-chain parity check.

The MM client (SDK)

The typed client that wraps quote signing + WS streaming for you.

The public REST API

The read-only facade that reads the engine’s telemetry sinks for the moat surfaces.

The data moat — five signals

Where latent demand (4) and quote competition (2) land in the full taxonomy.