LpClient is the surface an LP uses to buy in-range impermanent-loss protection: enumerate eligible Uniswap v3 positions, price one against a market, read its payoff-with-cap curve, sign the create transaction, watch the protection mature, and settle it at expiry. It is the only surface that carries claim A — the LP is always paid in FULL mode, qualified — exported verbatim as LP_CLAIM_A and stamped onto ProtectionStatus.noBadDebtFull.
It hangs off the factory as sdk.lp:
Two design properties run through every method on this client — repeat them.
- Reads are graceful. Every read that needs the live price catches an
OracleManager.getPricerevert and returns a typedNotPriceable { priceable:false, reason:'oracle-degraded' }envelope — it never throws. See degradation. - Writes degrade, never crash at construction. With no
walletClientthe client constructs fine; the write call (buyProtection,settle,poke,autoProtect) throws a single clear error — the one place a missing signer is a hard error.
The framing this surface must keep exact. This is an in-range convexity hedge, not “IL insurance.” Entry requires ; out-of-range positions are rejected at creation by the
inRange gate. The payout is and the cap is load-bearing for the no-bad-debt guarantee. Never state no-bad-debt unqualified — use the disclosure at the foot of this page.Method map
previewPremium — the load-bearing on-chain read [DEEP]
previewPremium(tokenId, marketId, opts?) answers the only question that matters before signing: what dollar premium will I be charged on this position if I sign in the next block? It is a fresh on-chain FairValue read plus the position’s geometry — never an off-chain price guess.
What it returns
PreviewResult:
Hex
The market the position was priced against.
bigint
The cap and the unit of risk — numéraire raw, 6-dec. Pure geometry, frozen at the moment of pricing.
The on-chain -sum fair value, before any load. Read, never reimplemented.
bigint
The dimensionless fair rate (WAD).
bigint
The EWMA the regime band keys off (WAD).
The Path-A pool premium —
fairPremium plus the full load stack, capped at MaxIL.The best live MM quote premium. Present only when an MM quote beat the pool.
bigint
The cheaper of
premiumA / premiumB — what you would actually pay.'A' | 'B'
Which rail produced
best.The five-step mechanic
This is the pricing stack executed client-side around the two on-chain reads. The Φ-sum is read on-chain; only the deterministic load is finished off-chain via the parity-lockedcvamm TS port — the single permitted duplication, because the deployed CvammPricing library is delegatecall-only and reverts a direct eth_call.
1
Read FairValue on-chain (Path A floor)
FairValueOracle.fairPremium(token, aWad, bWad, durationSeconds, maxIL) returns (premium, fairRateWad, sigmaRefWad). This is the closed-form Φ-sum — exact, on-chain, never reimplemented off-chain.2
Read pool inventory + load params
ConvexityVault.inventory() → (total, locked, free, util, conc) and Core.loadParams(). util and conc are the pool-state inputs to the load skew.3
Finish the load stack client-side (parity-locked)
where
totalLoad = cvamm.totalLoadWad(sigmaRef, util, conc, params), then capped at MaxIL. This is exactly _pricePathAFromFair on-chain. See the load stack.4
Fetch the best MM quote (Path B, best-effort)
GET ${engineBaseUrl}/quote?marketId=… for the cheapest live signed MM quote, then capped at the same MaxIL. An absent or unreachable engine simply leaves premiumB undefined and routes Path A — never an error.5
Pick the cheaper rail
best / path = the cheaper of premiumA / premiumB. A fire-and-forget telemetry ping (latent-demand Signal 4) is sent — never awaited into the result, never throwing.resolveGeometry & listEligiblePositions
Before pricing, the SDK must turn a Uniswap NFTtokenId into the exact geometry the math consumes.
resolveGeometry
resolveGeometry(tokenId, marketId) reads NPM.positions(tokenId), the market config and oracleDerivedSqrtPriceX96 from Core, walks TickMath for the range bounds, reads from OracleManager, and computes the entry amounts and MaxIL via ILMath.computeMaxIL / getAmountsForLiquidity. It returns Priceable<{ geometry: LpGeometry }>.
LpGeometry:
inRange flag is exact geometry — sqrtP0 >= sqrtPa && sqrtP0 <= sqrtPb, the same gate createSwap enforces on-chain. The aWad/bWad conversion is cvamm.abFromSqrt(sqrtP0, sqrtPa, sqrtPb) where , . These dimensionless ratios are what the Φ-sum and the MaxIL derivation consume — geometry, not dollar amounts.
Degradation routes: market-unknown (no such market on Core), position-unknown (no such NFT), oracle-degraded (no live ).
listEligiblePositions
listEligiblePositions(owner, opts?{durations?}) enumerates a wallet’s positions and prices each across one or more durations:
NPM.balanceOf / tokenOfOwnerByIndex / positions, then resolveGeometry per (position × duration). Degradation is graceful: an empty array if there is no NPM, and a per-row inRange:undefined if the oracle is degraded for that row — never a thrown call.
getPayoffCurve — payoff with the cap drawn in
getPayoffCurve(tokenId, marketId, opts?{points?}) returns the LP’s settlement payoff across a grid of terminal prices, with the MaxIL cap applied — this is the chart that makes the convexity hedge legible.
ILMath.computeIL over a sqrt-price grid spanning the range and applies the cap at each node. The payoff is exactly:
The non-negativity branch is invariant I3 — never an unchecked subtraction. Where capHit is true, the curve is flat at MaxIL: that flat ceiling is the no-bad-debt construction made visible — the underwriter posted exactly MaxIL, and the payout cannot exceed it. See the IL formula and the worked example.
buyProtection — approvals + escape hatches [DEEP]
buyProtection(params) is the one write that mints a protection. It sends approvals (by default), selects the create call, and returns the transaction hash.
Parameters
bigint
required
The Uniswap v3 position NFT to protect.
Hex
required
The market to protect it in.
The LP’s slippage guard — numéraire raw, 6-dec. The on-chain create reverts if the routed premium would exceed this.
'A' | 'B' | undefined
Force a rail. Omit (default) for best-of-both routing.
SignedQuote
Required when
escapeHatch:'B'. The per-market signed MM quote.Hex
Required when
escapeHatch:'B'. The EIP-712 signature over quote.boolean
default:"true"
When not explicitly
false, send the NPM + dUSDC approvals before the create call.Approvals — the spender is InflexionCore
Whenapprove is not explicitly false, the SDK sends two approvals first:
- NPM
approve(core.inflexionCore, tokenId)— hands the position NFT to the core. - dUSDC — reads
allowance(account, core.inflexionCore); if it is< maxPremium, sendsapprove(core.inflexionCore, maxPremium).
Which create call fires
- Default — routed best-of
- Force Path A
- Force Path B
escapeHatch: undefined → createSwapRouted(quoteTuple, signature, tokenId, maxPremium). The on-chain router gives the LP the cheaper of pool vs MM and never reverts on a bad MM quote — it falls back to the pool. A missing quote is passed as the EMPTY_QUOTE sentinel (mm == address(0), empty signature) so the router takes Path A. Only the executed rail mutates nonce / capacity / lock. This is the recommended path. See routing.getProtectionStatus & getClaimableFees
getProtectionStatus
getProtectionStatus(swapId) reads the live state of a protection. It returns ProtectionStatus or { available:false, reason } for an unknown swap.
Core.swaps plus settlePreview (via simulateContract), OracleManager.getPrice, and Core.oracleDerivedSqrtPriceX96. isPathA is mm == convexityVault. Critically, ilToDate is itself a Priceable — if the oracle is degraded the status still resolves, but the live IL inside it degrades independently.
getClaimableFees
getClaimableFees(swapId) returns the position’s checkpointed tokensOwed0/1 from NPM.positions.
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.
settle & findHintRoundId
At expiry the protocol pays the LP from the underwriter’s collateral.settle does that on-chain, and findHintRoundId provides the Chainlink round it settles against.
settle
settle(swapId, opts?{hintRoundId?}) recovers the swap’s expiry from Core.swaps, finds the bracketing Chainlink round (via findHintRoundId if no hint is given), and calls Core.settle(swapId, hintRoundId).
findHintRoundId
findHintRoundId(oracleToken, expiry, opts?{maxWalk?}) walks Chainlink latestRoundData / getRoundData backwards to find the round whose timestamp brackets expiry. It returns a bigint round id — and on an un-fetchable phase boundary it returns the best round it found rather than throwing.
Why a hint round id at all — Fork 1, settlement liveness. Settlement must always succeed within
expiry + LIVENESS_WINDOW + MAX_STALENESS + GRACE_PERIOD (invariant I8). The contract verifies the caller-supplied round actually brackets expiry, so the off-chain walk is just a gas-saving search for the right round id; a wrong hint is rejected on-chain, never trusted. Anyone can call settle — it is permissionless and the payout is deterministic. See oracles.autoProtect — orchestrated batch protection
autoProtect(options) orchestrates the whole flow — list → preview → buy — across a wallet’s eligible positions, with ACTIVE-swap dedupe so it never double-protects.
Core.swaps / nextSwapId for already-ACTIVE protections to skip, then prices and (unless dryRun) buys each eligible row. It is resilient by construction: every row returns a skipped or error result rather than aborting the batch — autoProtect never throws.
End-to-end worked example
A complete LP lifecycle against the live deployment — price, guard, buy (routed best-of), watch, settle. This is server/key-based; for the browser pattern see getting started.lp-lifecycle.ts
Live deployment addresses
Every address in this example resolves from the deployment registry — never hardcode one.
421614 · deploy block 274081134 (subgraph startBlock) · deployed 2026-06-05
Core protocol
Stylus & libraries
Oracles
Uniswap
Chainlink feeds
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.
Decimals & units recap
- dUSDC = 6 decimals — every
maxPremium,fairPremium,maxIL,premiumA/B,best, andpayoutis a 6-dec raw bigint (1 dUSDC = 1_000_000n). dWETH = 18, Chainlink feeds = 8. - WAD = 1e18 for ratios —
aWad,bWad,fairRateWad,sigmaRefWad, and the load components. sqrtPriceX96is the Uniswap Q64.96 sqrt price;sqrtToPriceWad(sqrtPX96) = (\text{sqrtP}/2^{96})^2 \cdot 10^{18}.
The claim this surface carries
LP no-bad-debt (claim A) and depositor / MM capital risk (claim B) are separate claims and are never merged. This page is claim A only.See also
Getting started
Wire the SDK in browser and server modes.
Routing
How createSwapRouted gives the LP the cheaper rail and never reverts on a bad MM quote.
MaxIL — cap & unit of risk
Why both premiums share one cap and why quotes are per-market.
Quote signing
Build a SignedQuote for the Path-B escape hatch.
The Φ-sum fair value
The on-chain closed form previewPremium reads.
Degradation
The typed envelopes every read returns instead of throwing.