Skip to main content
This is the type reference for MmClient — the SDK surface for the Path-B market-maker rail. Every method is listed with its exact TypeScript signature, the contract function(s) it reads or writes, and its degradation envelope. For the narrative — why an MM quote is per-market, the firm-quote / short-TTL economics, the end-to-end loop — see the MmClient guide. For the canonical wire format, see quote signing and the quote-helpers reference.
The whole MM workflow is sign + read + cancel. MmClient never sends a fill (an LP does that on-chain via createSwapRouted) and never holds collateral (a separate UnderwriterVault flow does). The one write it owns is cancelNonces.

Construction

MmClient is wired by the one-call factory; you never instantiate it directly. It is the only surface that receives the EIP-712 chainId. Defaults: coreAddress = core.inflexionCore, npmAddress = npm, chainId = 421614. It caches loadParams and per-market configs — both immutable after market freeze. MmClientOptions:
PublicClient
required
viem read client. Required for any on-chain read.
WalletClient (optional)
Write client for cancelNonces. Absent ⇒ cancelNonces throws MmQuoteError('no-signer').
Address (optional)
Override the InflexionCore address (defaults to the registry).
Address (optional)
Override the Uniswap NPM address (defaults to the registry).
number (optional)
EIP-712 domain chainId for quote signing. Defaults to 421614.

Method reference — at a glance

R = read, W = write (tx), sign = signs but sends no tx, build = returns unsent calldata, poll = returns a StreamHandle. Exported standalone utils:

Cached config reads

getLoadParams

Decodes InflexionCore.loadParams() and caches the result (immutable after freeze — read once, reused thereafter). Used by every guard and every load computation. LoadParams — all *Bps are integer bigint, all *Wad are 1e18-scaled:
Throws on no-RPC (until the first successful read caches it).
  • Limit: the first call needs a live read client; without one it rejects.
  • Why: the load params are the protocol’s authoritative price-cap config — the SDK reads them on-chain rather than embedding a stale copy.
  • Mitigation: construct the SDK with a working RPC; thereafter the value is cached and every dependent guard (I10, the load stack) is offline-fast.

getMarketConfig

Decodes InflexionCore.markets(marketId); returns undefined when the market is unregistered (token0 == address(0)). Cached per marketId (immutable). MarketConfig:

Pricing

getMarketPricing

The full pricing row — fair value + the decomposed pool load to beat — in one multicall: FairValueOracle.fairPremium + ConvexityVault.inventory + VolOracle.sigmaRef, finished with the parity-locked TS port of the load stack. The Φ-sum fairPremium is read on-chain, never reimplemented; only the deterministic load stack is mirrored off-chain (the deployed CvammPricing lib is delegatecall-only). Returns a typed degraded result on any revert — never throws. PricingGeometry input:
bigint
required
a=(Pa/P0)2a = (\sqrt{P_a}/\sqrt{P_0})^2 in WAD — lower range bound as a squared price ratio.
bigint
required
b=(Pb/P0)2b = (\sqrt{P_b}/\sqrt{P_0})^2 in WAD — upper range bound as a squared price ratio.
bigint
required
Protection tenor; selects the duration the FairValueOracle prices.
bigint
required
Position MaxIL in numéraire raw (dUSDC, 6 decimals) — scales the dollar premium.
Address (optional)
σ_ref token; defaults to the resolved market.oracleToken.
MarketPricingResult is a discriminated union:
MmMarketPricing:
The pool premium is exactly the on-chain _pricePathA expression: poolPremium  =  min⁡ ⁣( ⌈fairPremium⋅(1+totalLoad)⌉,  MaxIL)\text{poolPremium} \;=\; \min\!\Bigl(\, \bigl\lceil \text{fairPremium}\cdot(1 + \text{totalLoad}) \bigr\rceil,\; \text{MaxIL} \Bigr) LoadBreakdown and Regime:
PricingDegradeReason: 'no-rpc' | 'market-unknown' | 'vol-uninitialized' | 'fair-premium-revert' | 'inventory-revert'.

getPoolLoadToBeat

The first-class streamable signal: the geometry-free pool floor an MM must undercut. One multicall of ConvexityVault.inventory() + VolOracle.sigmaRef, finished by the TS-port load stack — no position geometry needed, because the load (a percentage spread over FairPremium) is geometry-independent. This is the mechanical face of MaxIL fungibility: appetite, not position, is what an MM expresses. totalLoad  =  baseLoad⏟σref regime band  +  utilSkew⏟util, convex above the knee  +  dispSkew⏟HHI dispersion(clamped to maxLoadBps=I10)\text{totalLoad} \;=\; \underbrace{\text{baseLoad}}_{\sigma_{\text{ref}}\text{ regime band}} \;+\; \underbrace{\text{utilSkew}}_{\text{util, convex above the knee}} \;+\; \underbrace{\text{dispSkew}}_{\text{HHI dispersion}} \quad\text{(clamped to } \text{maxLoadBps} = \text{I10}) PoolLoadTick — the optional fields are present only when available: true:

streamPoolLoadToBeat

Polls each market’s getPoolLoadToBeat on a loop (4 s default). The callback fires once per market per tick with a single PoolLoadTick argument (the tick carries its own marketId) — available or typed-degraded. Never throws inside the loop.
StreamHandle: { stop: () => void } — every streaming method returns this.

Vol oracle reads

getSigmaRef

VolOracle.sigmaRef(token) (WAD). Degrades to undefined if the token is uninitialized (the getter reverts).

getSigmaComponents

VolOracle.sigmaComponents(token) — the EWMA inputs and which one binds. Degrades to undefined on revert. SigmaComponents:
binding (which σ source binds) is distinct from the load regime (calm/normal/stressed). regime is derived from σ_ref vs the loadParams regime bands, not from sigmaComponents.binding. See σ_ref.

Position geometry (for $ display)

getPositionGeometry

Resolves the exact geometry of a v3 position: reads NPM.positions for ticks + liquidity, derives marketId from (token0, token1, fee, durationSeconds), pins P0 from InflexionCore.oracleDerivedSqrtPriceX96, and computes a/b + entry amounts + MaxIL on-chain via ILMath. durationSeconds is required — a position alone has no expiry, so the duration is what lines up the marketId and the FVO duration. Returns a typed degrade on an oracle revert or unrecognized position; never throws. PositionGeometry:
Geometry is for display, not for signing. You sign a per-market quote — the SignedQuote carries no tokenId. Geometry only lets you show a $ pool premium next to your $ quote (feed it to getMarketPricing). That is MaxIL fungibility in code: appetite is per-market, position is per-NFT.

The book and greeks

getBook

Every ACTIVE swap underwritten by mm plus aggregate exposure. The live deploy has no on-chain quoteId/nonce → swap index and MmClient takes no subgraph dependency, so the swap-id set is discovered by scanning swaps(id) over [fromSwapId, nextSwapId) (bounded; narrow with fromSwapId, or inject a known swapIds set). Each swap’s path is inferred via mm == convexityVault (Path A) else Path B. With withGreeks: true, per-position δ/γ are finite-differenced off the on-chain ILMath. A direct on-chain scan — independent of the subgraph. BookResult:
BookPosition:

greeksForSwap

Single-swap δ/γ via finite-differencing the deployed ILMath.computeIL over ±1% of the live oracle P0, each leg capped at MaxIL (the MM is short the capped claim). Returns undefined if the swap cannot be priced (oracle degraded / not active). This is the MM-book fast path: vega and theta are left at 0 — full vol-greeks live in the GreeksEngine, which finite-differences FairValueOracle.fairRate. Greeks:

Quote signing

signQuote

Signs an EIP-712 SignedQuote and enforces two guards before returning the envelope. The actual EIP-712 signing delegates to the engine signer (@inflexion/engine/quote.signQuote, never reimplemented) — it builds a viem account from the raw key, so this path is server-side only. For the browser, sign with the connected wallet and replicate these guards yourself (see the guide and quote signing).
1

I10 — the price cap (hard)

loadBps ≤ loadParams.maxLoadBps, else throws MmQuoteError('i10-exceeded') (invariant I10). The MM can never out-load the protocol cap.
2

Below-pool — strict beat (soft unless required)

loadBps < live pool-load-bps (read via getPoolLoadToBeat), else MmQuoteError('not-below-pool'). If the pool load is unreadable the check is skipped and reported as belowPoolChecked: false — unless requirePoolCheck: true, which makes it throw MmQuoteError('pool-load-unreadable').
The SignedQuote input — the 13 fields in struct/signing order; quotePrice, maxNotionalV0, validUntil, nonce are bigint, the *Bps and model are plain number:
opts:
boolean (default false)
Make an unreadable pool load a hard error (pool-load-unreadable) instead of skipping the below-pool guard.
Address (optional)
Override the EIP-712 verifyingContract (defaults to coreAddress).
number (optional)
Override the EIP-712 chainId (defaults to the client’s chainId).
SignQuoteResult:
QuoteEnvelope: { quote: SignedQuote; signature: Hex }.
CollateralModel.FULL = 0 is the only supported mode in v1. PARTIAL = 1 exists in the enum but the contract reverts on it.
  • Limit: you cannot sign model: 1 (PARTIAL).
  • Why: PARTIAL collateral parameters (first-loss size, floor curve, liquidation buffer) are gated on the Monte-Carlo calibrated risk-parameter set and are never hardcoded — the exact failure the audit flagged.
  • Path forward: always sign model: 0, partialRatioBps: 0. PARTIAL is roadmap, gated on the quant.

quoteStream

The auto-requote loop: each tick reads getPoolLoadToBeat, calls build(poolLoad) (return undefined to skip a tick), signs via signQuote (re-enforcing I10 + below-pool), then hands the envelope to your injected publish transport. MmClient deliberately does not depend on ws — you own the socket. Degrades silently per tick (a tick whose pool load is unreadable, or whose build/sign throws, is skipped and reported via onError). QuoteStreamOptions:

Fills, capacity, and cancel

isQuoteFilled

Coarse fill status from InflexionCore.isNonceUsed(mm, nonce). Degrades to spent: false with a detail on a read failure — never throws. NonceStatus:
Fill attribution is coarse on-chain — isQuoteFilled cannot tell filled from cancelled.
  • Limit: isNonceUsed is set on both a fill and a cancel, and SwapCreated/SwapRouted carry no quoteId/nonce, so the only honest direct read is “nonce spent: filled-or-cancelled” (precision: 'coarse').
  • Why: the on-chain settlement core stays minimal; precise attribution belongs in the indexer, not the hot path.
  • Path forward: the precise path is the now-live QuoteFilled event (swapId, mm, quoteId, nonce, loadBps), emitted since the 2026-06-05 deploy. It is queryable now — the subgraph indexes it and serves it live via the hosted API. If SUBGRAPH_URL were unset the history surface would degrade to a typed pending state, never an error.

watchFills

Polls SwapCreated logs filtered by the indexed mm topic and fires onFill per new matching log. The event carries mm but not quoteId/nonce, so the match to a specific quote is coarse (attribution: 'coarse'). Works now; the precise path is the QuoteFilled event, indexed by the subgraph and served live via the hosted API. Degrades silently on a failed poll (reports via onError). FillLog:
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.

capacityRemaining

The I7 capacity ledger. consumedNotional[quoteId] is the on-chain authority, incremented atomically in the effects phase before any external call, so concurrent fills cannot over-consume a quote. Degrades to { available: false } on a read failure — never throws. remaining  =  max⁡ ⁣(0,  maxNotionalV0−consumedNotional[quoteId])\text{remaining} \;=\; \max\!\bigl(0,\; \text{maxNotionalV0} - \text{consumedNotional}[\text{quoteId}]\bigr)

cancelNonces

The one write MmClient owns. Sends InflexionCore.cancelNonces from the wallet client, flipping exactly the bitmap bits you pass (never cancel-all), so you revoke precisely the quotes you want. Returns the tx hash. Throws MmQuoteError('no-signer') if no wallet (or no account) is wired. A cancelled bit can never fill — it satisfies the second clause of I7.

buildCancelNoncesTx

The unsigned cancelNonces calldata, for an external signer or a tx preview — no wallet required.

MM collateral — the UnderwriterVault flow

getMmCollateral

Reads the MM’s per-MM UnderwriterVault balances in one multicall (deposited / locked / availableBalance). All figures are dUSDC 6-dec raw. Degrades to undefined on a failed read. Collateral is not part of signQuote. Before any of an MM’s quotes can be filled, the MM posts collateral via a separate ERC-20 approve + deposit to the UnderwriterVault — MmClient only reads the state, it never writes the deposit. On a Path-B fill the contract requires availableBalance(mm) ≥ maxIL and locks maxIL of the MM’s own collateral; at settlement the LP is paid min⁡(realized_IL,MaxIL)\min(\text{realized\_IL}, \text{MaxIL}) from that locked collateral — never protocol funds. The standing invariant is I5: locked[mm] ≤ deposited[mm] by construction.

Errors

MmQuoteError — the typed error every guard and write throws:

Degradation envelope cheat-sheet

Reads degrade; writes throw. Every MM read returns a typed envelope (available/priceable/undefined) and never throws on a missing oracle or RPC. The two non-read paths — signQuote (a guard failure) and cancelNonces (a missing signer) — throw MmQuoteError with a .code, because a violated guard or absent wallet is a hard programmer error, not a degraded data state. See degradation.

The two separate claims

The MM rail changes neither guarantee. The settlement core enforces the LP-facing guarantee identically for both rails:
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 the underwriter — on Path B, the market maker posting collateral in its own UnderwriterVault:
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

MmClient guide

The narrative: per-market quoting economics, the end-to-end loop, browser signing.

Quote signing reference

The canonical EIP-712 domain, type-string, and verify/recover helpers.

Quote helpers

encodeQuote / decodeQuote / quoteDigest / verifyQuote signatures.

Greeks + hedge types

Full vol-greeks (vega/theta) and the three-leg hedge suggestion types.

Path B in the protocol

The on-chain createSwap gate, capacity (I7), the no-last-look protections.

The engine

The Path-B relayer: WS protocol, GET/POST /quote, the freshness gate.