@1delta/margin-fetcher 5.0.4 → 5.0.6

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
@@ -8416,7 +8416,28 @@ interface TermAssetRef {
8416
8416
  * `tags.ts` — never hand-written per lender, so they cannot drift from the
8417
8417
  * numbers they summarize.
8418
8418
  */
8419
- type TermTag = Open<'fixed-rate' | 'variable-rate' | 'user-set-rate' | 'zero-interest' | 'prepaid-interest' | 'nav-accrual' | 'has-maturity' | 'perpetual' | 'rolling-duration' | 'static-debt' | 'accruing-debt' | 'time-liquidation' | 'price-liquidation' | 'redeemable' | 'no-liquidation' | 'full-collateral-seizure' | 'early-exit-free' | 'early-exit-penalty' | 'early-exit-discount' | 'exit-instant' | 'exit-capped' | 'exit-cooldown' | 'exit-queued' | 'exit-market-sale' | 'exit-may-be-impossible' | 'permissioned' | 'capped' | 'cap-full' | 'first-loss' | 'socialized-loss' | 'physical-delivery' | 'undercollateralized' | 'nav-attested' | 'immutable' | 'no-timelock' | 'eoa-controlled' | 'points-rewards' | 'oracle-flagged' | 'no-oracle'>;
8419
+ type TermTag = Open<'fixed-rate' | 'variable-rate' | 'user-set-rate' | 'zero-interest' | 'prepaid-interest' | 'nav-accrual' | 'has-maturity' | 'perpetual' | 'rolling-duration' | 'static-debt' | 'accruing-debt' | 'time-liquidation' | 'price-liquidation' | 'redeemable' | 'no-liquidation' | 'full-collateral-seizure' | 'early-exit-free' | 'early-exit-penalty' | 'early-exit-discount' | 'exit-instant' | 'exit-capped' | 'exit-cooldown' | 'exit-queued' | 'exit-market-sale' | 'exit-may-be-impossible' | 'permissioned' | 'capped' | 'cap-full' | 'first-loss' | 'socialized-loss' | 'physical-delivery' | 'undercollateralized' | 'nav-attested' | 'immutable' | 'no-timelock' | 'eoa-controlled' | 'points-rewards'
8420
+ /**
8421
+ * The headline yield is MOSTLY the asset's own (staking / RWA / savings)
8422
+ * yield, not interest this market pays. Set at >= 50 % of `aprTotal`.
8423
+ *
8424
+ * Worth a tag rather than only a detail row, because the two are not
8425
+ * interchangeable and the difference is invisible in a single percentage: a
8426
+ * market rate is paid by borrowers and moves with utilization, while
8427
+ * intrinsic yield you would earn holding the asset in your wallet — you are
8428
+ * taking this market's risk for the DIFFERENCE, not for the headline.
8429
+ * Decisive when looping: a loop only carries if the collateral out-earns the
8430
+ * debt, and intrinsic yield does not scale with leverage the way a lending
8431
+ * spread does.
8432
+ */
8433
+ | 'intrinsic-yield'
8434
+ /**
8435
+ * This market pays NO interest of its own — the entire headline is intrinsic
8436
+ * yield and/or rewards. Curvance's collateral-only legs are the canonical
8437
+ * case (`debtCap == 0` ⇒ 0 % supply APR by construction), but any market
8438
+ * with no borrowers reads the same way.
8439
+ */
8440
+ | 'no-market-interest' | 'oracle-flagged' | 'no-oracle'>;
8420
8441
  /** Human-facing copy for one side of a term sheet. */
8421
8442
  interface TermInfo {
8422
8443
  /**
@@ -8646,6 +8667,22 @@ interface BorrowExitTerms {
8646
8667
  minDebt?: string;
8647
8668
  /** Over-repay REVERTS (Midnight `uint128` underflow) — a real footgun. */
8648
8669
  overRepayReverts?: boolean;
8670
+ /**
8671
+ * Seconds after OPENING (or topping up) during which the debt cannot be
8672
+ * repaid at all.
8673
+ *
8674
+ * The mirror of `SupplyExitTerms.cooldownSecs`, and deliberately a separate
8675
+ * field because it is a categorically worse term: a supply cooldown delays
8676
+ * your money, a REPAY cooldown means a position cannot be closed,
8677
+ * deleveraged or migrated out — and, on the one lender that has it, means an
8678
+ * incoming liquidation cannot be averted by repaying either.
8679
+ *
8680
+ * Only Curvance (`MIN_HOLD_PERIOD`, 1200 s) has one today, which is exactly
8681
+ * why it needs to be stated rather than assumed away: every other lender we
8682
+ * carry lets you exit at any block, and the generic builder's
8683
+ * `availability.canClose: true` encodes that assumption.
8684
+ */
8685
+ cooldownSecs?: number;
8649
8686
  fees: FeeTerm[];
8650
8687
  }
8651
8688
  /** One named penalty, for models where a single number is not enough. */
@@ -9429,7 +9466,6 @@ declare function duration(secs: number | undefined): string;
9429
9466
  declare function shortDate(unixSecs: number | undefined): string;
9430
9467
  /** One fee → a self-contained phrase, correct even for an unrecognised `id`. */
9431
9468
  declare function feePhrase(fee: FeeTerm): string;
9432
- /** Supply-side headline: ≤ ~100 chars, always populated. */
9433
9469
  declare function supplyHeadline(s: SupplyTermSheet): string;
9434
9470
  /** Borrow-side headline. */
9435
9471
  declare function borrowHeadline(b: BorrowTermSheet): string;
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"
@@ -62148,6 +62207,13 @@ function rateTags(rate) {
62148
62207
  }
62149
62208
  if (rate.rewards?.some((r) => r.indicative || r.kind === "points"))
62150
62209
  push(out, "points-rewards");
62210
+ const total = rate.aprTotal;
62211
+ const base = rate.components?.base ?? 0;
62212
+ const intrinsic = rate.components?.intrinsic ?? 0;
62213
+ if (total > 0) {
62214
+ if (intrinsic > 0 && intrinsic >= total / 2) push(out, "intrinsic-yield");
62215
+ if (base <= 1e-4 && intrinsic > 0) push(out, "no-market-interest");
62216
+ }
62151
62217
  return [...out];
62152
62218
  }
62153
62219
  function maturityTags(maturity) {
@@ -62355,11 +62421,22 @@ var exitPhrase = {
62355
62421
  "off-chain": "exit off-chain",
62356
62422
  "dex-only": "exit only via a DEX"
62357
62423
  };
62424
+ function provenance(rate) {
62425
+ const total = rate.aprTotal;
62426
+ const base = rate.components?.base ?? 0;
62427
+ const intrinsic = rate.components?.intrinsic ?? 0;
62428
+ if (!(intrinsic > 0) || !(total > 0)) return "";
62429
+ if (base <= 1e-4) return " (all from the asset itself)";
62430
+ if (intrinsic >= total / 2) {
62431
+ return ` (${pct(intrinsic)} from the asset, ${pct(base)} from the market)`;
62432
+ }
62433
+ return ` (incl. ${pct(intrinsic)} from the asset)`;
62434
+ }
62358
62435
  function supplyHeadline(s) {
62359
62436
  if (s.role === "collateral") {
62360
62437
  return `Collateral only \xB7 ${maturityPhrase(s.maturity)}`;
62361
62438
  }
62362
- const rate = `${rateLabel(s)} ${pct(s.rate.aprTotal)}`;
62439
+ const rate = `${rateLabel(s)} ${pct(s.rate.aprTotal)}${provenance(s.rate)}`;
62363
62440
  const exit = exitPhrase[String(s.exit.mode)] ?? (s.exit.settlement === "sync" ? "withdraw any time" : "delayed withdrawal");
62364
62441
  const cooldown = s.exit.cooldownSecs ? ` (${duration(s.exit.cooldownSecs)})` : "";
62365
62442
  const mat = s.maturity.kind === "perpetual" ? "" : ` ${maturityPhrase(s.maturity)}`;
@@ -62705,6 +62782,26 @@ var TERM_PROFILES = [
62705
62782
  ]
62706
62783
  }
62707
62784
  }),
62785
+ P({
62786
+ id: "curvance.dual@v1",
62787
+ name: "Curvance isolated market",
62788
+ family: "curvance",
62789
+ supply: {
62790
+ 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.",
62791
+ implications: [
62792
+ "Posting collateral starts a 20-minute hold during which you cannot redeem or transfer your shares.",
62793
+ "A collateral-only leg pays 0 % \u2014 the yield you earn is whatever the deposited asset earns on its own."
62794
+ ]
62795
+ },
62796
+ borrow: {
62797
+ description: "Borrow the market\u2019s other leg against your collateral, with liquidation running from a soft threshold (partial) to a hard one (full).",
62798
+ implications: [
62799
+ "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).",
62800
+ "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.",
62801
+ "Liquidation has TWO thresholds \u2014 the headline penalty is the one at the soft threshold, and it scales up continuously to the hard one."
62802
+ ]
62803
+ }
62804
+ }),
62708
62805
  // ── Vaults ───────────────────────────────────────────────────────────────
62709
62806
  P({
62710
62807
  id: "vault.lending@v1",
@@ -64386,6 +64483,107 @@ var resupplyAdapter = {
64386
64483
  };
64387
64484
  }
64388
64485
  };
64486
+ function minLoanInAssetUnits(minLoanUsdWad, priceUsdWad, decimals) {
64487
+ const floor = toBig6(minLoanUsdWad);
64488
+ const price2 = toBig6(priceUsdWad);
64489
+ if (floor == null || price2 == null || floor === 0n || price2 === 0n) {
64490
+ return void 0;
64491
+ }
64492
+ const dec = typeof decimals === "number" ? decimals : Number(decimals);
64493
+ if (!Number.isFinite(dec) || dec < 0 || dec > 36) return void 0;
64494
+ const scaled = floor * 10n ** BigInt(dec);
64495
+ const units = (scaled + price2 - 1n) / price2;
64496
+ return units > 0n ? units.toString() : void 0;
64497
+ }
64498
+ function toBig6(v) {
64499
+ if (typeof v === "bigint") return v;
64500
+ if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
64501
+ if (typeof v === "string" && /^\d+$/.test(v)) return BigInt(v);
64502
+ return void 0;
64503
+ }
64504
+ var curvanceAdapter = {
64505
+ id: "curvance",
64506
+ matches: isCurvance,
64507
+ profileId: () => "curvance.dual@v1",
64508
+ build: (input) => {
64509
+ const cv = input.market?.curvance ?? {};
64510
+ const holdSecs = typeof cv.holdPeriodSeconds === "number" && cv.holdPeriodSeconds > 0 ? cv.holdPeriodSeconds : void 0;
64511
+ const tokens = Array.isArray(cv.tokens) ? cv.tokens : [];
64512
+ const debtLeg = tokens.find(
64513
+ (t) => String(t?.underlying ?? "").toLowerCase() === String(input.underlying ?? "").toLowerCase()
64514
+ ) ?? void 0;
64515
+ const minDebt = minLoanInAssetUnits(
64516
+ cv.minLoanSizeUsdWad,
64517
+ debtLeg?.assetPrice,
64518
+ debtLeg?.assetDecimals ?? debtLeg?.decimals
64519
+ );
64520
+ const softPen = numOrUndef(debtLeg?.liquidationPenaltySoft);
64521
+ const hardPen = numOrUndef(debtLeg?.liquidationPenaltyHard);
64522
+ return {
64523
+ constraints: {
64524
+ // One position per address per market; both legs share one health.
64525
+ positionModel: "account",
64526
+ positionIdMeaning: "the borrower address itself \u2014 Curvance keys a position to the account, there is no loan id or NFT",
64527
+ crossMargin: false
64528
+ },
64529
+ borrow: {
64530
+ debtShape: "accruing",
64531
+ availability: {
64532
+ // A borrow below the floor REVERTS, so the CTA must be gated on it.
64533
+ minSize: minDebt
64534
+ // The hold period blocks CLOSING, which the generic builder assumes
64535
+ // is always possible. Stated on the exit side below; `canClose`
64536
+ // stays true because it is per-ACCOUNT and time-bound, not a market
64537
+ // property — a market-level `false` here would read as "nobody can
64538
+ // ever exit", which is wrong and worse.
64539
+ },
64540
+ exit: {
64541
+ partialAllowed: true,
64542
+ minDebt,
64543
+ cooldownSecs: holdSecs
64544
+ },
64545
+ ...softPen != null || hardPen != null ? {
64546
+ liquidation: {
64547
+ // Two thresholds with a continuously scaling penalty and close
64548
+ // factor between them, plus a Fastlane/Atlas auction path that
64549
+ // can set both at runtime.
64550
+ model: "dual-threshold",
64551
+ trigger: "price",
64552
+ penalty: softPen ?? hardPen,
64553
+ penalties: [
64554
+ ...softPen != null ? [
64555
+ {
64556
+ id: "soft",
64557
+ label: "Soft threshold (partial)",
64558
+ value: softPen,
64559
+ description: "First touch: a PARTIAL liquidation at the close factor, at the lower penalty."
64560
+ }
64561
+ ] : [],
64562
+ ...hardPen != null ? [
64563
+ {
64564
+ id: "hard",
64565
+ label: "Hard threshold (full)",
64566
+ value: hardPen,
64567
+ description: "Full liquidation at the higher penalty. The penalty scales continuously between the two."
64568
+ }
64569
+ ] : []
64570
+ ]
64571
+ }
64572
+ } : {}
64573
+ },
64574
+ supply: {
64575
+ exit: {
64576
+ // The same 20 minutes blocks redemption and share transfers, not
64577
+ // just repayment.
64578
+ cooldownSecs: holdSecs
64579
+ }
64580
+ }
64581
+ };
64582
+ }
64583
+ };
64584
+ function numOrUndef(v) {
64585
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
64586
+ }
64389
64587
 
64390
64588
  // src/terms/adapters/index.ts
64391
64589
  var TERM_ADAPTERS = [
@@ -64404,7 +64602,8 @@ var TERM_ADAPTERS = [
64404
64602
  gearboxAdapter,
64405
64603
  llamaLendAdapter,
64406
64604
  frankencoinAdapter,
64407
- resupplyAdapter
64605
+ resupplyAdapter,
64606
+ curvanceAdapter
64408
64607
  ];
64409
64608
  function resolveAdapter(lender) {
64410
64609
  return TERM_ADAPTERS.find((a) => a.matches(lender));