@1delta/margin-fetcher 5.0.52 → 5.0.54

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/dist/index.d.ts CHANGED
@@ -12179,12 +12179,23 @@ interface EarnExclusions {
12179
12179
  lowTvl: number;
12180
12180
  /** Above the risk ceiling the rest of the API also applies. */
12181
12181
  highRisk: number;
12182
+ /**
12183
+ * Rows whose position is a multi-token LP (`basket`), hidden by default.
12184
+ *
12185
+ * Excluded unasked because the row names ONE token while the position holds
12186
+ * several in a split the pool keeps moving — so a depositor reading it as
12187
+ * "USDC at 10 %" ends up carrying the other leg's price risk. Reported here
12188
+ * so a UI can offer `?lp=include` rather than let the rows vanish silently.
12189
+ */
12190
+ lp: number;
12182
12191
  }
12183
12192
  interface EarnAppliedDefaults {
12184
12193
  minTvlUsd: number;
12185
12194
  maxRiskScore: number;
12186
12195
  excludePassthrough: boolean;
12187
12196
  excludeIlliquid: boolean;
12197
+ /** LP / auto-rebalancing positions are hidden unless `?lp=include`. */
12198
+ excludeLp: boolean;
12188
12199
  }
12189
12200
  /**
12190
12201
  * The filter vocabulary, published rather than hard-coded.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import './chunk-BYTNVMX7.js';
3
3
  import './chunk-PR4QN5HX.js';
4
4
  import { Lender, isExactly, isMidnight, isTerm, isTermMax, isTeller, isListaDao, isLiquityFamily, isRiver, isInverse, isDssType, isCompoundV3Type, isFluid, isGearboxV3, isLlamaLend, isFrankencoin, isResupply, isCurvance, isTwyne, isFraxlend, LIQUITY_FAMILY_LENDERS, isAaveType, isCompoundV3, isMultiMarket, isSiloV2Type, isSiloV3Type, isDolomite, hasCrossMarginRisk, isEulerType, isAaveV4Type, isInit, isMorphoType, isCompoundV2Type, isVenusType, isSumerType, AAVE_V3_LENDERS, AAVE_V2_LENDERS, isMorphoBlue, isAaveV2Type, isAaveV32Type, isAaveV3Type, isUsdd, isSky, 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
- 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, skyLendersByChain, frankencoinLendersByChain, resupplyLendersByChain, curvanceLendersByChain, fraxlendLendersByChain, tellerConfigByChain, tellerPoolsByChain, termMaxConfigByChain, aaveReserves, compoundV3Reserves, liquityConfigFor, liquityBranchesByChain, riverConfigFor, riverChainData, termMaxApiBaseUrl, termMaxLenderKey, inverseConfigFor, inverseChainData, llamaLendConfigFor, llamaLendChainData, llamaLendApiBaseUrl, resupplyConfigFor, fraxlendConfigFor, dssConfigFor, dssChainData, dssDebtToken, dssDebtJoin, frankencoinConfigFor, frankencoinChainData, aaveOracles, morphoTypeMarkets, resolveMidnightApiBase, resolveTermApiBase, llamaLendMarketByVault, getSiloV2MarketEntry, getSiloV3MarketEntry, dolomiteEmode, curvanceConfigFor, aaveTokens, morphoTypeVaults, listaCollateralProvider, eulerConfigs, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxV3LenderKey, midnightMarketsByChain, twyneConfigFor, twyneChainData, parseAaveV4SpokeLenderKey, fraxlendPairsFor, gearboxAccountCompressor, compoundV2Tokens, exactlyMarketsByChain, aaveOraclesConfig, compoundV2Oracles, compoundV2TokenArray, compoundV2OraclesConfig, compoundV3OraclesData, compoundV3BaseData, compoundV2Reserves, aaveV4Oracles } from '@1delta/data-sdk';
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, skyLendersByChain, frankencoinLendersByChain, resupplyLendersByChain, curvanceLendersByChain, fraxlendLendersByChain, twyneLendersByChain, tellerConfigByChain, tellerPoolsByChain, termMaxConfigByChain, aaveReserves, compoundV3Reserves, liquityConfigFor, liquityBranchesByChain, riverConfigFor, riverChainData, termMaxApiBaseUrl, termMaxLenderKey, inverseConfigFor, inverseChainData, llamaLendConfigFor, llamaLendChainData, llamaLendApiBaseUrl, resupplyConfigFor, fraxlendConfigFor, dssConfigFor, dssChainData, dssDebtToken, dssDebtJoin, frankencoinConfigFor, frankencoinChainData, aaveOracles, morphoTypeMarkets, resolveMidnightApiBase, resolveTermApiBase, llamaLendMarketByVault, getSiloV2MarketEntry, getSiloV3MarketEntry, dolomiteEmode, curvanceConfigFor, aaveTokens, morphoTypeVaults, listaCollateralProvider, eulerConfigs, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxV3LenderKey, midnightMarketsByChain, twyneConfigFor, twyneChainData, parseAaveV4SpokeLenderKey, fraxlendPairsFor, gearboxAccountCompressor, compoundV2Tokens, exactlyMarketsByChain, aaveOraclesConfig, compoundV2Oracles, compoundV2TokenArray, compoundV2OraclesConfig, compoundV3OraclesData, compoundV3BaseData, compoundV2Reserves, aaveV4Oracles } from '@1delta/data-sdk';
7
7
  import lodash from 'lodash';
8
8
  import { Chain } from '@1delta/chain-registry';
9
9
  import { multicallRetryUniversal, getEvmClient, getEvmChain, getEvmClientUniversal } from '@1delta/providers';
@@ -6439,6 +6439,9 @@ var getLendersForChain = (c) => {
6439
6439
  for (const l of fraxlendLendersByChain(c)) {
6440
6440
  lenders.push(l);
6441
6441
  }
6442
+ for (const l of twyneLendersByChain(c)) {
6443
+ lenders.push(l);
6444
+ }
6442
6445
  if (tellerConfigByChain(c)?.tellerV2 && tellerPoolsByChain(c).length > 0) {
6443
6446
  lenders.push(Lender.TELLER);
6444
6447
  }
@@ -19473,6 +19476,10 @@ function parseVault(vault, chainId, prices, additionalYields, tokenList, liquidi
19473
19476
  }
19474
19477
  return value > 0 ? weighted / value : legs[0]?.rate ?? 0;
19475
19478
  };
19479
+ const smartLegTokens = /* @__PURE__ */ new Set([
19480
+ ...isSmartCol ? colLegs.map((l) => l.token.toLowerCase()) : [],
19481
+ ...isSmartDebt ? debtLegs.map((l) => l.token.toLowerCase()) : []
19482
+ ]);
19476
19483
  const basketSupplyRate = isSmartCol ? basketRate(colLegs) : void 0;
19477
19484
  const basketBorrowRate = isSmartDebt ? basketRate(debtLegs) : void 0;
19478
19485
  const irmTotals = (state, decimals) => state ? {
@@ -19561,7 +19568,7 @@ function parseVault(vault, chainId, prices, additionalYields, tokenList, liquidi
19561
19568
  * NOT the position's rate (use the basket rate); and an exit has to be
19562
19569
  * sized in shares rather than in this token.
19563
19570
  */
19564
- ...isSmartVault ? { autoBalanced: true } : {},
19571
+ ...smartLegTokens.has(leg.token.toLowerCase()) ? { autoBalanced: true } : {},
19565
19572
  fluid: isSmartVault ? {
19566
19573
  vaultType,
19567
19574
  /**
@@ -24644,6 +24651,7 @@ async function fetchTwyneMarkets(lender, chainId) {
24644
24651
  allowFailure: true
24645
24652
  });
24646
24653
  const out = [];
24654
+ const pendingDebtBalances = [];
24647
24655
  markets.forEach((m, i) => {
24648
24656
  const c = i * 8;
24649
24657
  const creditTotalAssets = big3(creditReads[c]);
@@ -24657,6 +24665,7 @@ async function fetchTwyneMarkets(lender, chainId) {
24657
24665
  let externalSupplyRate;
24658
24666
  let externalBorrowRate;
24659
24667
  let externalBorrowLiquidity;
24668
+ let debtAToken;
24660
24669
  if (aaveIdx >= 0) {
24661
24670
  const emode = aaveEmode[aaveIdx];
24662
24671
  const collReserve = aaveReserves6[aaveIdx * 2];
@@ -24666,6 +24675,12 @@ async function fetchTwyneMarkets(lender, chainId) {
24666
24675
  externalSupplyRate = Number(collReserve.currentLiquidityRate) / RAY4;
24667
24676
  if (ok(debtReserve) && debtReserve?.currentVariableBorrowRate !== void 0)
24668
24677
  externalBorrowRate = Number(debtReserve.currentVariableBorrowRate) / RAY4;
24678
+ if (ok(debtReserve)) {
24679
+ const virtual = big3(debtReserve.virtualUnderlyingBalance);
24680
+ if (virtual !== void 0 && virtual > 0n) externalBorrowLiquidity = virtual;
24681
+ else if (typeof debtReserve.aTokenAddress === "string")
24682
+ debtAToken = debtReserve.aTokenAddress;
24683
+ }
24669
24684
  } else if (eulerIdx >= 0) {
24670
24685
  const e = eulerIdx * 7;
24671
24686
  externalLiqLtv = big3(eulerReads[e]);
@@ -24682,7 +24697,10 @@ async function fetchTwyneMarkets(lender, chainId) {
24682
24697
  }
24683
24698
  }
24684
24699
  const s = i * 3;
24685
- if (externalBorrowLiquidity === void 0) externalBorrowLiquidity = big3(scaleReads[s + 2]);
24700
+ if (externalBorrowLiquidity === void 0 && !debtAToken) {
24701
+ externalBorrowLiquidity = big3(scaleReads[s + 2]);
24702
+ }
24703
+ if (debtAToken) pendingDebtBalances.push({ index: out.length, token: m.targetAsset, holder: debtAToken });
24686
24704
  out.push({
24687
24705
  market: m,
24688
24706
  creditTotalAssets,
@@ -24708,6 +24726,22 @@ async function fetchTwyneMarkets(lender, chainId) {
24708
24726
  paused
24709
24727
  });
24710
24728
  });
24729
+ if (pendingDebtBalances.length > 0) {
24730
+ const balances = await multicallRetryUniversal({
24731
+ chain: chainId,
24732
+ calls: pendingDebtBalances.map((p) => ({
24733
+ address: p.token,
24734
+ name: "balanceOf",
24735
+ args: [p.holder]
24736
+ })),
24737
+ abi: ERC20_ABI,
24738
+ allowFailure: true
24739
+ });
24740
+ pendingDebtBalances.forEach((p, i) => {
24741
+ const v = big3(balances[i]);
24742
+ if (v !== void 0 && out[p.index]) out[p.index].externalBorrowLiquidity = v;
24743
+ });
24744
+ }
24711
24745
  if (out.length === 0) return void 0;
24712
24746
  return { lender, chainId, config, markets: out };
24713
24747
  }
@@ -55610,16 +55644,116 @@ var fetchGearboxV3Pools = async (chainId, multicallRetry, prices = {}, tokenList
55610
55644
  return converter(raw) ?? {};
55611
55645
  };
55612
55646
 
55647
+ // src/vaults/curatorName.ts
55648
+ var STOP_WORDS = /* @__PURE__ */ new Set([
55649
+ "vault",
55650
+ "vaults",
55651
+ "savings",
55652
+ "yield",
55653
+ "exclusive",
55654
+ "dao",
55655
+ "x",
55656
+ "pt",
55657
+ "prime",
55658
+ "staked",
55659
+ "core",
55660
+ "rwa",
55661
+ "lista",
55662
+ // protocol brand; a bare "Lista …" vault has no distinct curator
55663
+ "moolah"
55664
+ ]);
55665
+ var ASSET_TOKENS = /* @__PURE__ */ new Set([
55666
+ "usdc",
55667
+ "usdt",
55668
+ "usd1",
55669
+ "usds",
55670
+ "dai",
55671
+ "usde",
55672
+ "susde",
55673
+ "lisusd",
55674
+ "gho",
55675
+ "frax",
55676
+ "pyusd",
55677
+ "rusd",
55678
+ "usr",
55679
+ "rlp",
55680
+ "eth",
55681
+ "weth",
55682
+ "wsteth",
55683
+ "reth",
55684
+ "steth",
55685
+ "cbeth",
55686
+ "weeth",
55687
+ "ezeth",
55688
+ "btc",
55689
+ "wbtc",
55690
+ "btcb",
55691
+ "cbbtc",
55692
+ "tbtc",
55693
+ "xaut",
55694
+ "xrp",
55695
+ "bnb",
55696
+ "wbnb",
55697
+ "sol",
55698
+ "op",
55699
+ "arb",
55700
+ "avax",
55701
+ "pol",
55702
+ "matic",
55703
+ "sei",
55704
+ "celo",
55705
+ "hype",
55706
+ "plume",
55707
+ "s"
55708
+ ]);
55709
+ var isAssetToken = (tl, asset) => {
55710
+ if (asset && tl === asset || ASSET_TOKENS.has(tl) || tl.includes("usd")) {
55711
+ return true;
55712
+ }
55713
+ const unwrapped = tl.replace(/^(cb|wst|ws|w|st)/, "");
55714
+ return unwrapped !== tl && ASSET_TOKENS.has(unwrapped);
55715
+ };
55716
+ function curatorNameFromVaultName(name, assetSymbol) {
55717
+ if (!name) return void 0;
55718
+ if (/\b(test|testing|fake)\b/i.test(name)) return void 0;
55719
+ const by = /\bby\s+(.{2,})$/i.exec(name);
55720
+ if (by) {
55721
+ const suffix = by[1].trim().split(/\s+/).slice(0, 3).join(" ");
55722
+ if (suffix.length >= 2 && !isAssetToken(suffix.toLowerCase(), (assetSymbol ?? "").toLowerCase())) {
55723
+ return suffix;
55724
+ }
55725
+ }
55726
+ const cleaned = name.replace(/\([^)]*\)\s*$/, "").split(":")[0].trim();
55727
+ if (!cleaned) return void 0;
55728
+ const tokens = cleaned.split(/[\s/]+/).flatMap((t) => t.split("-")).filter(Boolean);
55729
+ const asset = (assetSymbol ?? "").toLowerCase();
55730
+ const out = [];
55731
+ let terminated = false;
55732
+ for (const tok of tokens) {
55733
+ const tl = tok.toLowerCase();
55734
+ if (STOP_WORDS.has(tl) || isAssetToken(tl, asset)) {
55735
+ terminated = true;
55736
+ break;
55737
+ }
55738
+ out.push(tok);
55739
+ if (out.length >= 3) break;
55740
+ }
55741
+ if (!terminated || out.length === tokens.length) return void 0;
55742
+ const curator = out.join(" ").trim();
55743
+ return curator.length >= 2 ? curator : void 0;
55744
+ }
55745
+
55613
55746
  // src/vaults/morpho/fetchFromApi.ts
55614
55747
  var MORPHO_SHARE_DECIMALS = 18;
55615
55748
  var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
55616
55749
  var PAGE_SIZE2 = 200;
55750
+ var MIN_TOTAL_ASSETS_USD = 100;
55617
55751
  var vaultsQuery = (first, skip, chainId) => `
55618
55752
  query GetVaults {
55619
55753
  vaults(
55620
55754
  first: ${first},
55621
55755
  skip: ${skip},
55622
- where: { chainId_in: [${chainId}], listed: true },
55756
+ where: { chainId_in: [${chainId}], totalAssetsUsd_gte: ${MIN_TOTAL_ASSETS_USD} },
55623
55757
  orderBy: TotalAssetsUsd,
55624
55758
  orderDirection: Desc
55625
55759
  ) {
@@ -55738,6 +55872,7 @@ function parseVault2(v, chainId, prices, tokenList) {
55738
55872
  const totalAssetsUsd = apiTotalAssetsUsd || totalAssetsFormatted * resolvedPriceUsd;
55739
55873
  const curators = parseCurators(state.curators);
55740
55874
  const primaryCurator = curators?.[0];
55875
+ const curatorName = primaryCurator?.name ?? curatorNameFromVaultName(v.name, v.asset?.symbol ?? assetMeta?.symbol);
55741
55876
  return {
55742
55877
  address,
55743
55878
  underlying: assetAddr,
@@ -55745,7 +55880,7 @@ function parseVault2(v, chainId, prices, tokenList) {
55745
55880
  name: v.name ?? "",
55746
55881
  displayName: composeVaultDisplayName(
55747
55882
  "Morpho",
55748
- primaryCurator?.name,
55883
+ curatorName,
55749
55884
  assetMeta,
55750
55885
  v.name ?? void 0
55751
55886
  ),
@@ -55758,11 +55893,13 @@ function parseVault2(v, chainId, prices, tokenList) {
55758
55893
  depositRate: supplyRate + rewardsRate,
55759
55894
  fee: Number(state.fee ?? 0) * 100,
55760
55895
  timelock: Number(state.timelock ?? 0),
55896
+ // Since Aug-2026 `listed` means "listed in the Morpho app" (a far smaller
55897
+ // set than the old curated flag) — informational only, never a row filter.
55761
55898
  whitelisted: v.whitelisted ?? true,
55762
55899
  owner: state.owner?.toLowerCase() || void 0,
55763
55900
  curator: state.curator?.toLowerCase() || void 0,
55764
55901
  guardian: state.guardian?.toLowerCase() || void 0,
55765
- curatorName: primaryCurator?.name,
55902
+ curatorName,
55766
55903
  curatorImage: primaryCurator?.image,
55767
55904
  curators,
55768
55905
  asset: assetMeta,
@@ -55774,7 +55911,7 @@ function parseVault2(v, chainId, prices, tokenList) {
55774
55911
  liquidityUsd: liquidityUsd || liquidityFormatted * resolvedPriceUsd
55775
55912
  };
55776
55913
  }
55777
- async function fetchMorphoVaultsFromApi(chainId, prices = {}, tokenList = {}, maxItems = 600) {
55914
+ async function fetchMorphoVaultsFromApi(chainId, prices = {}, tokenList = {}, maxItems = 1e3) {
55778
55915
  const out = {};
55779
55916
  let skip = 0;
55780
55917
  while (skip < maxItems) {
@@ -56415,107 +56552,6 @@ var fetchMorphoVaults = async (chainId, multicallRetry, prices = {}, tokenList =
56415
56552
  }
56416
56553
  return fetchMorphoVaultsFromApi(chainId, prices, tokenList);
56417
56554
  };
56418
-
56419
- // src/vaults/curatorName.ts
56420
- var STOP_WORDS = /* @__PURE__ */ new Set([
56421
- "vault",
56422
- "vaults",
56423
- "savings",
56424
- "yield",
56425
- "exclusive",
56426
- "dao",
56427
- "x",
56428
- "pt",
56429
- "prime",
56430
- "staked",
56431
- "core",
56432
- "rwa",
56433
- "lista",
56434
- // protocol brand; a bare "Lista …" vault has no distinct curator
56435
- "moolah"
56436
- ]);
56437
- var ASSET_TOKENS = /* @__PURE__ */ new Set([
56438
- "usdc",
56439
- "usdt",
56440
- "usd1",
56441
- "usds",
56442
- "dai",
56443
- "usde",
56444
- "susde",
56445
- "lisusd",
56446
- "gho",
56447
- "frax",
56448
- "pyusd",
56449
- "rusd",
56450
- "usr",
56451
- "rlp",
56452
- "eth",
56453
- "weth",
56454
- "wsteth",
56455
- "reth",
56456
- "steth",
56457
- "cbeth",
56458
- "weeth",
56459
- "ezeth",
56460
- "btc",
56461
- "wbtc",
56462
- "btcb",
56463
- "cbbtc",
56464
- "tbtc",
56465
- "xaut",
56466
- "xrp",
56467
- "bnb",
56468
- "wbnb",
56469
- "sol",
56470
- "op",
56471
- "arb",
56472
- "avax",
56473
- "pol",
56474
- "matic",
56475
- "sei",
56476
- "celo",
56477
- "hype",
56478
- "plume",
56479
- "s"
56480
- ]);
56481
- var isAssetToken = (tl, asset) => {
56482
- if (asset && tl === asset || ASSET_TOKENS.has(tl) || tl.includes("usd")) {
56483
- return true;
56484
- }
56485
- const unwrapped = tl.replace(/^(cb|wst|ws|w|st)/, "");
56486
- return unwrapped !== tl && ASSET_TOKENS.has(unwrapped);
56487
- };
56488
- function curatorNameFromVaultName(name, assetSymbol) {
56489
- if (!name) return void 0;
56490
- if (/\b(test|testing|fake)\b/i.test(name)) return void 0;
56491
- const by = /\bby\s+(.{2,})$/i.exec(name);
56492
- if (by) {
56493
- const suffix = by[1].trim().split(/\s+/).slice(0, 3).join(" ");
56494
- if (suffix.length >= 2 && !isAssetToken(suffix.toLowerCase(), (assetSymbol ?? "").toLowerCase())) {
56495
- return suffix;
56496
- }
56497
- }
56498
- const cleaned = name.replace(/\([^)]*\)\s*$/, "").split(":")[0].trim();
56499
- if (!cleaned) return void 0;
56500
- const tokens = cleaned.split(/[\s/]+/).flatMap((t) => t.split("-")).filter(Boolean);
56501
- const asset = (assetSymbol ?? "").toLowerCase();
56502
- const out = [];
56503
- let terminated = false;
56504
- for (const tok of tokens) {
56505
- const tl = tok.toLowerCase();
56506
- if (STOP_WORDS.has(tl) || isAssetToken(tl, asset)) {
56507
- terminated = true;
56508
- break;
56509
- }
56510
- out.push(tok);
56511
- if (out.length >= 3) break;
56512
- }
56513
- if (!terminated || out.length === tokens.length) return void 0;
56514
- const curator = out.join(" ").trim();
56515
- return curator.length >= 2 ? curator : void 0;
56516
- }
56517
-
56518
- // src/vaults/morpho/fetchListaFromChain.ts
56519
56555
  var { chunk: chunk6 } = lodash;
56520
56556
  var LISTA_PROTOCOL = "LISTA_DAO";
56521
56557
  var LISTA_LENS_ABI = parseAbi([
@@ -72451,7 +72487,10 @@ var twyneAdapter = {
72451
72487
  description: [
72452
72488
  `Your collateral stays in ${externalName}; Twyne only reserves other lenders\u2019 unused borrowing power so the same collateral supports a larger loan.`,
72453
72489
  bandNote,
72454
- creditApr != null ? `On top of ${externalName}\u2019s borrow rate you pay ${creditApr.toFixed(3)} % a year on the RESERVED CREDIT only \u2014 not on your debt \u2014 and it is charged in the collateral asset.` : void 0
72490
+ creditApr != null ? `On top of ${externalName}\u2019s borrow rate you pay ${creditApr.toFixed(3)} % a year on the RESERVED CREDIT only \u2014 not on your debt \u2014 and it is charged in the collateral asset.` : void 0,
72491
+ // The loan is perpetual; the COLLATERAL is what expires. Said here
72492
+ // rather than in `maturity`, which would mis-type the loan itself.
72493
+ maturity != null && matured ? "This market\u2019s collateral has already matured. Existing positions can still be repaid and closed, but nothing new should be opened against it." : maturity != null ? `Your loan has no end date, but the collateral is a fixed-maturity token that stops accreting on ${new Date(maturity * 1e3).toISOString().slice(0, 10)}, and ${externalName}\u2019s risk parameters move as that date approaches.` : void 0
72455
72494
  ].filter(Boolean).join(" ")
72456
72495
  },
72457
72496
  availability: {
@@ -72485,21 +72524,20 @@ var twyneAdapter = {
72485
72524
  description: `Twyne liquidates first, above your chosen LTV. ${externalName}\u2019s own threshold of ${pct2(externalLiqLtv)} is the backstop, and reaching it is the bad case.`
72486
72525
  } : {}
72487
72526
  },
72488
- // The collateral has a maturity even though the loan does not — a PT
72489
- // stops accreting at expiry and the external threshold ramps out from
72490
- // under it, so this is a date the borrower has to act on.
72491
- ...maturity ? {
72492
- maturity: {
72493
- kind: "fixed-date",
72494
- maturity,
72495
- maturityIso: new Date(maturity * 1e3).toISOString(),
72496
- ...matured ? {
72497
- description: "This market\u2019s collateral has already matured. Existing positions can still be repaid and closed, but no new borrowing should be opened against it."
72498
- } : {
72499
- description: "The collateral is a fixed-maturity token. The loan itself has no end date, but the collateral stops accreting at maturity and the underlying market\u2019s risk parameters move as that date approaches."
72500
- }
72501
- }
72502
- } : {}
72527
+ // THE LOAN IS PERPETUAL. Only the COLLATERAL expires.
72528
+ //
72529
+ // The first version of this adapter put the PT's expiry on
72530
+ // `borrow.maturity` with `kind: 'fixed-date'`, and that is wrong in a
72531
+ // way that shows up immediately in a UI: every consumer reads that field
72532
+ // as "this is a fixed-TERM loan", so the market rendered with a `Fixed`
72533
+ // borrow-rate badge and no APR — a fixed-rate product Twyne does not
72534
+ // offer. A Twyne loan has no end date, no rollover and no settlement;
72535
+ // it accrues at the external market's variable rate until repaid.
72536
+ //
72537
+ // What the collateral's expiry actually means is carried where it
72538
+ // belongs: `twyne.collateralMaturity` / `collateralMatured` on the
72539
+ // market descriptor (computed at read time), and stated in prose here.
72540
+ maturity: { kind: "perpetual" }
72503
72541
  }
72504
72542
  };
72505
72543
  }
@@ -73104,6 +73142,10 @@ function resolveBasket(row) {
73104
73142
  const f = row.fluid;
73105
73143
  if (f && f.isSmartCol !== true) return void 0;
73106
73144
  const legs = (f?.collateralPair ?? []).map((a) => addr3(a)).filter((a) => !!a).map((address) => ({ address }));
73145
+ if (legs.length > 0) {
73146
+ const rowAsset = addr3(row.underlying) ?? addr3(row.asset?.address);
73147
+ if (!rowAsset || !legs.some((l) => l.address === rowAsset)) return void 0;
73148
+ }
73107
73149
  return {
73108
73150
  // Fluid emits one row PER LEG of the pool, so this row is a leg and a
73109
73151
  // consumer that sums the listing without deduping counts the position