@1delta/margin-fetcher 0.0.319 → 0.0.321

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
@@ -13447,7 +13447,18 @@ function getListaMarketDataConverter(lender, chainId, prices, additionalYields =
13447
13447
  rateCap: rateCap?.toString() ?? "0",
13448
13448
  rateFloor: rateFloor?.toString() ?? "0",
13449
13449
  // term menu for brokered markets (warm via fetchListaFixedTerms)
13450
- terms: fixedTermsForMarket
13450
+ terms: fixedTermsForMarket,
13451
+ // Canonical cross-protocol fixed-term descriptor (Lista + Midnight
13452
+ // share this shape). Lista: rolling-duration menu (no single
13453
+ // maturity), no market-level fees, and a per-loan early-repay
13454
+ // penalty (the concrete amount is position-level, on the loan's
13455
+ // `term.earlyRepayPenalty`).
13456
+ fixedTerm: {
13457
+ model: "lista",
13458
+ maturity: void 0,
13459
+ fees: {},
13460
+ earlyRepay: { kind: "penalty" }
13461
+ }
13451
13462
  }
13452
13463
  };
13453
13464
  data[m].chainId = chainId;
@@ -20885,9 +20896,63 @@ function tickToApr(tick, timeToMaturity) {
20885
20896
  function tickToAprNumber(tick, timeToMaturity) {
20886
20897
  return Number(tickToApr(tick, timeToMaturity)) / 1e18;
20887
20898
  }
20899
+ function midnightMaxLif(lltv, liquidationCursor) {
20900
+ const denom = WAD2 - liquidationCursor * (WAD2 - lltv) / WAD2;
20901
+ if (denom <= 0n) return 0n;
20902
+ return WAD2 * WAD2 / denom;
20903
+ }
20904
+ function midnightLiquidationPenaltyNumber(lltv, liquidationCursor) {
20905
+ if (lltv <= 0n || liquidationCursor <= 0n) return 0;
20906
+ const lif = midnightMaxLif(lltv, liquidationCursor);
20907
+ if (lif <= WAD2) return 0;
20908
+ return Number(lif - WAD2) / 1e18;
20909
+ }
20910
+ var SETTLEMENT_FEE_BREAKPOINTS_SECS = [
20911
+ 0,
20912
+ 86400,
20913
+ 604800,
20914
+ 2592e3,
20915
+ 7776e3,
20916
+ 15552e3,
20917
+ 31104e3
20918
+ ];
20919
+ var CBP_TO_FRACTION = 1e-6;
20920
+ function midnightSettlementFeeForTtm(settlementFeeCbp, ttmSecs) {
20921
+ const cbp = settlementFeeCbp;
20922
+ if (!cbp || cbp.length < 7) return 0;
20923
+ const bps = SETTLEMENT_FEE_BREAKPOINTS_SECS;
20924
+ if (ttmSecs <= 0) return cbp[0] * CBP_TO_FRACTION;
20925
+ if (ttmSecs >= bps[6]) return cbp[6] * CBP_TO_FRACTION;
20926
+ let i = 0;
20927
+ while (i < 6 && ttmSecs > bps[i + 1]) i++;
20928
+ const spanLo = bps[i];
20929
+ const spanHi = bps[i + 1];
20930
+ const t = (ttmSecs - spanLo) / (spanHi - spanLo);
20931
+ const feeCbp = cbp[i] + (cbp[i + 1] - cbp[i]) * t;
20932
+ return feeCbp * CBP_TO_FRACTION;
20933
+ }
20934
+ function midnightContinuousFeeAprPercent(continuousFeeRaw) {
20935
+ if (continuousFeeRaw == null) return 0;
20936
+ let raw;
20937
+ try {
20938
+ raw = typeof continuousFeeRaw === "bigint" ? continuousFeeRaw : BigInt(continuousFeeRaw);
20939
+ } catch {
20940
+ return 0;
20941
+ }
20942
+ if (raw <= 0n) return 0;
20943
+ return Number(raw * SECONDS_PER_YEAR7) / 1e18 * 100;
20944
+ }
20888
20945
 
20889
20946
  // src/lending/public-data/midnight/convertPublic.ts
20890
20947
  var nowSec = () => Math.floor(Date.now() / 1e3);
20948
+ function toBigIntOr0(v) {
20949
+ if (v === void 0 || v === null || v === "") return 0n;
20950
+ try {
20951
+ return typeof v === "bigint" ? v : BigInt(v);
20952
+ } catch {
20953
+ return 0n;
20954
+ }
20955
+ }
20891
20956
  function midnightLenderKey(marketId) {
20892
20957
  return "MORPHO_MIDNIGHT_" + marketId.slice(2).toUpperCase();
20893
20958
  }
@@ -20916,6 +20981,13 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20916
20981
  const ttmBig = BigInt(Math.max(0, ttm));
20917
20982
  const supplyAprPct = top?.bestSupplyTick != null && !matured ? tickToAprNumber(top.bestSupplyTick, ttmBig) * 100 : 0;
20918
20983
  const borrowAprPct = top?.bestBorrowTick != null && !matured ? tickToAprNumber(top.bestBorrowTick, ttmBig) * 100 : 0;
20984
+ const settlementFee = midnightSettlementFeeForTtm(
20985
+ config.settlementFeeCbp,
20986
+ ttm
20987
+ );
20988
+ const continuousFeeAprPct = midnightContinuousFeeAprPercent(
20989
+ config.continuousFee
20990
+ );
20919
20991
  const terms = borrowAprPct > 0 && !matured ? [
20920
20992
  {
20921
20993
  termId: 0,
@@ -20926,10 +20998,16 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20926
20998
  ] : void 0;
20927
20999
  const loanAddr = config.loanToken.toLowerCase();
20928
21000
  const loanAsset = tokens[loanAddr];
20929
- const loanKey = toOracleKey(loanAsset?.assetGroup) ?? toGenericPriceKey(loanAsset?.address ?? config.loanToken, chainId);
21001
+ const loanKey = toOracleKey(loanAsset?.assetGroup) || toGenericPriceKey(loanAddr, chainId);
20930
21002
  const loanPrice = prices[loanKey] ?? 0;
20931
- const supplyLiquidity = formatNr(top?.supplyDepthAssets ?? 0n, config.loanDecimals);
20932
- const borrowLiquidity = formatNr(top?.borrowDepthAssets ?? 0n, config.loanDecimals);
21003
+ const supplyLiquidity = formatNr(
21004
+ top?.supplyDepthAssets ?? 0n,
21005
+ config.loanDecimals
21006
+ );
21007
+ const borrowLiquidity = formatNr(
21008
+ top?.borrowDepthAssets ?? 0n,
21009
+ config.loanDecimals
21010
+ );
20933
21011
  const entry = { data: {} };
20934
21012
  const loanUid = createMarketUid(chainId, m, loanAddr);
20935
21013
  entry.data[loanUid] = {
@@ -20984,7 +21062,8 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
20984
21062
  config.collateralParams.forEach((c) => {
20985
21063
  const collAddr = c.token.toLowerCase();
20986
21064
  const ltv = parseLtv(c.lltv);
20987
- const liquidationPenalty = liquidationPenaltyFromLltv(ltv);
21065
+ const cursor = toBigIntOr0(c.liquidationCursor);
21066
+ const liquidationPenalty = cursor > 0n ? midnightLiquidationPenaltyNumber(toBigIntOr0(c.lltv), cursor) : liquidationPenaltyFromLltv(ltv);
20988
21067
  const collUid = createMarketUid(chainId, m, collAddr);
20989
21068
  entry.data[collUid] = {
20990
21069
  marketUid: collUid,
@@ -21048,6 +21127,23 @@ function convertMidnightMarketsToResponse(raw, chainId, prices = {}, _additional
21048
21127
  // Midnight-specific extras (consumed by calldata / worker-api resolvers)
21049
21128
  maturity: config.maturity,
21050
21129
  rcfThreshold: config.rcfThreshold,
21130
+ // Raw on-chain-snapshotted fees — authoritative for downstream ingest
21131
+ // (yield-tracer reads these verbatim).
21132
+ settlementFeeCbp: config.settlementFeeCbp,
21133
+ continuousFee: config.continuousFee,
21134
+ // Canonical cross-protocol fixed-term descriptor (Lista + Midnight share
21135
+ // this shape). Midnight: single calendar maturity, continuous +
21136
+ // settlement fees, and NO early-repay penalty (exit = buy debt units
21137
+ // back on the book at market price).
21138
+ fixedTerm: {
21139
+ model: "midnight",
21140
+ maturity: Number(config.maturity),
21141
+ fees: {
21142
+ continuousFeeApr: continuousFeeAprPct,
21143
+ settlementFee
21144
+ },
21145
+ earlyRepay: { kind: "none" }
21146
+ },
21051
21147
  enterGate: config.enterGate,
21052
21148
  liquidatorGate: config.liquidatorGate,
21053
21149
  collateralParams: config.collateralParams
@@ -22548,7 +22644,7 @@ var buildDolomiteUserCall = async (chainId, _lender, account, accountNumbers) =>
22548
22644
  params: [{ owner: account, number }]
22549
22645
  }));
22550
22646
  };
22551
- var midnightMarketCallCount = (collateralCount) => 1 + collateralCount;
22647
+ var midnightMarketCallCount = (collateralCount) => 2 + collateralCount;
22552
22648
  var buildMidnightUserCall = (chainId, _lender, account) => {
22553
22649
  const midnight = midnightConfig()?.[chainId]?.midnight;
22554
22650
  const markets = midnightMarketsByChain(chainId);
@@ -22560,6 +22656,29 @@ var buildMidnightUserCall = (chainId, _lender, account) => {
22560
22656
  name: "position",
22561
22657
  params: [market.marketId, account]
22562
22658
  });
22659
+ calls.push({
22660
+ address: midnight,
22661
+ name: "updatePositionView",
22662
+ params: [
22663
+ {
22664
+ chainId: BigInt(chainId),
22665
+ midnight,
22666
+ loanToken: market.loanToken,
22667
+ collateralParams: market.collateralParams.map((c) => ({
22668
+ token: c.token,
22669
+ lltv: BigInt(c.lltv),
22670
+ liquidationCursor: BigInt(c.liquidationCursor),
22671
+ oracle: c.oracle
22672
+ })),
22673
+ maturity: BigInt(market.maturity),
22674
+ rcfThreshold: BigInt(market.rcfThreshold),
22675
+ enterGate: market.enterGate,
22676
+ liquidatorGate: market.liquidatorGate
22677
+ },
22678
+ market.marketId,
22679
+ account
22680
+ ]
22681
+ });
22563
22682
  for (let index = 0; index < market.collateralParams.length; index++) {
22564
22683
  calls.push({
22565
22684
  address: midnight,
@@ -24829,6 +24948,21 @@ function readDebtUnits(positionResult) {
24829
24948
  return toBigInt10(positionResult.debt);
24830
24949
  return 0n;
24831
24950
  }
24951
+ function readUpdatedLender(updateResult) {
24952
+ if (Array.isArray(updateResult)) {
24953
+ return {
24954
+ credit: toBigInt10(updateResult[0]),
24955
+ pendingFee: toBigInt10(updateResult[1])
24956
+ };
24957
+ }
24958
+ if (updateResult && typeof updateResult === "object") {
24959
+ return {
24960
+ credit: toBigInt10(updateResult.newCredit),
24961
+ pendingFee: toBigInt10(updateResult.newPendingFee)
24962
+ };
24963
+ }
24964
+ return { credit: 0n, pendingFee: 0n };
24965
+ }
24832
24966
  var getMidnightUserDataConverter = (_lender, chainId, account, meta) => {
24833
24967
  const markets = midnightMarketsByChain(chainId);
24834
24968
  const totalCalls = markets.reduce(
@@ -24842,8 +24976,9 @@ var getMidnightUserDataConverter = (_lender, chainId, account, meta) => {
24842
24976
  let cursor = 0;
24843
24977
  for (const market of markets) {
24844
24978
  const positionResult = data[cursor];
24979
+ const updateResult = data[cursor + 1];
24845
24980
  const collateralResults = market.collateralParams.map(
24846
- (_c, i) => data[cursor + 1 + i]
24981
+ (_c, i) => data[cursor + 2 + i]
24847
24982
  );
24848
24983
  cursor += midnightMarketCallCount(market.collateralParams.length);
24849
24984
  const lenderKey = midnightLenderKey(market.marketId);
@@ -24858,24 +24993,40 @@ var getMidnightUserDataConverter = (_lender, chainId, account, meta) => {
24858
24993
  const debtUnits = readDebtUnits(positionResult);
24859
24994
  const debtStr = parseRawAmount(debtUnits.toString(), market.loanDecimals);
24860
24995
  const debtNum = Number(debtStr);
24996
+ const { credit, pendingFee } = readUpdatedLender(updateResult);
24997
+ const netCreditUnits = credit > pendingFee ? credit - pendingFee : 0n;
24998
+ const depositsStr = parseRawAmount(
24999
+ netCreditUnits.toString(),
25000
+ market.loanDecimals
25001
+ );
25002
+ const depositsNum = Number(depositsStr);
25003
+ const pendingFeeStr = parseRawAmount(
25004
+ pendingFee.toString(),
25005
+ market.loanDecimals
25006
+ );
24861
25007
  const posData = {};
24862
- let anyBalance = debtUnits > 0n;
24863
- let depositsUSD24h = 0;
25008
+ let anyBalance = debtUnits > 0n || netCreditUnits > 0n;
25009
+ let depositsUSD24h = depositsNum * loanPriceHist;
24864
25010
  posData[loanUid] = {
24865
25011
  marketUid: loanUid,
24866
25012
  underlying: loanAddr,
24867
- deposits: "0",
25013
+ deposits: depositsStr,
24868
25014
  debt: debtStr,
24869
25015
  debtStable: "0",
24870
- depositsUSD: 0,
25016
+ depositsUSD: depositsNum * loanDisplayPrice,
24871
25017
  debtUSD: debtNum * loanDisplayPrice,
24872
25018
  debtStableUSD: 0,
24873
- depositsUSDOracle: 0,
25019
+ depositsUSDOracle: depositsNum * loanOraclePrice,
24874
25020
  debtUSDOracle: debtNum * loanOraclePrice,
24875
25021
  debtStableUSDOracle: 0,
24876
25022
  stableBorrowRate: "0",
25023
+ // Supply on Midnight is a lend position, not collateral for a borrow.
24877
25024
  collateralEnabled: false,
24878
- claimableRewards: 0
25025
+ claimableRewards: 0,
25026
+ // Midnight-specific: accrued continuous fee owed by the lender (loan
25027
+ // units), already netted out of `deposits`. 0 on markets with no
25028
+ // continuous fee (the current live default).
25029
+ pendingFee: pendingFeeStr
24879
25030
  };
24880
25031
  market.collateralParams.forEach((c, i) => {
24881
25032
  const collAmt = toBigInt10(collateralResults[i]);
@@ -24888,19 +25039,19 @@ var getMidnightUserDataConverter = (_lender, chainId, account, meta) => {
24888
25039
  const collDisplayPrice = collMeta ? getDisplayPrice(collMeta) : 0;
24889
25040
  const collOraclePrice = collMeta ? getOraclePrice(collMeta) : 0;
24890
25041
  const collPriceHist = collMeta?.price?.priceUsd24h ?? collDisplayPrice;
24891
- const depositsStr = parseRawAmount(collAmt.toString(), collDec);
24892
- const depositsNum = Number(depositsStr);
24893
- depositsUSD24h += depositsNum * collPriceHist;
25042
+ const depositsStr2 = parseRawAmount(collAmt.toString(), collDec);
25043
+ const depositsNum2 = Number(depositsStr2);
25044
+ depositsUSD24h += depositsNum2 * collPriceHist;
24894
25045
  posData[collUid] = {
24895
25046
  marketUid: collUid,
24896
25047
  underlying: collAddr,
24897
- deposits: depositsStr,
25048
+ deposits: depositsStr2,
24898
25049
  debt: "0",
24899
25050
  debtStable: "0",
24900
- depositsUSD: depositsNum * collDisplayPrice,
25051
+ depositsUSD: depositsNum2 * collDisplayPrice,
24901
25052
  debtUSD: 0,
24902
25053
  debtStableUSD: 0,
24903
- depositsUSDOracle: depositsNum * collOraclePrice,
25054
+ depositsUSDOracle: depositsNum2 * collOraclePrice,
24904
25055
  debtUSDOracle: 0,
24905
25056
  debtStableUSDOracle: 0,
24906
25057
  stableBorrowRate: "0",