> ## Documentation Index
> Fetch the complete documentation index at: https://inflexion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Degradation envelopes

> The typed envelopes that make SDK reads never throw and writes fail clearly — Degraded<T>, Priceable<T>, ApiPending, NonceStatus, WriteResult — plus the live-today vs degradation cheat-sheet and the approvals/spenders one-glance table.

<Info>
  **Two contracts run through the whole SDK.** A **read** never throws — it returns a typed degraded envelope you must narrow before you can touch the data. A **write** never crashes at construction — without a signer it either throws a clear error *on call* or returns a deferred result carrying the unsigned tx. This page is the complete catalogue of those envelopes, the live-today vs degradation cheat-sheet, and the approvals/spenders table — the three things a UI engineer needs to render Inflexion safely, including when a backing (subgraph URL / RPC / telemetry) is absent.
</Info>

A degraded read is a **value, not a thrown error**. That single decision is what lets the app render a typed "oracle degraded" or "served from `<route>`" state instead of wrapping every call in a `try/catch`. The type system enforces it: every fallible read returns a discriminated union, and the compiler will not let you read the payload until you check the discriminant.

```ts theme={null}
// The compiler refuses .geometry until you check .priceable.
const g = await sdk.lp.resolveGeometry(tokenId, marketId)
if (!g.priceable) {
  console.warn('not priceable:', g.reason) // reason ∈ a closed union (see below)
} else {
  console.log(g.geometry.maxIL)            // safe — narrowed to the success branch
}
```

***

## The five envelopes

There are exactly five degradation shapes in the SDK. Three are read envelopes (`Degraded<T>`, `Priceable<T>`, `ApiPending`), one is a read **precision** flag (`NonceStatus`), and one is the write-result union (`WriteResult`). Everything fallible the SDK returns is one of these.

<CardGroup cols={2}>
  <Card title="Degraded<T>" icon="layer-group">
    Generic read envelope: success **or** a `{ available: false, reason }`. Vault state, position, regime, surface σ\_ref.
  </Card>

  <Card title="Priceable<T>" icon="tag">
    Anything that needs the live price $P_0$. Success **or** `NotPriceable { priceable: false, reason, detail? }`.
  </Card>

  <Card title="ApiPending" icon="hourglass-half">
    History/aggregate surfaces when no backing is configured. Names the route + the exact query. Served live today via the hosted API.
  </Card>

  <Card title="NonceStatus" icon="crosshairs">
    Not pass/fail — a **precision** flag: `'coarse' | 'precise'`. MM fill attribution is coarse on-chain.
  </Card>
</CardGroup>

<Card title="WriteResult" icon="pen-to-square">
  The DepositorClient write union: `{ tx, txHash?, status: 'sent' | 'deferred-no-wallet' | 'error', detail? }`. LP/MM writes throw a clear error instead of deferring.
</Card>

### Quick reference

| Envelope | Success branch | Failure / degraded branch | Used by |
| - | - | - | - |
| `Degraded<T>` | `{ available: true } & T` | `{ available: false, reason: string }` | vault state, position, regime, surface σ\_ref |
| `Priceable<T>` | `{ priceable: true } & T` | `NotPriceable = { priceable: false, reason: DegradeReason, detail? }` | anything needing $P_0$ (geometry, preview, payoff, status) |
| `ApiPending` | — (served live via the API; this is the no-backing fallback) | `{ available: false, reason: 'no-history-source', endpoint, query, detail }` | all DataClient history methods |
| `NonceStatus` | `{ spent, precision: 'coarse' \| 'precise', detail }` | — (always returns; precision degrades) | MM fill attribution (`isQuoteFilled`) |
| `WriteResult` | `{ status: 'sent', txHash, tx }` | `{ status: 'deferred-no-wallet' \| 'error', tx, detail? }` | DepositorClient writes |

***

## `Degraded<T>` — generic read degradation

```ts theme={null}
type Degraded<T> = ({ available: true } & T) | { available: false; reason: string }
```

The general-purpose envelope for reads that can fail for an environmental reason (no RPC, an uninitialised vol oracle) rather than a price-specific one. `DepositorClient.getVaultState`, `getPosition`, `getRegime`, and `DataClient.getSurfaceSigmaRef` all return `Degraded<T>`.

```ts theme={null}
const state = await sdk.depositor.getVaultState()
if (!state.available) {
  // reason ∈ { 'no-rpc', 'vol-uninitialized' }
  return renderDegraded(state.reason)
}
// narrowed — every VaultState field is now safe, including the claim-B flag:
console.log(state.utilWad, state.regime, state.capitalNotGuaranteed)
```

<Note>
  On `Degraded<VaultState>`, the success branch carries `capitalNotGuaranteed: true` — the SDK attaches [Claim B](/concepts/no-bad-debt) to every depositor state read. Render the [tranche risk disclosure](/concepts/tranches) wherever you surface vault state.
</Note>

***

## `Priceable<T>` — anything that needs the live price

```ts theme={null}
type Priceable<T> = ({ priceable: true } & T) | NotPriceable
type NotPriceable  = { priceable: false; reason: DegradeReason; detail?: string }
```

Distinct from `Degraded<T>` because the dominant failure is the live price $P_0$ being unavailable. Every read that derives $P_0$ catches an `OracleManager.getPrice` revert (stale feed, sequencer down) and returns `NotPriceable { reason: 'oracle-degraded' }` rather than throwing. This covers `lp.resolveGeometry`, `previewPremium`, `getPayoffCurve`, and the nested `ilToDate` inside `getProtectionStatus`.

```ts theme={null}
const preview = await sdk.lp.previewPremium(demo.lpPositionTokenId, demo.marketId_fee500_7d)
if (!preview.priceable) {
  // reason ∈ { 'oracle-degraded', 'market-unknown', 'position-unknown' }
  return renderNotPriceable(preview.reason, preview.detail)
}
console.log(preview.best, preview.path) // safe — e.g. 8_930_000n (=$8.93), 'B'
```

<Tip>
  Envelopes nest. `getProtectionStatus` returns `ProtectionStatus | { available: false, reason }`, and its `ilToDate` field is **itself** a `Priceable<{ il, payout, capHit }>`. So a swap can be *known* (`available: true`) while its mark-to-market IL is *not priceable* this block (oracle degraded) — and the UI shows the position with a "mark unavailable" badge instead of failing the whole card.
</Tip>

### `DegradeReason` — the closed union

Every `reason` on a `Priceable`/`Degraded`/`ApiPending` failure is drawn from one closed union, so the UI can exhaustively switch on it:

```ts theme={null}
type DegradeReason =
  | 'oracle-degraded'    // OracleManager.getPrice reverted (stale / sequencer down)
  | 'no-rpc'             // no read client could be built
  | 'rich-events-absent' // an indexed event the surface needs isn't on-chain yet
  | 'no-history-source'  // no subgraph/API backing configured (ApiPending uses this)
  | 'vol-uninitialized'  // VolOracle.sigmaRef reverts — σ_ref not seeded for the token
  | 'market-unknown'     // Core.markets returns token0 == address(0)
  | 'position-unknown'   // NPM.positions has no such tokenId
```

| Reason | Trigger | Typical fix / fallback |
| - | - | - |
| `oracle-degraded` | `OracleManager.getPrice` reverts | retry next block; the protocol's [settlement liveness fork](/security/forks) guarantees `settle` still succeeds |
| `no-rpc` | no read client (no `rpcUrl`/transport/`publicClient`, no env) | provide an RPC; see [getting started](/sdk/getting-started) |
| `rich-events-absent` | a needed indexed event isn't emitted on-chain yet | use the coarse on-chain path; precise path served live via the [subgraph](/integrate/subgraph) |
| `no-history-source` | no subgraph/API backing is configured (if `SUBGRAPH_URL` is unset) | render the `ApiPending` "served from `<route>`" state |
| `vol-uninitialized` | `VolOracle.sigmaRef` reverts (token never seeded) | `lp.poke(token)` / `mm` requote after the EWMA seeds |
| `market-unknown` | `Core.markets(marketId).token0 == 0` | check the [markets grid](/reference/markets-grid) for valid ids |
| `position-unknown` | `NPM.positions(tokenId)` has no entry | confirm the tokenId and owner |

***

## `ApiPending` — the SDK that never fabricates history

```ts theme={null}
type ApiPending = {
  available: false
  reason: 'no-history-source'
  endpoint: string  // the API route this calls
  query: unknown    // the exact query sent
  detail?: string
}
```

History/aggregate surfaces are **served live** from the subgraph via the [hosted API](https://inflexion-backend.onrender.com) (`/`), backed by the deployed subgraph (slug `inflexion-arb-sepolia`) which has indexed on-chain history since the redeploy block. `ApiPending` is the **graceful-degradation fallback**: if no backing is configured (e.g. `SUBGRAPH_URL` is unset), each DataClient **history** method returns this typed envelope rather than inventing a number — it names the **route and the exact query** that would be sent, so the UI can render "served from `<route>`" with **zero re-derivation** when a backing is present. The SDK never fabricates history.

```ts theme={null}
const hist = await sdk.data.getLoadSurfaceHistory({ marketId: demo.marketId_fee500_7d })
// Live (backing configured): { available: true, rows: [...] }
// Fallback (no backing, e.g. SUBGRAPH_URL unset): {
//   available: false,
//   reason:   'no-history-source',
//   endpoint: '/data/load-surface',
//   query:    { marketId: '0x67c4…', from: undefined, to: undefined, bucket: undefined },
// }
if (!hist.available) ui.showPending(`served from ${hist.endpoint}`)
```

| DataClient method | Endpoint | Backed by |
| - | - | - |
| `getLoadSurfaceHistory` | `/data/load-surface` | subgraph (live) |
| `getQuoteCompetition` | `/data/quote-competition` | subgraph + engine `COMPETITION_LOG` (live) |
| `getDemandRequests` | `/data/demand-requests` | engine `DEMAND_LOG` (off-chain only, latent demand never touches the chain — [invariant I7](/security/invariants)) |
| `getNavHistory` | `/pool/nav-history` | subgraph (live; carries Claim B) |
| `getNetGamma` | `/data/net-gamma` | off-chain compute |

<Note>
  **History/aggregate surfaces are live today — served from the subgraph via the [hosted API](https://inflexion-backend.onrender.com).**

  The deployed subgraph (slug `inflexion-arb-sepolia`) has indexed on-chain history since the redeploy block, so all five [data-moat history signals](/concepts/data-moat) resolve to real data through the API. `getCurrentLoadSurface` and `getSurfaceSigmaRef` hit live RPC directly. `getDemandRequests` is the one inherently off-chain signal: it is served from engine telemetry, not the chain, because latent demand never touches the chain by [invariant I7](/security/invariants). If no backing is configured (e.g. `SUBGRAPH_URL` is unset), the history methods degrade to a typed `ApiPending` envelope naming their route — they never throw.
</Note>

***

## `NonceStatus` — coarse vs precise fill attribution

```ts theme={null}
type NonceStatus = { spent: boolean; precision: 'coarse' | 'precise'; detail: string }
```

Not a pass/fail envelope — it **always** returns — but it carries a **precision** flag that degrades, and that distinction is load-bearing for MMs.

<Warning>
  **MM fill attribution is COARSE on-chain — and that is structural, not a bug.**

  * **Limit.** `mm.isQuoteFilled(mm, nonce)` returns `precision: 'coarse'`.
  * **Why.** The bitmap nonce `isNonceUsed(mm, nonce)` is set on **both** a fill and a cancel, and `SwapCreated` / `SwapRouted` carry no `quoteId`/`nonce` — so a spent bit cannot, on-chain alone, be told apart as filled-vs-cancelled.
  * **Mitigation.** The precise path is the `QuoteFilled` event (`swapId, mm, quoteId, nonce, loadBps`) emitted since the 2026-06-05 deploy; it is now indexed by the live subgraph and queryable as `precision: 'precise'`. The coarse on-chain path remains available too — `watchFills` polls `SwapCreated` logs filtered by `mm`, and `capacityRemaining` reads the exact on-chain `consumedNotional` ([invariant I7](/security/invariants)) for the number that actually matters — remaining capacity.
</Warning>

```ts theme={null}
const s = await sdk.mm.isQuoteFilled(MM_ADDRESS, nonce)
// { spent: true, precision: 'coarse', detail: 'nonce bit set (fill OR cancel)' }
if (s.precision === 'coarse') {
  // exact remaining capacity is still precise on-chain (I7):
  const cap = await sdk.mm.capacityRemaining(quoteId, maxNotionalV0)
}
```

***

## `WriteResult` — writes fail clearly, never silently

```ts theme={null}
type WriteResult = {
  tx: { to: Hex; data: Hex; value: bigint } // the (un)signed calldata
  txHash?: Hex                              // present only when status === 'sent'
  status: 'sent' | 'deferred-no-wallet' | 'error'
  detail?: string
}
```

The factory **always constructs**, even with no signer — `makeWalletClient` returns `undefined` rather than crashing. What happens when you then call a write is deliberately different per surface, because the right failure mode differs by intent.

<Tabs>
  <Tab title="DepositorClient — deferred result">
    `deposit`, `requestWithdrawal`, and `withdraw` return a `WriteResult`. Without a signer the status is `'deferred-no-wallet'` and `tx` carries the **unsigned calldata** for an external signer — the right shape for server-side and custody flows where the SDK builds calldata that something else signs. An on-chain revert (e.g. the `JuniorBelowLocked` gate on `withdraw`) surfaces as `status: 'error'`, never a thrown exception.
  </Tab>

  <Tab title="LpClient / MmClient — throw on call">
    `lp.buyProtection`, `lp.settle`, `lp.poke`, and `mm.cancelNonces` **throw a clear error on the call** when there is no wallet. These are direct, user-driven transactions — there is no sensible "deferred" semantics for "buy this protection now" — so a missing signer is an explicit hard error at the call site, never at construction.
  </Tab>
</Tabs>

```ts theme={null}
// No wallet → DepositorClient builds but does not send.
const sdk = createInflexionSdk({ publicClient }) // read-only
const res = await sdk.depositor.deposit('junior', 10_000_000_000n)
if (res.status === 'deferred-no-wallet') signElsewhere(res.tx)
if (res.status === 'error') console.warn('reverted:', res.detail)

// No wallet → LpClient throws on the write call (not at construction).
await sdk.lp.buyProtection({ tokenId, marketId, maxPremium }) // throws a clear error
```

<Tip>
  Most write methods have a `build*` sibling that returns raw `{ to, data, value }` calldata directly, for full external-signing control regardless of wallet state: `depositor.buildDeposit` / `buildApprove`, `mm.buildCancelNoncesTx`. See [guardrails](/sdk/guardrails) for the full write-safety model.
</Tip>

***

## Live-RPC vs subgraph-backed — the cheat-sheet

The SDK is fully usable against the [live deployment](/reference/deployments), and the subgraph is deployed and indexing too. The line is between live-RPC surfaces (data straight from the chain) and history/aggregate surfaces served from the subgraph via the [hosted API](https://inflexion-backend.onrender.com) — which degrade to a typed pending state only if no backing is configured.

<Tabs>
  <Tab title="Live-RPC (direct, no subgraph needed)">
    | Surface | Method(s) | Envelope on failure |
    | - | - | - |
    | Premium preview | `lp.previewPremium` | `Priceable` (`oracle-degraded`) |
    | Position geometry | `lp.resolveGeometry`, `mm.getPositionGeometry` | `Priceable` |
    | Payoff curve | `lp.getPayoffCurve` | `Priceable` |
    | Protection status | `lp.getProtectionStatus` | `{available:false}` + nested `Priceable` `ilToDate` |
    | Current load surface | `data.getCurrentLoadSurface` | per-row inlined `SurfaceRow` degrade |
    | σ\_ref / regime | `data.getSurfaceSigmaRef`, `depositor.getRegime` | `Degraded` (`vol-uninitialized`) |
    | Market pricing / pool load | `mm.getMarketPricing`, `getPoolLoadToBeat` | inline / `{available:false}` |
    | Vault state / position | `depositor.getVaultState`, `getPosition` | `Degraded` (`no-rpc`) |
    | Book (on-chain scan) | `mm.getBook` | always returns |
    | Fills (coarse) | `mm.watchFills`, `isQuoteFilled` | `NonceStatus { precision:'coarse' }` |
  </Tab>

  <Tab title="Subgraph-backed (live via API)">
    | Surface | Method(s) | Source (fallback if no backing) |
    | - | - | - |
    | Load-surface history | `data.getLoadSurfaceHistory` | `/data/load-surface` (else `ApiPending`) |
    | Quote competition | `data.getQuoteCompetition` | `/data/quote-competition` (else `ApiPending`) |
    | Demand requests | `data.getDemandRequests` | `/data/demand-requests` — engine telemetry, off-chain by design |
    | NAV history | `data.getNavHistory` | `/pool/nav-history` (Claim B; else `ApiPending`) |
    | Net gamma | `data.getNetGamma` | `/data/net-gamma` (else `ApiPending`) |
    | Precise fill attribution | `mm.isQuoteFilled` (precise) | `QuoteFilled` event — `precision:'precise'`, now indexed |
    | Full fee accounting | `lp.getClaimableFees` | precise figure served from the subgraph |
  </Tab>
</Tabs>

<Note>
  **The subgraph is deployed and indexing — history, NAV, precise fill attribution, and full fee accounting are live, served via the [hosted API](https://inflexion-backend.onrender.com).**

  The subgraph (slug `inflexion-arb-sepolia`) has indexed on-chain history since the redeploy block, so these surfaces resolve to real data with no consumer-side changes. Graceful degradation remains a **design property**: if a backing is absent (e.g. `SUBGRAPH_URL` is unset), each affected surface degrades to a typed envelope — `ApiPending` (history), `NonceStatus { precision: 'coarse' }` (fills), or an under-stating-but-honest read (`getClaimableFees`) — and the UI renders the pending state; nothing throws.
</Note>

<Note>
  **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.
</Note>

***

## Approvals & spenders — the one-glance table

Reads need no approvals. For writes, the spender differs per surface — and getting it right is the difference between a successful tx and a wasted approval to the wrong contract.

| Action | Spender / vault | Approval flow | Decimals |
| - | - | - | - |
| `lp.buyProtection` | **InflexionCore** | NPM `approve(core, tokenId)` **+** dUSDC `approve(core, maxPremium)` — auto when `approve !== false` | dUSDC 6-dec |
| `depositor.deposit` | **ConvexityVault** | dUSDC `approve(vault, amount)` — auto when `autoApprove: true` — then `deposit(tranche, amount)` | dUSDC 6-dec |
| MM collateral | **UnderwriterVault** | separate ERC-20 approve + deposit — **not** part of `signQuote` | dUSDC 6-dec |
| `lp.settle` · `depositor.requestWithdrawal` / `withdraw` · `mm.cancelNonces` | — | **no approval** | — |

<Warning>
  **The LP and depositor spenders are different contracts — do not cross them.** `buyProtection` approves dUSDC (and the position NFT) to **InflexionCore**; `deposit` approves dUSDC to the **ConvexityVault**; MM collateral goes to the **UnderwriterVault**. Approving the wrong contract leaves the real call under-allowanced and reverting. The SDK's `auto*` flags wire the correct spender for you — prefer them over hand-rolled approvals.
</Warning>

<Note>
  All collateral and premium flows are denominated in **dUSDC, 6 decimals** (the numéraire — `1 dUSDC = 1_000_000n`). `maxPremium`, `amount`, `V0`, `maxIL`, and every collateral figure are 6-dec raw bigints. See [decimals & units](/reference/decimals-units) and [addresses & units](/sdk/addresses-units).
</Note>

***

## The depositor claim, stated correctly

Because the live load surface, NAV history, and vault-state reads on this page all touch depositor capital, the SDK attaches Claim B to every depositor read. It is never merged with [Claim A (LP no-bad-debt)](/concepts/no-bad-debt).

<Warning>
  **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.
</Warning>

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Guardrails" icon="shield-halved" href="/sdk/guardrails">
    The write-safety model in full — `build*` calldata, deferred results, and the never-merge claim discipline.
  </Card>

  <Card title="Data client" icon="chart-line" href="/sdk/data-client">
    The live-RPC surface and the five behavioral signals of the data moat, served via the API.
  </Card>

  <Card title="Getting started" icon="rocket" href="/sdk/getting-started">
    Construction, the full config table, and the RPC/key resolution order that drives `no-rpc`.
  </Card>

  <Card title="Subgraph" icon="diagram-project" href="/integrate/subgraph">
    What the live subgraph indexes, and which envelopes resolve from coarse/pending to precise.
  </Card>

  <Card title="Invariants" icon="lock" href="/security/invariants">
    I7 (capacity authority) and the settlement-liveness fork behind `oracle-degraded`.
  </Card>

  <Card title="Deployments" icon="server" href="/reference/deployments">
    Live Arbitrum Sepolia addresses — never hardcode; import from the SDK.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.