@1delta/margin-fetcher 5.0.12 → 5.0.13

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.js CHANGED
@@ -23006,6 +23006,12 @@ var bandGrid = (m) => {
23006
23006
  const set = /* @__PURE__ */ new Set([...BANDS_ENDPOINTS, bandsFor(m)]);
23007
23007
  return [...set].sort((a, b) => a - b);
23008
23008
  };
23009
+ var MIN_BANDS = 4;
23010
+ var MAX_BANDS = 50;
23011
+ var LTV_CURVE_BANDS = Array.from(
23012
+ { length: MAX_BANDS - MIN_BANDS + 1 },
23013
+ (_3, i) => MIN_BANDS + i
23014
+ );
23009
23015
  var maxBorrowableCall = (m, oneUnit, n) => m.version === 1 ? {
23010
23016
  address: m.controller,
23011
23017
  name: "max_borrowable",
@@ -23025,7 +23031,8 @@ var buildBandLtv = (market) => {
23025
23031
  // negligible at any realistic position size and converges as it grows.
23026
23032
  collateral: 10n ** BigInt(market.collateralDecimals + 3),
23027
23033
  collateralDecimals: market.collateralDecimals,
23028
- bandCounts: bandGrid(market)
23034
+ // The FULL domain, not the multicall grid — see `LTV_CURVE_BANDS`.
23035
+ bandCounts: LTV_CURVE_BANDS
23029
23036
  });
23030
23037
  return Object.keys(curve).length > 0 ? curve : null;
23031
23038
  } catch {
@@ -46177,32 +46184,85 @@ var upshiftFetcher = {
46177
46184
  }
46178
46185
  };
46179
46186
 
46180
- // src/yields/yieldTypes.ts
46181
- var addressToAssetYearn = {
46182
- "0xe007ca01894c863d7898045ed5a3b4abf0b18f37": "vbETH yVault::yvvbETH",
46183
- "0x80c34bd3a3569e126e7055831036aa7b212cb159": "vbUSDC yVault::yvvbUSDC",
46184
- "0x9a6bd7b6fd5c4f87eb66356441502fc7dcdd185b": "vbUSDT yVault::yvvbUSDT",
46185
- "0xaa0362ecc584b985056e47812931270b99c91f9d": "vbWBTC yVault::yvvbWBTC",
46186
- "0x93fec6639717b6215a48e5a72a162c50dcc40d68": "vbAUSD yVault::yvvbAUSD"
46187
- };
46187
+ // src/vaults/yearn/api.ts
46188
+ var YEARN_YDAEMON_BASE = "https://ydaemon.yearn.fi";
46189
+ var YEARN_CHAIN_IDS = /* @__PURE__ */ new Set([
46190
+ "1",
46191
+ // Ethereum
46192
+ "137",
46193
+ // Polygon
46194
+ "8453",
46195
+ // Base
46196
+ "42161",
46197
+ // Arbitrum
46198
+ "100",
46199
+ // Gnosis
46200
+ "146",
46201
+ // Sonic
46202
+ "747474"
46203
+ // Katana
46204
+ ]);
46205
+ var hasYearnVaults = (chainId) => YEARN_CHAIN_IDS.has(String(chainId));
46206
+ var isYearnV3 = (v) => typeof v?.version === "string" && v.version.startsWith("3.");
46207
+ var PAGE_SIZE = 2e3;
46208
+ async function fetchYearnApiVaults(chainId) {
46209
+ const out = [];
46210
+ const seen = /* @__PURE__ */ new Set();
46211
+ for (let page = 1; page <= 20; page++) {
46212
+ const url = `${YEARN_YDAEMON_BASE}/${chainId}/vaults/all?limit=${PAGE_SIZE}&page=${page}`;
46213
+ const response = await fetch(url);
46214
+ if (!response.ok) {
46215
+ throw new Error(
46216
+ `Yearn yDaemon API failed: ${response.status} - ${response.statusText}`
46217
+ );
46218
+ }
46219
+ const body = await response.text();
46220
+ if (!body.trim()) break;
46221
+ const items = JSON.parse(body);
46222
+ if (!Array.isArray(items)) break;
46223
+ for (const v of items) {
46224
+ const address = (v?.address ?? "").toLowerCase();
46225
+ if (!address || seen.has(address)) continue;
46226
+ if (isYearnV3(v) && v.endorsed) {
46227
+ seen.add(address);
46228
+ out.push(v);
46229
+ }
46230
+ }
46231
+ if (items.length < PAGE_SIZE) break;
46232
+ }
46233
+ return out;
46234
+ }
46188
46235
 
46189
- // src/yields/intrinsic/fetchers/yearnKatana.ts
46190
- var YEARN_KATANA_URL = "https://katana-apr-service.vercel.app/api/vaults";
46191
- var yearnKatanaFetcher = {
46192
- label: "YEARN_KATANA",
46236
+ // src/yields/intrinsic/fetchers/yearn.ts
46237
+ var toPct = (v) => typeof v === "number" && Number.isFinite(v) ? v * 100 : null;
46238
+ var yearnVaultApr = (v) => toPct(v.apr?.netAPR) ?? toPct(v.apr?.forwardAPR?.netAPR);
46239
+ var yearnFetcher = {
46240
+ label: "YEARN",
46193
46241
  fetch: async () => {
46194
- const res = await fetch(
46195
- YEARN_KATANA_URL
46196
- ).then((r) => r.json());
46197
- let map = {};
46198
- Object.entries(res).forEach(([addr2, data]) => {
46199
- const key2 = addressToAssetYearn[addr2.toLowerCase()];
46200
- if (key2) {
46201
- const apr = ((data.apr.netAPR ?? 0) + (data.apr.extra?.extrinsicYield ?? 0) + (data.apr.extra?.katanaNativeYield ?? 0)) * 100;
46202
- map[key2] = apr;
46203
- }
46204
- });
46205
- return map;
46242
+ const chainIds = [...YEARN_CHAIN_IDS];
46243
+ const lists = await getListsMulti(chainIds);
46244
+ const perChain = await Promise.all(
46245
+ chainIds.map(async (chainId) => {
46246
+ const out = {};
46247
+ let vaults = [];
46248
+ try {
46249
+ vaults = await fetchYearnApiVaults(chainId);
46250
+ } catch (e) {
46251
+ console.error(`Error fetching YEARN vaults on chain ${chainId}:`, e);
46252
+ return out;
46253
+ }
46254
+ for (const v of vaults) {
46255
+ const address = (v.address ?? "").toLowerCase();
46256
+ const assetKey = lists[chainId]?.list?.[address]?.assetGroup;
46257
+ if (!assetKey) continue;
46258
+ const apr = yearnVaultApr(v);
46259
+ if (apr == null) continue;
46260
+ out[assetKey] = apr;
46261
+ }
46262
+ return out;
46263
+ })
46264
+ );
46265
+ return Object.assign({}, ...perChain);
46206
46266
  }
46207
46267
  };
46208
46268
 
@@ -46266,7 +46326,9 @@ var LENDING_ONLY_FETCHERS = [
46266
46326
  nestFetcher,
46267
46327
  accountableFetcher,
46268
46328
  upshiftFetcher,
46269
- yearnKatanaFetcher,
46329
+ // Every endorsed Yearn V3 vault on every Yearn chain (Katana included —
46330
+ // it superseded the Katana-only fetcher, which double-counted).
46331
+ yearnFetcher,
46270
46332
  // DefiLlama-mapped intrinsic yields for classifier assets lacking a bespoke source
46271
46333
  lsethFetcher,
46272
46334
  tethFetcher,
@@ -46367,6 +46429,76 @@ function extractRewardBreakdowns(opp) {
46367
46429
  distributionType: b.distributionType
46368
46430
  }));
46369
46431
  }
46432
+ function unixSeconds(v) {
46433
+ if (v === void 0 || v === null) return void 0;
46434
+ const n = typeof v === "number" ? v : Number(v);
46435
+ return Number.isFinite(n) && n > 0 ? n : void 0;
46436
+ }
46437
+ function buildMerklStreams(opp, side, totalApr, source) {
46438
+ const rewardBreakdowns = opp.rewardsRecord?.breakdowns ?? [];
46439
+ const endsAt = unixSeconds(opp.earliestCampaignEnd ?? opp.latestCampaignEnd);
46440
+ const startsAt = unixSeconds(
46441
+ opp.earliestCampaignStart ?? opp.latestCampaignStart
46442
+ );
46443
+ const base = {
46444
+ side,
46445
+ kind: "token",
46446
+ claim: "merkl",
46447
+ endsAt,
46448
+ startsAt
46449
+ };
46450
+ if (!rewardBreakdowns.length) {
46451
+ return [{ ...base, apr: totalApr, token: void 0, source }];
46452
+ }
46453
+ const aprByCampaign = /* @__PURE__ */ new Map();
46454
+ for (const b of opp.aprRecord?.breakdowns ?? []) {
46455
+ if (b?.identifier && Number.isFinite(b.value))
46456
+ aprByCampaign.set(b.identifier.toLowerCase(), b.value);
46457
+ }
46458
+ const totalDaily = rewardBreakdowns.reduce((s, b) => s + (b.value || 0), 0);
46459
+ return rewardBreakdowns.map((b) => {
46460
+ const campaignApr = b.onChainCampaignId ? aprByCampaign.get(b.onChainCampaignId.toLowerCase()) : void 0;
46461
+ const apr = campaignApr ?? (totalDaily > 0 ? totalApr * (b.value || 0) / totalDaily : totalApr / rewardBreakdowns.length);
46462
+ return {
46463
+ ...base,
46464
+ apr,
46465
+ token: {
46466
+ address: b.token.address.toLowerCase(),
46467
+ chainId: String(opp.chainId),
46468
+ symbol: b.token.symbol,
46469
+ decimals: b.token.decimals,
46470
+ logoURI: b.token.icon,
46471
+ priceUsd: b.token.price ?? void 0
46472
+ },
46473
+ dailyRewardsUsd: b.value,
46474
+ source: {
46475
+ ...source,
46476
+ refs: {
46477
+ ...source.refs ?? {},
46478
+ ...b.onChainCampaignId ? { onChainCampaignId: b.onChainCampaignId } : {},
46479
+ ...b.campaignId ? { campaignId: b.campaignId } : {},
46480
+ ...b.distributionType ? { distributionType: b.distributionType } : {}
46481
+ }
46482
+ }
46483
+ };
46484
+ });
46485
+ }
46486
+ function merklSourceRef(opp, merklProtocolId) {
46487
+ const protocolName = opp.protocol?.name ?? merklProtocolId;
46488
+ return {
46489
+ id: `merkl:${merklProtocolId}`,
46490
+ label: `Merkl \xB7 ${protocolName}`,
46491
+ link: getMerkleUrl(opp),
46492
+ platform: "merkl",
46493
+ refs: {
46494
+ opportunityId: opp.identifier,
46495
+ opportunityType: opp.type,
46496
+ chain: opp.chain?.name ?? String(opp.chainId),
46497
+ ...opp.name ? { opportunityName: opp.name } : {},
46498
+ ...opp.liveCampaigns ? { liveCampaigns: opp.liveCampaigns } : {}
46499
+ }
46500
+ };
46501
+ }
46370
46502
  function resolveUnderlyingFromOpportunity(opp) {
46371
46503
  if (!opp.tokens?.length) return void 0;
46372
46504
  if (opp.depositUrl) {
@@ -46432,6 +46564,11 @@ function createMerklRewardFetcher(config) {
46432
46564
  );
46433
46565
  const breakdowns = extractRewardBreakdowns(opp);
46434
46566
  reward.link = getMerkleUrl(opp);
46567
+ const source = merklSourceRef(opp, config.merklProtocolId);
46568
+ if (!reward.streams) reward.streams = [];
46569
+ reward.streams.push(
46570
+ ...buildMerklStreams(opp, bucket, opp.apr, source)
46571
+ );
46435
46572
  if (bucket === "deposit") {
46436
46573
  reward.deposit += opp.apr;
46437
46574
  reward.additionalDepositData.push(...breakdowns);
@@ -46636,8 +46773,21 @@ var ABI = [
46636
46773
  stateMutability: "view",
46637
46774
  inputs: [],
46638
46775
  outputs: [{ type: "uint8" }]
46776
+ },
46777
+ {
46778
+ name: "symbol",
46779
+ type: "function",
46780
+ stateMutability: "view",
46781
+ inputs: [],
46782
+ outputs: [{ type: "string" }]
46639
46783
  }
46640
46784
  ];
46785
+ var DTRINITY_SOURCE = {
46786
+ id: "dtrinity:rebate",
46787
+ label: "dTRINITY borrow rebate",
46788
+ link: "https://docs.dtrinity.org/protocol-components/dlend",
46789
+ platform: "protocol"
46790
+ };
46641
46791
  var isZero = (a) => !a || a.toLowerCase() === ZERO_ADDRESS5;
46642
46792
  var scale = (raw, decimals) => Number(raw) / 10 ** decimals;
46643
46793
  function emptyReward2() {
@@ -46751,19 +46901,25 @@ async function fetchForPool(chainId, lender, pool, nowSeconds) {
46751
46901
  calls: pairs.flatMap((p) => [
46752
46902
  call(p.leg.controller, "getRewardsData", [p.leg.balanceToken, p.reward]),
46753
46903
  call(p.reward, "decimals"),
46754
- call(oracle, "getAssetPrice", [p.reward])
46904
+ call(oracle, "getAssetPrice", [p.reward]),
46905
+ call(p.reward, "symbol")
46755
46906
  ]),
46756
46907
  allowFailure: false
46757
46908
  });
46909
+ const STRIDE = 4;
46758
46910
  const result = {};
46759
46911
  pairs.forEach((p, i) => {
46760
- const data = round5[i * 3];
46912
+ const data = round5[i * STRIDE];
46761
46913
  if (!data) return;
46914
+ const rewardDecimals = Number(round5[i * STRIDE + 1] ?? 18);
46915
+ const rewardPrice = Number(round5[i * STRIDE + 2] ?? 0n);
46916
+ const rewardSymbol = round5[i * STRIDE + 3];
46917
+ const distributionEnd = Number(data[3] ?? 0);
46762
46918
  const apr = rebateApr({
46763
46919
  emissionPerSecond: BigInt(data[1] ?? 0),
46764
46920
  distributionEnd: BigInt(data[3] ?? 0),
46765
- rewardDecimals: Number(round5[i * 3 + 1] ?? 18),
46766
- rewardPrice: Number(round5[i * 3 + 2] ?? 0n),
46921
+ rewardDecimals,
46922
+ rewardPrice,
46767
46923
  balanceTotalSupply: BigInt(p.leg.totalSupply ?? 0),
46768
46924
  balanceDecimals: p.leg.decimals,
46769
46925
  assetPrice: priceByAsset[p.leg.asset] ?? 0,
@@ -46778,9 +46934,34 @@ async function fetchForPool(chainId, lender, pool, nowSeconds) {
46778
46934
  const breakdown = {
46779
46935
  tokenAddress: p.reward.toLowerCase(),
46780
46936
  emissionPerSecond: String(data[1]),
46781
- distributionEnd: Number(data[3]),
46937
+ distributionEnd,
46782
46938
  distributionType: "aave-incentives"
46783
46939
  };
46940
+ if (!entry.streams) entry.streams = [];
46941
+ entry.streams.push({
46942
+ side: p.leg.bucket,
46943
+ apr,
46944
+ kind: "token",
46945
+ token: {
46946
+ address: p.reward.toLowerCase(),
46947
+ chainId,
46948
+ symbol: rewardSymbol,
46949
+ decimals: rewardDecimals,
46950
+ // The pool's AaveOracle is 1e8-based on every dLEND deployment.
46951
+ priceUsd: rewardPrice > 0 ? rewardPrice / 1e8 : void 0
46952
+ },
46953
+ source: {
46954
+ ...DTRINITY_SOURCE,
46955
+ refs: {
46956
+ incentivesController: p.leg.controller.toLowerCase(),
46957
+ balanceToken: p.leg.balanceToken.toLowerCase(),
46958
+ emissionPerSecond: String(data[1])
46959
+ }
46960
+ },
46961
+ claim: "accrual",
46962
+ endsAt: distributionEnd > 0 ? distributionEnd : void 0,
46963
+ dailyRewardsUsd: rewardPrice > 0 ? Number(data[1] ?? 0) / 10 ** rewardDecimals * 86400 * (rewardPrice / 1e8) : void 0
46964
+ });
46784
46965
  if (p.leg.bucket === "deposit") {
46785
46966
  entry.deposit += apr;
46786
46967
  entry.additionalDepositData.push(breakdown);
@@ -51721,7 +51902,7 @@ var fetchGearboxV3Pools = async (chainId, multicallRetry, prices = {}, tokenList
51721
51902
  // src/vaults/morpho/fetchFromApi.ts
51722
51903
  var MORPHO_SHARE_DECIMALS = 18;
51723
51904
  var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
51724
- var PAGE_SIZE = 200;
51905
+ var PAGE_SIZE2 = 200;
51725
51906
  var vaultsQuery = (first, skip, chainId) => `
51726
51907
  query GetVaults {
51727
51908
  vaults(
@@ -51886,14 +52067,14 @@ async function fetchMorphoVaultsFromApi(chainId, prices = {}, tokenList = {}, ma
51886
52067
  const out = {};
51887
52068
  let skip = 0;
51888
52069
  while (skip < maxItems) {
51889
- const page = await fetchPage(chainId, PAGE_SIZE, skip);
52070
+ const page = await fetchPage(chainId, PAGE_SIZE2, skip);
51890
52071
  if (page.length === 0) break;
51891
52072
  for (const v of page) {
51892
52073
  const parsed = parseVault2(v, chainId, prices, tokenList);
51893
52074
  if (parsed) out[parsed.address] = parsed;
51894
52075
  }
51895
- if (page.length < PAGE_SIZE) break;
51896
- skip += PAGE_SIZE;
52076
+ if (page.length < PAGE_SIZE2) break;
52077
+ skip += PAGE_SIZE2;
51897
52078
  }
51898
52079
  return out;
51899
52080
  }
@@ -59690,7 +59871,7 @@ query LagoonVaults($where: VaultFilterInput!, $first: Int!, $skip: Int!) {
59690
59871
  }
59691
59872
  }
59692
59873
  }`;
59693
- var PAGE_SIZE2 = 200;
59874
+ var PAGE_SIZE3 = 200;
59694
59875
  async function fetchLagoonApiVaults(chainId) {
59695
59876
  const out = [];
59696
59877
  let skip = 0;
@@ -59702,7 +59883,7 @@ async function fetchLagoonApiVaults(chainId) {
59702
59883
  query: VAULTS_QUERY,
59703
59884
  variables: {
59704
59885
  where: { chainId_eq: Number(chainId), isVisible_eq: true },
59705
- first: PAGE_SIZE2,
59886
+ first: PAGE_SIZE3,
59706
59887
  skip
59707
59888
  }
59708
59889
  })
@@ -59720,8 +59901,8 @@ async function fetchLagoonApiVaults(chainId) {
59720
59901
  }
59721
59902
  const items = json?.data?.vaults?.items ?? [];
59722
59903
  out.push(...items);
59723
- if (items.length < PAGE_SIZE2) break;
59724
- skip += PAGE_SIZE2;
59904
+ if (items.length < PAGE_SIZE3) break;
59905
+ skip += PAGE_SIZE3;
59725
59906
  }
59726
59907
  return out;
59727
59908
  }
@@ -59841,7 +60022,7 @@ var Erc4626TotalAssetsAbi = [
59841
60022
 
59842
60023
  // src/vaults/aave-earn/api.ts
59843
60024
  var AAVE_EARN_API_URL = "https://api.v3.aave.com/graphql";
59844
- var PAGE_SIZE3 = "FIFTY";
60025
+ var PAGE_SIZE4 = "FIFTY";
59845
60026
  var VAULT_FIELDS = `
59846
60027
  address
59847
60028
  shareName
@@ -59899,7 +60080,7 @@ async function fetchAaveEarnApiVaults(chainId, targets) {
59899
60080
  for (let page = 0; page < 20; page++) {
59900
60081
  const request = {
59901
60082
  criteria: { ownedBy: [owner] },
59902
- pageSize: PAGE_SIZE3
60083
+ pageSize: PAGE_SIZE4
59903
60084
  };
59904
60085
  if (cursor) request.cursor = cursor;
59905
60086
  const data = await postGraphql(OWNED_VAULTS_QUERY, { request });
@@ -60158,47 +60339,6 @@ var fetchUpshiftVaults = async (chainId, prices = {}, tokenList = {}) => {
60158
60339
  return out;
60159
60340
  };
60160
60341
 
60161
- // src/vaults/yearn/api.ts
60162
- var YEARN_YDAEMON_BASE = "https://ydaemon.yearn.fi";
60163
- var YEARN_CHAIN_IDS = /* @__PURE__ */ new Set([
60164
- "1",
60165
- // Ethereum
60166
- "137",
60167
- // Polygon
60168
- "8453",
60169
- // Base
60170
- "42161",
60171
- // Arbitrum
60172
- "100",
60173
- // Gnosis
60174
- "146",
60175
- // Sonic
60176
- "747474"
60177
- // Katana
60178
- ]);
60179
- var hasYearnVaults = (chainId) => YEARN_CHAIN_IDS.has(String(chainId));
60180
- var isYearnV3 = (v) => typeof v?.version === "string" && v.version.startsWith("3.");
60181
- var PAGE_SIZE4 = 500;
60182
- async function fetchYearnApiVaults(chainId) {
60183
- const out = [];
60184
- for (let page = 1; page <= 20; page++) {
60185
- const url = `${YEARN_YDAEMON_BASE}/${chainId}/vaults/all?limit=${PAGE_SIZE4}&page=${page}`;
60186
- const response = await fetch(url);
60187
- if (!response.ok) {
60188
- throw new Error(
60189
- `Yearn yDaemon API failed: ${response.status} - ${response.statusText}`
60190
- );
60191
- }
60192
- const items = await response.json();
60193
- if (!Array.isArray(items)) break;
60194
- for (const v of items) {
60195
- if (isYearnV3(v) && v.endorsed) out.push(v);
60196
- }
60197
- if (items.length < PAGE_SIZE4) break;
60198
- }
60199
- return out;
60200
- }
60201
-
60202
60342
  // src/vaults/yearn/fetchPublic.ts
60203
60343
  var BRAND3 = "Yearn";
60204
60344
  var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
@@ -60244,7 +60384,7 @@ var asBig = (v) => {
60244
60384
  }
60245
60385
  };
60246
60386
  var num9 = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
60247
- var toPct = (v) => {
60387
+ var toPct2 = (v) => {
60248
60388
  const n = num9(v);
60249
60389
  return n == null ? 0 : n * 100;
60250
60390
  };
@@ -60280,9 +60420,9 @@ function parseVault9(v, chainId, prices, tokenList) {
60280
60420
  const realized = num9(v.apr?.netAPR);
60281
60421
  const forward = num9(v.apr?.forwardAPR?.netAPR);
60282
60422
  const isForwardApr = realized == null && forward != null;
60283
- const supplyRate = toPct(realized ?? forward);
60284
- const rewardsRate = toPct(v.apr?.extra?.stakingRewardsAPR);
60285
- const fee = toPct(v.apr?.fees?.performance);
60423
+ const supplyRate = toPct2(realized ?? forward);
60424
+ const rewardsRate = toPct2(v.apr?.extra?.stakingRewardsAPR);
60425
+ const fee = toPct2(v.apr?.fees?.performance);
60286
60426
  const oracleKey = toOracleKey(assetMeta?.assetGroup) ?? toGenericPriceKey(assetAddr, chainId);
60287
60427
  const apiPrice = num9(v.tvl?.price) ?? 0;
60288
60428
  const priceUsd = prices[oracleKey] ?? prices[assetAddr] ?? apiPrice;
@@ -65066,11 +65206,12 @@ var llamaLendAdapter = {
65066
65206
  /**
65067
65207
  * The band count as an editable TERM, not just a curve to read.
65068
65208
  *
65069
- * `bandLtv` alone cannot drive a control: it is four sampled points,
65070
- * and it is missing on any market whose curve could not be computed.
65071
- * The domain is always known — `MIN_TICKS`/`MAX_TICKS` are 4..50 on
65072
- * both generations — so the control works even where the curve does
65073
- * not.
65209
+ * `bandLtv` alone cannot drive a control: it is missing on any
65210
+ * market whose geometry could not be read. The domain is always
65211
+ * known — `MIN_TICKS`/`MAX_TICKS` are 4..50 on both generations — so
65212
+ * the control works even where the curve does not. Where the curve
65213
+ * IS present it spans the full domain, so a control can show the LTV
65214
+ * for every value it offers.
65074
65215
  *
65075
65216
  * `immutableAfterOpen` is what tells the UI to render this read-only
65076
65217
  * on an existing loan: `_add_collateral_borrow` reuses the tick
@@ -65135,7 +65276,16 @@ var frankencoinAdapter = {
65135
65276
  return {
65136
65277
  // Not a coverage hole: the protocol has no price feed by design.
65137
65278
  oracle: { kind: "none" },
65279
+ supply: {
65280
+ // Collateral inside a position. There is no ZCHF lending deposit at
65281
+ // all — the earn surface is the savings module, a separate provider —
65282
+ // so this side never pays interest.
65283
+ role: "collateral",
65284
+ availability: { opensWith: "both-legs" },
65285
+ counterparty: { kind: "cdp", solvency: "overcollateralized" }
65286
+ },
65138
65287
  borrow: {
65288
+ availability: { opensWith: "both-legs" },
65139
65289
  debtShape: "accruing",
65140
65290
  maturity: expiration > 0 ? {
65141
65291
  kind: "fixed-date",