@1delta/margin-fetcher 0.0.318 → 0.0.320

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @1delta/margin-fetcher
2
2
 
3
- Multi-protocol lending data fetcher supporting Morpho Blue, Aave V2/V3, Compound V2/V3, Euler, Init, and Lista DAO. Provides public market data (rates, TVL, configs) and per-user position data (balances, shares, collateral) in a unified format.
3
+ Multi-protocol lending data fetcher supporting Morpho Blue, Aave V2/V3, Compound V2/V3, Euler, Init, Lista DAO, and Morpho Midnight. Provides public market data (rates, TVL, configs) and per-user position data (balances, shares, collateral) in a unified format.
4
4
 
5
5
  ## How Morpho Blue Works
6
6
 
@@ -299,3 +299,36 @@ Brokered markets emit **no variable debt** — the position's `debt` is `0`, the
299
299
 
300
300
  > **Note:** `terms[]` currently itemizes only fixed loans. The dynamic position (post-refinance) is
301
301
  > included in the `debtStable` total but not yet surfaced as its own repayable term.
302
+
303
+ ## Morpho Midnight extension
304
+
305
+ Morpho Midnight is a Base-only, **fixed-rate / fixed-maturity** lending protocol built on an
306
+ **order book of off-chain signed offers** (zero-coupon credit/debt units) — it is **NOT a Morpho
307
+ Blue fork**, so it is modeled as its own `'midnight'` provider (`Lender.MORPHO_MIDNIGHT`,
308
+ `isMidnight`), not `isMorphoType`. There is no pool and no utilization curve: rates are derived
309
+ from each offer's `tick` → price and its time-to-maturity, knowable only via the API. Each
310
+ maturity is its own isolated market, keyed `MORPHO_MIDNIGHT_<marketId>`.
311
+
312
+ ### API-first fetching
313
+
314
+ Unlike Morpho Blue's GraphQL-then-on-chain hybrid, Midnight public data is **API-only** — there
315
+ is no on-chain public fallback in `fetchLenderAll`. The fetcher lives in
316
+ [`lending/public-data/midnight/`](src/lending/public-data/midnight/):
317
+
318
+ - **`apiClient` / `fetchPublic`** — an `ApiBookSource` reads the order book per market
319
+ (`GET /books/{id}`) from the Midnight API. The base URL resolves override → config → hosted
320
+ default (`https://api.morpho.org/v0/midnight`); the worker can point it at a dev API via
321
+ `MIDNIGHT_API_BASE` (see `data-sdk` `setMidnightApiBase`).
322
+ - **`math.ts`** — ported `TickLib` (`tickToApr` etc.), bit-verified against `@morpho-org/midnight-sdk`.
323
+ - **`convertPublic`** — normalizes the book into a `MorphoGeneralPublicResponse` keyed by
324
+ `MORPHO_MIDNIGHT_<id>`, emitting the fixed borrow/supply APR and a single-term
325
+ `params.market.terms[]` (`{termId, durationSecs, durationDays, apr}`) carrying the maturity — so
326
+ the maturity flows through the same generic `terms[]` path as Lista's fixed loans. Units are in
327
+ the **loan token's decimals** (not 18): `assets = units × price / WAD`, so `units ≈ assets`.
328
+
329
+ User positions (`{credit, debt, collateral[128], collateralBitmap}`) are read via multicall in
330
+ [`lending/user-data/midnight/`](src/lending/user-data/midnight/), one `UserData` per market.
331
+
332
+ > **Scope:** the fetcher and direct spot actions (supply collateral / borrow-via-`take` / repay /
333
+ > withdraw) are wired. Composer-routed leverage and native ETH are deferred — see
334
+ > [`calldata-sdk/src/evm/generic/midnight/COMPOSER_PLAN.md`](../calldata-sdk/src/evm/generic/midnight/COMPOSER_PLAN.md).
package/dist/index.js CHANGED
@@ -20885,9 +20885,63 @@ function tickToApr(tick, timeToMaturity) {
20885
20885
  function tickToAprNumber(tick, timeToMaturity) {
20886
20886
  return Number(tickToApr(tick, timeToMaturity)) / 1e18;
20887
20887
  }
20888
+ function midnightMaxLif(lltv, liquidationCursor) {
20889
+ const denom = WAD2 - liquidationCursor * (WAD2 - lltv) / WAD2;
20890
+ if (denom <= 0n) return 0n;
20891
+ return WAD2 * WAD2 / denom;
20892
+ }
20893
+ function midnightLiquidationPenaltyNumber(lltv, liquidationCursor) {
20894
+ if (lltv <= 0n || liquidationCursor <= 0n) return 0;
20895
+ const lif = midnightMaxLif(lltv, liquidationCursor);
20896
+ if (lif <= WAD2) return 0;
20897
+ return Number(lif - WAD2) / 1e18;
20898
+ }
20899
+ var SETTLEMENT_FEE_BREAKPOINTS_SECS = [
20900
+ 0,
20901
+ 86400,
20902
+ 604800,
20903
+ 2592e3,
20904
+ 7776e3,
20905
+ 15552e3,
20906
+ 31104e3
20907
+ ];
20908
+ var CBP_TO_FRACTION = 1e-6;
20909
+ function midnightSettlementFeeForTtm(settlementFeeCbp, ttmSecs) {
20910
+ const cbp = settlementFeeCbp;
20911
+ if (!cbp || cbp.length < 7) return 0;
20912
+ const bps = SETTLEMENT_FEE_BREAKPOINTS_SECS;
20913
+ if (ttmSecs <= 0) return cbp[0] * CBP_TO_FRACTION;
20914
+ if (ttmSecs >= bps[6]) return cbp[6] * CBP_TO_FRACTION;
20915
+ let i = 0;
20916
+ while (i < 6 && ttmSecs > bps[i + 1]) i++;
20917
+ const spanLo = bps[i];
20918
+ const spanHi = bps[i + 1];
20919
+ const t = (ttmSecs - spanLo) / (spanHi - spanLo);
20920
+ const feeCbp = cbp[i] + (cbp[i + 1] - cbp[i]) * t;
20921
+ return feeCbp * CBP_TO_FRACTION;
20922
+ }
20923
+ function midnightContinuousFeeAprPercent(continuousFeeRaw) {
20924
+ if (continuousFeeRaw == null) return 0;
20925
+ let raw;
20926
+ try {
20927
+ raw = typeof continuousFeeRaw === "bigint" ? continuousFeeRaw : BigInt(continuousFeeRaw);
20928
+ } catch {
20929
+ return 0;
20930
+ }
20931
+ if (raw <= 0n) return 0;
20932
+ return Number(raw * SECONDS_PER_YEAR7) / 1e18 * 100;
20933
+ }
20888
20934
 
20889
20935
  // src/lending/public-data/midnight/convertPublic.ts
20890
20936
  var nowSec = () => Math.floor(Date.now() / 1e3);
20937
+ function toBigIntOr0(v) {
20938
+ if (v === void 0 || v === null || v === "") return 0n;
20939
+ try {
20940
+ return typeof v === "bigint" ? v : BigInt(v);
20941
+ } catch {
20942
+ return 0n;
20943
+ }
20944
+ }
20891
20945
  function midnightLenderKey(marketId) {
20892
20946
  return "MORPHO_MIDNIGHT_" + marketId.slice(2).toUpperCase();
20893
20947
  }
@@ -20916,6 +20970,13 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20916
20970
  const ttmBig = BigInt(Math.max(0, ttm));
20917
20971
  const supplyAprPct = top?.bestSupplyTick != null && !matured ? tickToAprNumber(top.bestSupplyTick, ttmBig) * 100 : 0;
20918
20972
  const borrowAprPct = top?.bestBorrowTick != null && !matured ? tickToAprNumber(top.bestBorrowTick, ttmBig) * 100 : 0;
20973
+ const settlementFee = midnightSettlementFeeForTtm(
20974
+ config.settlementFeeCbp,
20975
+ ttm
20976
+ );
20977
+ const continuousFeeAprPct = midnightContinuousFeeAprPercent(
20978
+ config.continuousFee
20979
+ );
20919
20980
  const terms = borrowAprPct > 0 && !matured ? [
20920
20981
  {
20921
20982
  termId: 0,
@@ -20925,9 +20986,17 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20925
20986
  }
20926
20987
  ] : void 0;
20927
20988
  const loanAddr = config.loanToken.toLowerCase();
20928
- const loanPrice = prices[loanAddr] ?? 0;
20929
- const supplyLiquidity = formatNr(top?.supplyDepthAssets ?? 0n, config.loanDecimals);
20930
- const borrowLiquidity = formatNr(top?.borrowDepthAssets ?? 0n, config.loanDecimals);
20989
+ const loanAsset = tokens[loanAddr];
20990
+ const loanKey = toOracleKey(loanAsset?.assetGroup) || toGenericPriceKey(loanAddr, chainId);
20991
+ const loanPrice = prices[loanKey] ?? 0;
20992
+ const supplyLiquidity = formatNr(
20993
+ top?.supplyDepthAssets ?? 0n,
20994
+ config.loanDecimals
20995
+ );
20996
+ const borrowLiquidity = formatNr(
20997
+ top?.borrowDepthAssets ?? 0n,
20998
+ config.loanDecimals
20999
+ );
20931
21000
  const entry = { data: {} };
20932
21001
  const loanUid = createMarketUid(chainId, m, loanAddr);
20933
21002
  entry.data[loanUid] = {
@@ -20952,6 +21021,11 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20952
21021
  // Midnight is fixed-rate: expose the fixed borrow APR on stableBorrowRate
20953
21022
  // too, so fixed-rate consumers pick it up like a term product.
20954
21023
  stableBorrowRate: borrowAprPct,
21024
+ // Mutable per-market fees (display convenience; authoritative raw values
21025
+ // live on params.market). continuousFeeApr is a %/yr lender-side haircut;
21026
+ // settlementFee is the effective fraction at the current TTM.
21027
+ continuousFeeApr: continuousFeeAprPct,
21028
+ settlementFee,
20955
21029
  intrinsicYield: 0,
20956
21030
  rewards: void 0,
20957
21031
  decimals: config.loanDecimals,
@@ -20982,7 +21056,8 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20982
21056
  config.collateralParams.forEach((c) => {
20983
21057
  const collAddr = c.token.toLowerCase();
20984
21058
  const ltv = parseLtv(c.lltv);
20985
- const liquidationPenalty = liquidationPenaltyFromLltv(ltv);
21059
+ const cursor = toBigIntOr0(c.liquidationCursor);
21060
+ const liquidationPenalty = cursor > 0n ? midnightLiquidationPenaltyNumber(toBigIntOr0(c.lltv), cursor) : liquidationPenaltyFromLltv(ltv);
20986
21061
  const collUid = createMarketUid(chainId, m, collAddr);
20987
21062
  entry.data[collUid] = {
20988
21063
  marketUid: collUid,
@@ -21046,6 +21121,13 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
21046
21121
  // Midnight-specific extras (consumed by calldata / worker-api resolvers)
21047
21122
  maturity: config.maturity,
21048
21123
  rcfThreshold: config.rcfThreshold,
21124
+ // Mutable, on-chain-snapshotted fees. Raw values are authoritative for
21125
+ // downstream ingest (yield-tracer); the derived `continuousFeeApr` (%)
21126
+ // and `settlementFee` (fraction at current TTM) are convenience fields.
21127
+ settlementFeeCbp: config.settlementFeeCbp,
21128
+ continuousFee: config.continuousFee,
21129
+ continuousFeeApr: continuousFeeAprPct,
21130
+ settlementFee,
21049
21131
  enterGate: config.enterGate,
21050
21132
  liquidatorGate: config.liquidatorGate,
21051
21133
  collateralParams: config.collateralParams