@1delta/margin-fetcher 5.0.4 → 5.0.5

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
@@ -331,4 +331,4 @@ User positions (`{credit, debt, collateral[128], collateralBitmap}`) are read vi
331
331
 
332
332
  > **Scope:** the fetcher and direct spot actions (supply collateral / borrow-via-`take` / repay /
333
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).
334
+ > [`calldata-sdk/src/evm/generic/midnight/COMPOSER.md`](../calldata-sdk/src/evm/generic/midnight/COMPOSER.md).
package/dist/index.d.ts CHANGED
@@ -3332,7 +3332,7 @@ declare function resupplyKeyParts(key: string): {
3332
3332
  * Map one Resupply deployment's on-chain batch into the shared
3333
3333
  * `MorphoGeneralPublicResponse` shape, keyed `RESUPPLY_<chainId>_<PAIR>`.
3334
3334
  *
3335
- * Four modelling decisions worth knowing (all from RESUPPLY_PLAN.md):
3335
+ * Four modelling decisions worth knowing (all from RESUPPLY.md):
3336
3336
  *
3337
3337
  * - **The collateral we publish is the UNDERLYING (crvUSD / frxUSD), not the
3338
3338
  * ERC-4626 share.** The share is an internal accounting unit that no token
@@ -3620,7 +3620,7 @@ declare const usddIlkBytes32: (ilk: string) => `0x${string}`;
3620
3620
  * ink total).
3621
3621
  *
3622
3622
  * The roster is EMPTY on both EVM chains today (`cdpi() = 0`, no ilk filed —
3623
- * see USDD_PLAN.md), so this returns zero markets without issuing a
3623
+ * see USDD.md), so this returns zero markets without issuing a
3624
3624
  * multicall. The code path stays live so the day metadata fills, data flows
3625
3625
  * with no code change.
3626
3626
  */
@@ -3744,7 +3744,7 @@ declare function frankencoinKeyParts(key: string): {
3744
3744
  * Map one Frankencoin deployment's on-chain batch into the shared
3745
3745
  * `MorphoGeneralPublicResponse` shape, keyed `FRANKENCOIN_<chainId>_<ORIGINAL>`.
3746
3746
  *
3747
- * Three modelling decisions worth knowing (all from FRANKENCOIN_PLAN.md):
3747
+ * Three modelling decisions worth knowing (all from FRANKENCOIN.md):
3748
3748
  *
3749
3749
  * - **The liquidation price is owner-DECLARED, not an oracle.** `price` is
3750
3750
  * 36-dec scaled against the collateral's decimals and is policed by a
@@ -8646,6 +8646,22 @@ interface BorrowExitTerms {
8646
8646
  minDebt?: string;
8647
8647
  /** Over-repay REVERTS (Midnight `uint128` underflow) — a real footgun. */
8648
8648
  overRepayReverts?: boolean;
8649
+ /**
8650
+ * Seconds after OPENING (or topping up) during which the debt cannot be
8651
+ * repaid at all.
8652
+ *
8653
+ * The mirror of `SupplyExitTerms.cooldownSecs`, and deliberately a separate
8654
+ * field because it is a categorically worse term: a supply cooldown delays
8655
+ * your money, a REPAY cooldown means a position cannot be closed,
8656
+ * deleveraged or migrated out — and, on the one lender that has it, means an
8657
+ * incoming liquidation cannot be averted by repaying either.
8658
+ *
8659
+ * Only Curvance (`MIN_HOLD_PERIOD`, 1200 s) has one today, which is exactly
8660
+ * why it needs to be stated rather than assumed away: every other lender we
8661
+ * carry lets you exit at any block, and the generic builder's
8662
+ * `availability.canClose: true` encodes that assumption.
8663
+ */
8664
+ cooldownSecs?: number;
8649
8665
  fees: FeeTerm[];
8650
8666
  }
8651
8667
  /** One named penalty, for models where a single number is not enough. */
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { parseAbi, keccak256, encodeAbiParameters, formatEther, BaseError, erc20Abi, stringToHex, pad, encodeFunctionData, formatUnits, isAddress, getAddress, decodeFunctionResult, decodeAbiParameters, concat, AbiEncodingLengthMismatchError, concatHex, toBytes, slice, hexToString, InvalidAddressError, boolToHex, integerRegex, numberToHex, bytesRegex, BytesSizeMismatchError, arrayRegex, UnsupportedPackedAbiType } from './chunk-YILYOOYB.js';
2
2
  import './chunk-BYTNVMX7.js';
3
3
  import './chunk-PR4QN5HX.js';
4
- import { Lender, isExactly, isMidnight, isTerm, isTermMax, isTeller, isListaDao, isLiquityFamily, isRiver, isInverse, isUsdd, isCompoundV3Type, isFluid, isGearboxV3, isLlamaLend, isFrankencoin, isResupply, LIQUITY_FAMILY_LENDERS, isAaveType, isCompoundV3, isMultiMarket, isSiloV2Type, isSiloV3Type, isDolomite, isCurvance, hasCrossMarginRisk, isEulerType, isAaveV4Type, isInit, isMorphoType, isCompoundV2Type, isVenusType, isSumerType, AAVE_V3_LENDERS, AAVE_V2_LENDERS, isAaveV2Type, isAaveV32Type, isAaveV3Type, isYLDR, isLista, hasSubAccounts, isTectonicType, isKineticType, isBenqiType } from '@1delta/lender-registry';
4
+ import { Lender, isExactly, isMidnight, isTerm, isTermMax, isTeller, isListaDao, isLiquityFamily, isRiver, isInverse, isUsdd, isCompoundV3Type, isFluid, isGearboxV3, isLlamaLend, isFrankencoin, isResupply, isCurvance, LIQUITY_FAMILY_LENDERS, isAaveType, isCompoundV3, isMultiMarket, isSiloV2Type, isSiloV3Type, isDolomite, hasCrossMarginRisk, isEulerType, isAaveV4Type, isInit, isMorphoType, isCompoundV2Type, isVenusType, isSumerType, AAVE_V3_LENDERS, AAVE_V2_LENDERS, isAaveV2Type, isAaveV32Type, isAaveV3Type, isYLDR, isLista, hasSubAccounts, isTectonicType, isKineticType, isBenqiType } from '@1delta/lender-registry';
5
5
  export { hasCrossMarginRisk, isAaveType, isAaveV2Type, isAaveV32Type, isAaveV3Type, isCompoundV3, isCompoundV3Type, isInit, isMorphoType, isMultiMarket, isYLDR } from '@1delta/lender-registry';
6
6
  import { fetchTokenLists, fetchTokenList, aavePools, compoundV3Pools, initConfig, morphoPools, compoundV2Pools, eulerVaults, siloMarkets, siloV2LenderKey, siloMarketsV3, siloV3LenderKey, aaveV4Spokes, aaveV4SpokeLenderKey, fluidResolvers, gearboxResolvers, dolomiteConfigs, midnightConfig, termConfig, termMarketsByChain, exactlyConfig, liquityLendersByChain, riverLendersByChain, inverseLendersByChain, llamaLendLendersByChain, usddLendersByChain, frankencoinLendersByChain, resupplyLendersByChain, curvanceLendersByChain, tellerConfigByChain, tellerPoolsByChain, termMaxConfigByChain, aaveReserves, compoundV3Reserves, liquityConfigFor, liquityBranchesByChain, riverConfigFor, riverChainData, termMaxApiBaseUrl, termMaxLenderKey, inverseConfigFor, inverseChainData, llamaLendConfigFor, llamaLendChainData, llamaLendApiBaseUrl, resupplyConfigFor, usddConfigFor, usddChainData, frankencoinConfigFor, frankencoinChainData, aaveOracles, morphoTypeMarkets, resolveMidnightApiBase, resolveTermApiBase, llamaLendMarketByVault, getSiloV2MarketEntry, getSiloV3MarketEntry, dolomiteEmode, curvanceConfigFor, aaveTokens, morphoTypeVaults, eulerConfigs, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxV3LenderKey, midnightMarketsByChain, parseAaveV4SpokeLenderKey, gearboxAccountCompressor, compoundV2Tokens, exactlyMarketsByChain, aaveOraclesConfig, compoundV2Oracles, compoundV2TokenArray, compoundV2OraclesConfig, compoundV3OraclesData, compoundV3BaseData, compoundV2Reserves, aaveV4Oracles } from '@1delta/data-sdk';
7
7
  import lodash from 'lodash';
@@ -23450,6 +23450,30 @@ async function fetchCurvanceMarkets(lender, chainId) {
23450
23450
  );
23451
23451
  return result;
23452
23452
  }
23453
+ async function fetchMinLoanSizes(chainId, marketManagers) {
23454
+ const out = /* @__PURE__ */ new Map();
23455
+ if (marketManagers.length === 0) return out;
23456
+ try {
23457
+ const res = await multicallRetryUniversal({
23458
+ chain: chainId,
23459
+ calls: marketManagers.map((mm) => ({
23460
+ address: mm,
23461
+ name: "MIN_LOAN_SIZE",
23462
+ params: []
23463
+ })),
23464
+ abi: marketManagers.map(() => CurvanceMarketManagerAbi),
23465
+ allowFailure: true
23466
+ });
23467
+ marketManagers.forEach((mm, i) => {
23468
+ const v = res?.[i];
23469
+ if (typeof v === "bigint") out.set(mm.toLowerCase(), v);
23470
+ else if (typeof v === "string" && /^\d+$/.test(v))
23471
+ out.set(mm.toLowerCase(), BigInt(v));
23472
+ });
23473
+ } catch {
23474
+ }
23475
+ return out;
23476
+ }
23453
23477
  async function fetchViaReader(lender, chainId, config) {
23454
23478
  const reader = config.protocolReader;
23455
23479
  let staticData = [];
@@ -23540,9 +23564,18 @@ async function fetchViaReader(lender, chainId, config) {
23540
23564
  markets.push({
23541
23565
  marketManager,
23542
23566
  cooldownLength: big2(m?.cooldownLength),
23567
+ // Filled in below — one batched round for every market at once.
23568
+ minLoanSizeUsdWad: 0n,
23543
23569
  tokens
23544
23570
  });
23545
23571
  }
23572
+ const minLoans = await fetchMinLoanSizes(
23573
+ chainId,
23574
+ markets.map((mk) => mk.marketManager)
23575
+ );
23576
+ for (const mk of markets) {
23577
+ mk.minLoanSizeUsdWad = minLoans.get(mk.marketManager.toLowerCase()) ?? 0n;
23578
+ }
23546
23579
  return { lender, config, markets };
23547
23580
  }
23548
23581
  async function fetchViaMulticall(lender, chainId, config) {
@@ -23729,10 +23762,19 @@ async function fetchViaMulticall(lender, chainId, config) {
23729
23762
  // `MIN_HOLD_PERIOD` is a constant on every deployment; hardcoding the
23730
23763
  // known value here beats an extra round-trip in the fallback path.
23731
23764
  cooldownLength: 1200n,
23765
+ minLoanSizeUsdWad: 0n,
23732
23766
  tokens: [token]
23733
23767
  });
23734
23768
  });
23735
- return { lender, config, markets: [...byManager.values()] };
23769
+ const out = [...byManager.values()];
23770
+ const minLoans = await fetchMinLoanSizes(
23771
+ chainId,
23772
+ out.map((mk) => mk.marketManager)
23773
+ );
23774
+ for (const mk of out) {
23775
+ mk.minLoanSizeUsdWad = minLoans.get(mk.marketManager.toLowerCase()) ?? 0n;
23776
+ }
23777
+ return { lender, config, markets: out };
23736
23778
  }
23737
23779
  function allowlist(config) {
23738
23780
  if (!config.marketAllowlist || config.marketAllowlist.length === 0) {
@@ -24608,6 +24650,13 @@ function convertCurvanceMarketsToResponse(raw, chainId, prices = {}, _additional
24608
24650
  * opened or topped up — gate every exit flow on it.
24609
24651
  */
24610
24652
  holdPeriodSeconds: Number(market.cooldownLength || 1200n),
24653
+ /**
24654
+ * `MIN_LOAN_SIZE` in WAD **USD** — not base units, unlike every
24655
+ * other lender's floor. Gates BOTH ends: opening below it reverts,
24656
+ * and so does a partial repay leaving less (a full repay always
24657
+ * works). `0` means the read failed, NOT that there is no floor.
24658
+ */
24659
+ minLoanSizeUsdWad: market.minLoanSizeUsdWad.toString(),
24611
24660
  /**
24612
24661
  * Native-leverage entry points for this market, or `undefined` when
24613
24662
  * none is registered — which is a REAL state, not a gap: 5 of the 25
@@ -62083,6 +62132,16 @@ var ROW_DESCRIPTOR_KEYS = [
62083
62132
  "exactly",
62084
62133
  "usdd",
62085
62134
  "termmax",
62135
+ // These five were missing, and the failure is SILENT: the adapter still
62136
+ // runs, reads an empty descriptor and returns a sheet that looks complete
62137
+ // while omitting the lender's whole reason for having an adapter. Any lender
62138
+ // with an entry in `TERM_ADAPTERS` that reads `input.market.<key>` belongs
62139
+ // here — the adapter list and this list must not drift apart.
62140
+ "llamalend",
62141
+ "frankencoin",
62142
+ "resupply",
62143
+ "gearbox",
62144
+ "curvance",
62086
62145
  "broker",
62087
62146
  "collateralProvider",
62088
62147
  "loanProvider"
@@ -62705,6 +62764,26 @@ var TERM_PROFILES = [
62705
62764
  ]
62706
62765
  }
62707
62766
  }),
62767
+ P({
62768
+ id: "curvance.dual@v1",
62769
+ name: "Curvance isolated market",
62770
+ family: "curvance",
62771
+ supply: {
62772
+ description: "An isolated two-token market on Monad. Each leg is a cToken; a leg with no debt cap is collateral-only and pays no supply interest \u2014 its return is the underlying asset\u2019s own yield.",
62773
+ implications: [
62774
+ "Posting collateral starts a 20-minute hold during which you cannot redeem or transfer your shares.",
62775
+ "A collateral-only leg pays 0 % \u2014 the yield you earn is whatever the deposited asset earns on its own."
62776
+ ]
62777
+ },
62778
+ borrow: {
62779
+ description: "Borrow the market\u2019s other leg against your collateral, with liquidation running from a soft threshold (partial) to a hard one (full).",
62780
+ implications: [
62781
+ "You cannot repay for 20 MINUTES after opening or topping up \u2014 the position cannot be closed, deleveraged or migrated out in that window, and an incoming liquidation cannot be averted by repaying (adding collateral still works).",
62782
+ "There is a $10 minimum debt, and it gates the exit as well: a partial repay that would leave less than $10 of debt reverts. Closing in full is always allowed.",
62783
+ "Liquidation has TWO thresholds \u2014 the headline penalty is the one at the soft threshold, and it scales up continuously to the hard one."
62784
+ ]
62785
+ }
62786
+ }),
62708
62787
  // ── Vaults ───────────────────────────────────────────────────────────────
62709
62788
  P({
62710
62789
  id: "vault.lending@v1",
@@ -64386,6 +64465,107 @@ var resupplyAdapter = {
64386
64465
  };
64387
64466
  }
64388
64467
  };
64468
+ function minLoanInAssetUnits(minLoanUsdWad, priceUsdWad, decimals) {
64469
+ const floor = toBig6(minLoanUsdWad);
64470
+ const price2 = toBig6(priceUsdWad);
64471
+ if (floor == null || price2 == null || floor === 0n || price2 === 0n) {
64472
+ return void 0;
64473
+ }
64474
+ const dec = typeof decimals === "number" ? decimals : Number(decimals);
64475
+ if (!Number.isFinite(dec) || dec < 0 || dec > 36) return void 0;
64476
+ const scaled = floor * 10n ** BigInt(dec);
64477
+ const units = (scaled + price2 - 1n) / price2;
64478
+ return units > 0n ? units.toString() : void 0;
64479
+ }
64480
+ function toBig6(v) {
64481
+ if (typeof v === "bigint") return v;
64482
+ if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
64483
+ if (typeof v === "string" && /^\d+$/.test(v)) return BigInt(v);
64484
+ return void 0;
64485
+ }
64486
+ var curvanceAdapter = {
64487
+ id: "curvance",
64488
+ matches: isCurvance,
64489
+ profileId: () => "curvance.dual@v1",
64490
+ build: (input) => {
64491
+ const cv = input.market?.curvance ?? {};
64492
+ const holdSecs = typeof cv.holdPeriodSeconds === "number" && cv.holdPeriodSeconds > 0 ? cv.holdPeriodSeconds : void 0;
64493
+ const tokens = Array.isArray(cv.tokens) ? cv.tokens : [];
64494
+ const debtLeg = tokens.find(
64495
+ (t) => String(t?.underlying ?? "").toLowerCase() === String(input.underlying ?? "").toLowerCase()
64496
+ ) ?? void 0;
64497
+ const minDebt = minLoanInAssetUnits(
64498
+ cv.minLoanSizeUsdWad,
64499
+ debtLeg?.assetPrice,
64500
+ debtLeg?.assetDecimals ?? debtLeg?.decimals
64501
+ );
64502
+ const softPen = numOrUndef(debtLeg?.liquidationPenaltySoft);
64503
+ const hardPen = numOrUndef(debtLeg?.liquidationPenaltyHard);
64504
+ return {
64505
+ constraints: {
64506
+ // One position per address per market; both legs share one health.
64507
+ positionModel: "account",
64508
+ positionIdMeaning: "the borrower address itself \u2014 Curvance keys a position to the account, there is no loan id or NFT",
64509
+ crossMargin: false
64510
+ },
64511
+ borrow: {
64512
+ debtShape: "accruing",
64513
+ availability: {
64514
+ // A borrow below the floor REVERTS, so the CTA must be gated on it.
64515
+ minSize: minDebt
64516
+ // The hold period blocks CLOSING, which the generic builder assumes
64517
+ // is always possible. Stated on the exit side below; `canClose`
64518
+ // stays true because it is per-ACCOUNT and time-bound, not a market
64519
+ // property — a market-level `false` here would read as "nobody can
64520
+ // ever exit", which is wrong and worse.
64521
+ },
64522
+ exit: {
64523
+ partialAllowed: true,
64524
+ minDebt,
64525
+ cooldownSecs: holdSecs
64526
+ },
64527
+ ...softPen != null || hardPen != null ? {
64528
+ liquidation: {
64529
+ // Two thresholds with a continuously scaling penalty and close
64530
+ // factor between them, plus a Fastlane/Atlas auction path that
64531
+ // can set both at runtime.
64532
+ model: "dual-threshold",
64533
+ trigger: "price",
64534
+ penalty: softPen ?? hardPen,
64535
+ penalties: [
64536
+ ...softPen != null ? [
64537
+ {
64538
+ id: "soft",
64539
+ label: "Soft threshold (partial)",
64540
+ value: softPen,
64541
+ description: "First touch: a PARTIAL liquidation at the close factor, at the lower penalty."
64542
+ }
64543
+ ] : [],
64544
+ ...hardPen != null ? [
64545
+ {
64546
+ id: "hard",
64547
+ label: "Hard threshold (full)",
64548
+ value: hardPen,
64549
+ description: "Full liquidation at the higher penalty. The penalty scales continuously between the two."
64550
+ }
64551
+ ] : []
64552
+ ]
64553
+ }
64554
+ } : {}
64555
+ },
64556
+ supply: {
64557
+ exit: {
64558
+ // The same 20 minutes blocks redemption and share transfers, not
64559
+ // just repayment.
64560
+ cooldownSecs: holdSecs
64561
+ }
64562
+ }
64563
+ };
64564
+ }
64565
+ };
64566
+ function numOrUndef(v) {
64567
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
64568
+ }
64389
64569
 
64390
64570
  // src/terms/adapters/index.ts
64391
64571
  var TERM_ADAPTERS = [
@@ -64404,7 +64584,8 @@ var TERM_ADAPTERS = [
64404
64584
  gearboxAdapter,
64405
64585
  llamaLendAdapter,
64406
64586
  frankencoinAdapter,
64407
- resupplyAdapter
64587
+ resupplyAdapter,
64588
+ curvanceAdapter
64408
64589
  ];
64409
64590
  function resolveAdapter(lender) {
64410
64591
  return TERM_ADAPTERS.find((a) => a.matches(lender));