@1delta/margin-fetcher 5.0.11 → 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 {
@@ -24326,7 +24333,19 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24326
24333
  // this as a number gets NaN and can branch, where "0" would silently
24327
24334
  // become a zero LTV. The real curve is in `llamalend.bandLtv`.
24328
24335
  lltv: ltv !== null ? String(ltv) : "",
24329
- oracle: market.priceOracle ?? market.amm,
24336
+ /**
24337
+ * The AMM, deliberately — LlamaLend's price feed is `price_oracle()` on
24338
+ * the LLAMMA itself, so that is the only address a reader can call.
24339
+ *
24340
+ * This used to read `market.priceOracle ?? market.amm`, but
24341
+ * `priceOracle` was never assigned anywhere in this pipeline (the name
24342
+ * is used elsewhere for the price VALUE, not the contract), so the
24343
+ * fallback was doing all the work. Naming it directly removes the trap:
24344
+ * a Curve `price_oracle_contract` exposes `price()` and NOT
24345
+ * `price_oracle()`, so populating that field would have silently
24346
+ * pointed every oracle reader at an interface it cannot call.
24347
+ */
24348
+ oracle: market.amm,
24330
24349
  irm: market.monetaryPolicy ?? zeroAddress,
24331
24350
  collateralAddress: collAddr,
24332
24351
  loanAddress: loanAddr,
@@ -24382,7 +24401,6 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24382
24401
  */
24383
24402
  amm: market.amm,
24384
24403
  monetaryPolicy: market.monetaryPolicy,
24385
- priceOracle: market.priceOracle,
24386
24404
  /**
24387
24405
  * Curve's deployed v1 leverage zaps and the aggregator routers
24388
24406
  * they are hard-wired to. We route leverage through these rather
@@ -40699,6 +40717,40 @@ var fraxSavingsFetcher = {
40699
40717
  }
40700
40718
  };
40701
40719
 
40720
+ // src/yields/intrinsic/fetchers/binance.ts
40721
+ var HISTORY_URL2 = "https://www.binance.com/bapi/earn/v1/public/pos/cftoken/project/exchange-rate/history";
40722
+ var WBETH = "Wrapped Binance Beacon ETH::wBETH";
40723
+ var LLAMA_POOL = "80b8bf92-b953-4c20-98ea-c9653ef2bb98";
40724
+ var DAY_MS = 864e5;
40725
+ var LOOKBACK_MS = 14 * DAY_MS;
40726
+ var TIMEOUT_MS2 = 8e3;
40727
+ var wbethFetcher = {
40728
+ label: "WBETH",
40729
+ fetch: async () => {
40730
+ const now = Date.now();
40731
+ const url = `${HISTORY_URL2}?startTime=${now - LOOKBACK_MS}&endTime=${now}`;
40732
+ try {
40733
+ const res = await fetch(url, {
40734
+ method: "GET",
40735
+ headers: { Accept: "application/json" },
40736
+ signal: AbortSignal.timeout(TIMEOUT_MS2)
40737
+ }).then((r) => r.json());
40738
+ const points = res.data ?? [];
40739
+ if (points.length > 0) {
40740
+ const latest = points.reduce(
40741
+ (a, b) => Number(b.calcDate) > Number(a.calcDate) ? b : a
40742
+ );
40743
+ const apr = Number(latest.apr) * 100;
40744
+ if (Number.isFinite(apr) && apr > 0) return { [WBETH]: apr };
40745
+ }
40746
+ } catch (e) {
40747
+ console.log("WBETH history failed, falling back to DefiLlama", e);
40748
+ }
40749
+ const apy = await fetchDefiLlamaApy(LLAMA_POOL);
40750
+ return { [WBETH]: apyToAprPercent(apy) };
40751
+ }
40752
+ };
40753
+
40702
40754
  // src/vaults/lst/registry.ts
40703
40755
  var LST_REGISTRY = {
40704
40756
  // Monad (143) — native-MON LSTs. shMON / aprMON are ERC-4626 over native
@@ -41101,6 +41153,46 @@ var LST_REGISTRY = {
41101
41153
  yieldFetcher: cbethFetcher,
41102
41154
  yieldKey: "CBETH"
41103
41155
  },
41156
+ {
41157
+ // Binance wBETH — the *other* exchange LST, and unlike cbETH above
41158
+ // it is genuinely permissionless on-chain in both directions:
41159
+ // mint `deposit(address referral)` payable, no allowlist
41160
+ // redeem `requestWithdrawEth(uint256)` → the UnwrapTokenV1ETH
41161
+ // queue at 0x79973d557CD9dd87eb61E250cc2572c990e20196
41162
+ // (both simulated against mainnet — `deposit` succeeds from an
41163
+ // arbitrary EOA, `requestWithdrawEth` reverts only on balance).
41164
+ //
41165
+ // A FiatTokenProxy (Circle's USDC codebase) + Binance's
41166
+ // StakedTokenV3 mixin, so it inherits USDC-style `blacklist(address)`
41167
+ // and `pause()` on BOTH the token and the unwrap queue — Binance can
41168
+ // freeze any holder. Same trust class as USDC; that is the live risk
41169
+ // for anything treating wBETH as collateral.
41170
+ //
41171
+ // The `queued` exit has two teeth that a plain cooldown does not:
41172
+ // * the ETH owed is FROZEN at request time (`ethAmount` is stored,
41173
+ // not recomputed), so the position stops earning for the whole
41174
+ // `lockTime()` — currently 864000s / 10 days, admin-settable down
41175
+ // to MIN_LOCK_TIME = 172800s / 2 days. Read it live.
41176
+ // * a request is only auto-allocated while
41177
+ // `availableAllocateAmount` covers it (~3 ETH on Ethereum today);
41178
+ // anything larger waits for Binance's operator to `allocate()`,
41179
+ // with no SLA.
41180
+ address: "0xa2e3356610840701bdf5611a53974510ae27e2e1",
41181
+ underlying: "0x0000000000000000000000000000000000000000",
41182
+ symbol: "wBETH",
41183
+ brand: "Binance",
41184
+ decimals: 18,
41185
+ reader: "binanceWbeth",
41186
+ isErc4626: false,
41187
+ isRebasing: false,
41188
+ isMintable: true,
41189
+ isNativeUnderlying: true,
41190
+ mintContract: "0xa2e3356610840701bdf5611a53974510ae27e2e1",
41191
+ mintInputAsset: "native",
41192
+ withdrawalMode: "queued",
41193
+ yieldFetcher: wbethFetcher,
41194
+ yieldKey: "Wrapped Binance Beacon ETH::wBETH"
41195
+ },
41104
41196
  {
41105
41197
  address: "0xa43a7c62d56df036c187e1966c03e2799d8987ed",
41106
41198
  // TruFin TruStake MATIC Vault uses the MATIC ERC-20 (not POL).
@@ -41667,6 +41759,37 @@ var LST_REGISTRY = {
41667
41759
  listaStakeManager: "0x1adb950d8bb3da4be104211d5ab038628e477fe6"
41668
41760
  }
41669
41761
  },
41762
+ {
41763
+ // Binance wBETH on BNB — SAME token address as Ethereum, but a
41764
+ // different implementation behind the proxy (`WrapTokenV2BSC` vs
41765
+ // `WrapTokenV3ETH`), so the mint leg is NOT portable:
41766
+ // Ethereum `deposit(address referral)` payable, native ETH
41767
+ // BNB `deposit(uint256 amount, address referral)` nonpayable,
41768
+ // pulls the Binance-pegged ETH ERC-20 below → needs approve
41769
+ // The read surface and the pushed rate ARE identical (one oracle
41770
+ // 0x81720695… writes both chains, and `exchangeRate()` returns the
41771
+ // same value), hence the shared reader and the shared `yieldKey`.
41772
+ //
41773
+ // Exit is the same UnwrapTokenV1 queue at the same address, with the
41774
+ // same frozen-amount / 10-day-lock / operator-allocation caveats as
41775
+ // the Ethereum entry — see there.
41776
+ address: "0xa2e3356610840701bdf5611a53974510ae27e2e1",
41777
+ // Binance-pegged ETH on BSC — the deposit input, not native BNB.
41778
+ underlying: "0x2170ed0880ac9a755fd29b2688956bd959f933f8",
41779
+ symbol: "wBETH",
41780
+ brand: "Binance",
41781
+ decimals: 18,
41782
+ reader: "binanceWbeth",
41783
+ isErc4626: false,
41784
+ isRebasing: false,
41785
+ isMintable: true,
41786
+ isNativeUnderlying: false,
41787
+ mintContract: "0xa2e3356610840701bdf5611a53974510ae27e2e1",
41788
+ mintInputAsset: "0x2170ed0880ac9a755fd29b2688956bd959f933f8",
41789
+ withdrawalMode: "queued",
41790
+ yieldFetcher: wbethFetcher,
41791
+ yieldKey: "Wrapped Binance Beacon ETH::wBETH"
41792
+ },
41670
41793
  {
41671
41794
  // YieldNest ynBNB — ERC-4626 vault over slisBNB (Lista), restaked
41672
41795
  // via Kernel. Redeems to slisBNB synchronously; the BNB unstake
@@ -46061,32 +46184,85 @@ var upshiftFetcher = {
46061
46184
  }
46062
46185
  };
46063
46186
 
46064
- // src/yields/yieldTypes.ts
46065
- var addressToAssetYearn = {
46066
- "0xe007ca01894c863d7898045ed5a3b4abf0b18f37": "vbETH yVault::yvvbETH",
46067
- "0x80c34bd3a3569e126e7055831036aa7b212cb159": "vbUSDC yVault::yvvbUSDC",
46068
- "0x9a6bd7b6fd5c4f87eb66356441502fc7dcdd185b": "vbUSDT yVault::yvvbUSDT",
46069
- "0xaa0362ecc584b985056e47812931270b99c91f9d": "vbWBTC yVault::yvvbWBTC",
46070
- "0x93fec6639717b6215a48e5a72a162c50dcc40d68": "vbAUSD yVault::yvvbAUSD"
46071
- };
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
+ }
46072
46235
 
46073
- // src/yields/intrinsic/fetchers/yearnKatana.ts
46074
- var YEARN_KATANA_URL = "https://katana-apr-service.vercel.app/api/vaults";
46075
- var yearnKatanaFetcher = {
46076
- 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",
46077
46241
  fetch: async () => {
46078
- const res = await fetch(
46079
- YEARN_KATANA_URL
46080
- ).then((r) => r.json());
46081
- let map = {};
46082
- Object.entries(res).forEach(([addr2, data]) => {
46083
- const key2 = addressToAssetYearn[addr2.toLowerCase()];
46084
- if (key2) {
46085
- const apr = ((data.apr.netAPR ?? 0) + (data.apr.extra?.extrinsicYield ?? 0) + (data.apr.extra?.katanaNativeYield ?? 0)) * 100;
46086
- map[key2] = apr;
46087
- }
46088
- });
46089
- 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);
46090
46266
  }
46091
46267
  };
46092
46268
 
@@ -46150,7 +46326,9 @@ var LENDING_ONLY_FETCHERS = [
46150
46326
  nestFetcher,
46151
46327
  accountableFetcher,
46152
46328
  upshiftFetcher,
46153
- 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,
46154
46332
  // DefiLlama-mapped intrinsic yields for classifier assets lacking a bespoke source
46155
46333
  lsethFetcher,
46156
46334
  tethFetcher,
@@ -46251,6 +46429,76 @@ function extractRewardBreakdowns(opp) {
46251
46429
  distributionType: b.distributionType
46252
46430
  }));
46253
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
+ }
46254
46502
  function resolveUnderlyingFromOpportunity(opp) {
46255
46503
  if (!opp.tokens?.length) return void 0;
46256
46504
  if (opp.depositUrl) {
@@ -46316,6 +46564,11 @@ function createMerklRewardFetcher(config) {
46316
46564
  );
46317
46565
  const breakdowns = extractRewardBreakdowns(opp);
46318
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
+ );
46319
46572
  if (bucket === "deposit") {
46320
46573
  reward.deposit += opp.apr;
46321
46574
  reward.additionalDepositData.push(...breakdowns);
@@ -46520,8 +46773,21 @@ var ABI = [
46520
46773
  stateMutability: "view",
46521
46774
  inputs: [],
46522
46775
  outputs: [{ type: "uint8" }]
46776
+ },
46777
+ {
46778
+ name: "symbol",
46779
+ type: "function",
46780
+ stateMutability: "view",
46781
+ inputs: [],
46782
+ outputs: [{ type: "string" }]
46523
46783
  }
46524
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
+ };
46525
46791
  var isZero = (a) => !a || a.toLowerCase() === ZERO_ADDRESS5;
46526
46792
  var scale = (raw, decimals) => Number(raw) / 10 ** decimals;
46527
46793
  function emptyReward2() {
@@ -46635,19 +46901,25 @@ async function fetchForPool(chainId, lender, pool, nowSeconds) {
46635
46901
  calls: pairs.flatMap((p) => [
46636
46902
  call(p.leg.controller, "getRewardsData", [p.leg.balanceToken, p.reward]),
46637
46903
  call(p.reward, "decimals"),
46638
- call(oracle, "getAssetPrice", [p.reward])
46904
+ call(oracle, "getAssetPrice", [p.reward]),
46905
+ call(p.reward, "symbol")
46639
46906
  ]),
46640
46907
  allowFailure: false
46641
46908
  });
46909
+ const STRIDE = 4;
46642
46910
  const result = {};
46643
46911
  pairs.forEach((p, i) => {
46644
- const data = round5[i * 3];
46912
+ const data = round5[i * STRIDE];
46645
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);
46646
46918
  const apr = rebateApr({
46647
46919
  emissionPerSecond: BigInt(data[1] ?? 0),
46648
46920
  distributionEnd: BigInt(data[3] ?? 0),
46649
- rewardDecimals: Number(round5[i * 3 + 1] ?? 18),
46650
- rewardPrice: Number(round5[i * 3 + 2] ?? 0n),
46921
+ rewardDecimals,
46922
+ rewardPrice,
46651
46923
  balanceTotalSupply: BigInt(p.leg.totalSupply ?? 0),
46652
46924
  balanceDecimals: p.leg.decimals,
46653
46925
  assetPrice: priceByAsset[p.leg.asset] ?? 0,
@@ -46662,9 +46934,34 @@ async function fetchForPool(chainId, lender, pool, nowSeconds) {
46662
46934
  const breakdown = {
46663
46935
  tokenAddress: p.reward.toLowerCase(),
46664
46936
  emissionPerSecond: String(data[1]),
46665
- distributionEnd: Number(data[3]),
46937
+ distributionEnd,
46666
46938
  distributionType: "aave-incentives"
46667
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
+ });
46668
46965
  if (p.leg.bucket === "deposit") {
46669
46966
  entry.deposit += apr;
46670
46967
  entry.additionalDepositData.push(breakdown);
@@ -48220,6 +48517,79 @@ var listaFetcher = {
48220
48517
  parse: parseListaResults,
48221
48518
  getAbi: getListaAbi
48222
48519
  };
48520
+ function generateLlamaLendLenderKey(marketId) {
48521
+ return `${Lender.LLAMALEND}_${marketId.replace(/^0x/i, "").toUpperCase()}`;
48522
+ }
48523
+ function getLlamaLendMarketsForChain(chainId, marketOverrides) {
48524
+ return marketOverrides?.[chainId] ?? [];
48525
+ }
48526
+ function getLlamaLendCalls(chainId, context) {
48527
+ const markets = getLlamaLendMarketsForChain(chainId, context?.marketOverrides);
48528
+ if (markets.length === 0) return [];
48529
+ return markets.map((market) => {
48530
+ const call = {
48531
+ address: market.amm,
48532
+ name: "price_oracle",
48533
+ params: []
48534
+ };
48535
+ return {
48536
+ calls: [call],
48537
+ meta: { markets: [market] },
48538
+ lender: generateLlamaLendLenderKey(market.marketId)
48539
+ };
48540
+ });
48541
+ }
48542
+ function parseLlamaLendResults(data, meta, context) {
48543
+ const { chainId, usdPrices, tokenList } = context;
48544
+ const entries = [];
48545
+ const rawPrice = data[0];
48546
+ if (rawPrice === void 0 || rawPrice === null || rawPrice === "0x") {
48547
+ return entries;
48548
+ }
48549
+ for (const market of meta.markets) {
48550
+ const loanAsset = market.loanAsset.toLowerCase();
48551
+ const collateralAsset = market.collateralAsset.toLowerCase();
48552
+ let collateralInLoan;
48553
+ try {
48554
+ collateralInLoan = Number(formatUnits(BigInt(rawPrice.toString()), 18));
48555
+ } catch {
48556
+ continue;
48557
+ }
48558
+ if (!Number.isFinite(collateralInLoan) || collateralInLoan === 0) continue;
48559
+ const loanOracleKey = tokenList?.[loanAsset]?.assetGroup ?? `${chainId}-${loanAsset}`;
48560
+ const loanAssetUSD = usdPrices[loanOracleKey] ?? usdPrices[loanAsset];
48561
+ if (!loanAssetUSD) continue;
48562
+ const lenderKey = generateLlamaLendLenderKey(market.marketId);
48563
+ entries.push({
48564
+ asset: loanAsset,
48565
+ price: 1,
48566
+ priceUSD: loanAssetUSD,
48567
+ marketUid: createMarketUid(chainId, lenderKey, loanAsset),
48568
+ targetLender: lenderKey,
48569
+ description: "LlamaLend borrowed asset",
48570
+ staticBase: true,
48571
+ baseAsset: loanAsset
48572
+ });
48573
+ entries.push({
48574
+ asset: collateralAsset,
48575
+ price: collateralInLoan,
48576
+ priceUSD: collateralInLoan * loanAssetUSD,
48577
+ marketUid: createMarketUid(chainId, lenderKey, collateralAsset),
48578
+ targetLender: lenderKey,
48579
+ description: "LlamaLend collateral (AMM EMA oracle)",
48580
+ baseAsset: loanAsset
48581
+ });
48582
+ }
48583
+ return entries;
48584
+ }
48585
+ function getLlamaLendAbi() {
48586
+ return LlamaLendAmmAbi;
48587
+ }
48588
+ var llamaLendFetcher = {
48589
+ getCalls: getLlamaLendCalls,
48590
+ parse: parseLlamaLendResults,
48591
+ getAbi: getLlamaLendAbi
48592
+ };
48223
48593
 
48224
48594
  // src/abis/euler/priceLens.ts
48225
48595
  var priceLensAbi = [
@@ -49580,7 +49950,7 @@ async function executeGroup(group, chainId, chainBatchSize, retries, allowFailur
49580
49950
  };
49581
49951
  }
49582
49952
  }
49583
- async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3, batchSize = void 0, allowFailure = true, basePrices = {}, morphoMarketOverrides, listaMarketOverrides, stalenessThresholdSeconds = 3600, onlyFetchers, probeFeedStaleness = true) {
49953
+ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3, batchSize = void 0, allowFailure = true, basePrices = {}, morphoMarketOverrides, listaMarketOverrides, stalenessThresholdSeconds = 3600, onlyFetchers, probeFeedStaleness = true, llamaLendMarketOverrides) {
49584
49954
  const totalStart = Date.now();
49585
49955
  const result = {};
49586
49956
  const chainPromises = chainIds.map(async (chainId) => {
@@ -49625,6 +49995,13 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
49625
49995
  }),
49626
49996
  getCallsErrors
49627
49997
  ) : [];
49998
+ const llamaLendResults = isActive("llamalend") ? safeGetCalls(
49999
+ "llamaLend",
50000
+ () => llamaLendFetcher.getCalls(chainId, {
50001
+ marketOverrides: llamaLendMarketOverrides
50002
+ }),
50003
+ getCallsErrors
50004
+ ) : [];
49628
50005
  const eulerResults = isActive("eulerv2") ? safeGetCalls(
49629
50006
  "eulerV2",
49630
50007
  () => eulerV2Fetcher.getCalls(chainId),
@@ -49745,6 +50122,13 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
49745
50122
  ProxyOracleAbi,
49746
50123
  "direct"
49747
50124
  );
50125
+ const llamaLendGroup = buildGroup(
50126
+ "llamaLend",
50127
+ llamaLendResults,
50128
+ llamaLendFetcher.parse,
50129
+ getLlamaLendAbi(),
50130
+ "derived"
50131
+ );
49748
50132
  const eulerGroup = buildGroup(
49749
50133
  "eulerV2",
49750
50134
  eulerResults,
@@ -49871,6 +50255,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
49871
50255
  compoundV2Group,
49872
50256
  compoundV3Group,
49873
50257
  listaGroup,
50258
+ llamaLendGroup,
49874
50259
  eulerGroup,
49875
50260
  aaveV4Group,
49876
50261
  morphoGroup,
@@ -49918,6 +50303,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
49918
50303
  compoundV2Data,
49919
50304
  compoundV3Data,
49920
50305
  listaData,
50306
+ llamaLendData,
49921
50307
  eulerData,
49922
50308
  aaveV4Data,
49923
50309
  fluidData,
@@ -49968,6 +50354,14 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
49968
50354
  allowFailure,
49969
50355
  rpcOverrides
49970
50356
  ),
50357
+ executeGroup(
50358
+ llamaLendGroup,
50359
+ chainId,
50360
+ chainBatchSize,
50361
+ retries,
50362
+ allowFailure,
50363
+ rpcOverrides
50364
+ ),
49971
50365
  executeGroup(
49972
50366
  eulerGroup,
49973
50367
  chainId,
@@ -50125,6 +50519,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
50125
50519
  { group: compoundV2Group, data: compoundV2Data },
50126
50520
  { group: compoundV3Group, data: compoundV3Data },
50127
50521
  { group: listaGroup, data: listaData },
50522
+ { group: llamaLendGroup, data: llamaLendData },
50128
50523
  { group: eulerGroup, data: eulerData },
50129
50524
  { group: aaveV4Group, data: aaveV4Data },
50130
50525
  { group: fluidGroup, data: fluidData },
@@ -50317,6 +50712,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
50317
50712
  }
50318
50713
  parseTrackers(midnightGroup, midnightData.results, false);
50319
50714
  parseTrackers(tellerGroup, tellerData.results, false);
50715
+ parseTrackers(llamaLendGroup, llamaLendData.results, false);
50320
50716
  if (stalenessThresholdSeconds > 0) {
50321
50717
  const feedTimestamps = await feedTimestampsPromise;
50322
50718
  for (const [lender, assetMap] of Object.entries(feedTimestamps)) {
@@ -51506,7 +51902,7 @@ var fetchGearboxV3Pools = async (chainId, multicallRetry, prices = {}, tokenList
51506
51902
  // src/vaults/morpho/fetchFromApi.ts
51507
51903
  var MORPHO_SHARE_DECIMALS = 18;
51508
51904
  var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
51509
- var PAGE_SIZE = 200;
51905
+ var PAGE_SIZE2 = 200;
51510
51906
  var vaultsQuery = (first, skip, chainId) => `
51511
51907
  query GetVaults {
51512
51908
  vaults(
@@ -51671,14 +52067,14 @@ async function fetchMorphoVaultsFromApi(chainId, prices = {}, tokenList = {}, ma
51671
52067
  const out = {};
51672
52068
  let skip = 0;
51673
52069
  while (skip < maxItems) {
51674
- const page = await fetchPage(chainId, PAGE_SIZE, skip);
52070
+ const page = await fetchPage(chainId, PAGE_SIZE2, skip);
51675
52071
  if (page.length === 0) break;
51676
52072
  for (const v of page) {
51677
52073
  const parsed = parseVault2(v, chainId, prices, tokenList);
51678
52074
  if (parsed) out[parsed.address] = parsed;
51679
52075
  }
51680
- if (page.length < PAGE_SIZE) break;
51681
- skip += PAGE_SIZE;
52076
+ if (page.length < PAGE_SIZE2) break;
52077
+ skip += PAGE_SIZE2;
51682
52078
  }
51683
52079
  return out;
51684
52080
  }
@@ -54808,6 +55204,38 @@ var readerAnkrRatio = (entry) => ({
54808
55204
  }
54809
55205
  });
54810
55206
 
55207
+ // src/vaults/lst/abis/binance.ts
55208
+ var WbethExchangeRateAbi = [
55209
+ {
55210
+ name: "exchangeRate",
55211
+ type: "function",
55212
+ stateMutability: "view",
55213
+ inputs: [],
55214
+ outputs: [{ type: "uint256" }]
55215
+ }
55216
+ ];
55217
+
55218
+ // src/vaults/lst/readers/binance.ts
55219
+ var readerBinanceWbeth = (entry) => ({
55220
+ calls: [
55221
+ { address: entry.address, name: "totalSupply", params: [] },
55222
+ { address: entry.address, name: "exchangeRate", params: [] }
55223
+ ],
55224
+ abis: [TotalSupplyAbi, WbethExchangeRateAbi],
55225
+ parse: ([supply, rate]) => {
55226
+ const totalSupply = toBigInt13(supply);
55227
+ const exchangeRate = toBigInt13(rate);
55228
+ if (totalSupply === void 0 || exchangeRate === void 0) {
55229
+ return void 0;
55230
+ }
55231
+ return {
55232
+ totalAssets: totalSupply * exchangeRate / ONE_E189,
55233
+ totalSupply,
55234
+ exchangeRate
55235
+ };
55236
+ }
55237
+ });
55238
+
54811
55239
  // src/vaults/lst/abis/core.ts
54812
55240
  var CoreEarnRateAbi = [
54813
55241
  {
@@ -54930,6 +55358,8 @@ var buildReader = (entry) => {
54930
55358
  return readerKelpRsEth(entry);
54931
55359
  case "swellGetRate":
54932
55360
  return readerSwellGetRate(entry);
55361
+ case "binanceWbeth":
55362
+ return readerBinanceWbeth(entry);
54933
55363
  case "stakewiseOsEth":
54934
55364
  return readerStakeWiseOsEth(entry);
54935
55365
  case "staderEthx":
@@ -55827,6 +56257,96 @@ var readerBenqi = {
55827
56257
  }
55828
56258
  };
55829
56259
 
56260
+ // src/vaults/lst/withdrawals/abis/binance.ts
56261
+ var BinanceUnwrapQueueAbi = [
56262
+ {
56263
+ name: "getUserWithdrawRequests",
56264
+ type: "function",
56265
+ stateMutability: "view",
56266
+ inputs: [{ type: "address", name: "recipient" }],
56267
+ outputs: [
56268
+ {
56269
+ type: "tuple[]",
56270
+ components: [
56271
+ { type: "address", name: "recipient" },
56272
+ { type: "uint256", name: "wbethAmount" },
56273
+ { type: "uint256", name: "ethAmount" },
56274
+ { type: "uint256", name: "triggerTime" },
56275
+ { type: "uint256", name: "claimTime" },
56276
+ { type: "bool", name: "allocated" }
56277
+ ]
56278
+ }
56279
+ ]
56280
+ },
56281
+ {
56282
+ // Currently 864000 (10 days). Admin-settable down to
56283
+ // `MIN_LOCK_TIME` = 172800 (2 days) — always read it, never hardcode.
56284
+ name: "lockTime",
56285
+ type: "function",
56286
+ stateMutability: "view",
56287
+ inputs: [],
56288
+ outputs: [{ type: "uint256" }]
56289
+ },
56290
+ {
56291
+ name: "claimWithdraw",
56292
+ type: "function",
56293
+ stateMutability: "nonpayable",
56294
+ inputs: [{ type: "uint256", name: "index" }],
56295
+ outputs: [{ type: "uint256" }]
56296
+ }
56297
+ ];
56298
+
56299
+ // src/vaults/lst/withdrawals/readers/binance.ts
56300
+ var readerBinanceWbeth2 = {
56301
+ fetch: async (user, multicallRetry, chainId, entry) => {
56302
+ if (!entry.withdrawalContract) return [];
56303
+ const res = await multicallRetry({
56304
+ chain: chainId,
56305
+ calls: [
56306
+ {
56307
+ address: entry.withdrawalContract,
56308
+ name: "getUserWithdrawRequests",
56309
+ params: [user]
56310
+ },
56311
+ {
56312
+ address: entry.withdrawalContract,
56313
+ name: "lockTime",
56314
+ params: []
56315
+ }
56316
+ ],
56317
+ abi: [BinanceUnwrapQueueAbi, BinanceUnwrapQueueAbi]
56318
+ });
56319
+ const reqs = res[0];
56320
+ const lockTime = toNumber(res[1]);
56321
+ if (!Array.isArray(reqs) || lockTime === void 0) return [];
56322
+ const out = [];
56323
+ for (let i = 0; i < reqs.length; i++) {
56324
+ const r = reqs[i];
56325
+ const triggerTime = toNumber(r.triggerTime);
56326
+ const ethAmount = toBigInt14(r.ethAmount);
56327
+ const wbethAmount = toBigInt14(r.wbethAmount);
56328
+ if (triggerTime === void 0 || ethAmount === void 0) continue;
56329
+ const readyAt = triggerTime + lockTime;
56330
+ const claimed = (toNumber(r.claimTime) ?? 0) > 0;
56331
+ const allocated = r.allocated === true;
56332
+ out.push({
56333
+ lst: entry.lst,
56334
+ brand: entry.brand,
56335
+ symbol: entry.symbol,
56336
+ // Positional — see note 1 above.
56337
+ requestId: String(i),
56338
+ amountUnderlying: ethAmount.toString(),
56339
+ shares: wbethAmount?.toString(),
56340
+ // Claimed requests are popped from the array, so this branch is
56341
+ // defensive only.
56342
+ status: claimed ? "claimed" : allocated ? computeStatus(readyAt) : "pending",
56343
+ readyAt
56344
+ });
56345
+ }
56346
+ return out;
56347
+ }
56348
+ };
56349
+
55830
56350
  // src/vaults/lst/withdrawals/abis/berapaw.ts
55831
56351
  var BeraPawForgeWithdrawalAbi = [
55832
56352
  {
@@ -57968,6 +58488,8 @@ var readerYieldNest = {
57968
58488
  // src/vaults/lst/withdrawals/readers/index.ts
57969
58489
  var buildWithdrawalReader = (entry) => {
57970
58490
  switch (entry.reader) {
58491
+ case "binanceWbethQueue":
58492
+ return readerBinanceWbeth2;
57971
58493
  case "lidoQueue":
57972
58494
  return readerLido;
57973
58495
  case "etherfiNft":
@@ -58033,6 +58555,20 @@ var buildWithdrawalReader = (entry) => {
58033
58555
  // src/vaults/lst/withdrawals/registry.ts
58034
58556
  var LST_WITHDRAWAL_REGISTRY = {
58035
58557
  "1": [
58558
+ {
58559
+ // wBETH — Binance's UnwrapTokenV1 queue, the SAME contract address
58560
+ // on Ethereum and BNB. Entered via `wBETH.requestWithdrawEth`;
58561
+ // `getUserWithdrawRequests(user)` enumerates open requests, and
58562
+ // `claimWithdraw(index)` takes the user's ARRAY POSITION (swap-and-pop,
58563
+ // so ids shift on every claim — never cache them). `lockTime()` is
58564
+ // 10 days today but is admin-settable down to 2, and the ETH owed is
58565
+ // frozen at request time, so the position stops earning meanwhile.
58566
+ lst: "0xa2e3356610840701bdf5611a53974510ae27e2e1",
58567
+ brand: "Binance",
58568
+ symbol: "wBETH",
58569
+ reader: "binanceWbethQueue",
58570
+ withdrawalContract: "0x79973d557cd9dd87eb61e250cc2572c990e20196"
58571
+ },
58036
58572
  // Ankr ankrETH — Ankr unstake queue; reader not yet implemented.
58037
58573
  {
58038
58574
  lst: "0xe95a203b1a91a908f9b9ce46459d101078c2c3cb",
@@ -58405,6 +58941,20 @@ var LST_WITHDRAWAL_REGISTRY = {
58405
58941
  }
58406
58942
  ],
58407
58943
  "56": [
58944
+ {
58945
+ // wBETH — Binance's UnwrapTokenV1 queue, the SAME contract address
58946
+ // on Ethereum and BNB. Entered via `wBETH.requestWithdrawEth`;
58947
+ // `getUserWithdrawRequests(user)` enumerates open requests, and
58948
+ // `claimWithdraw(index)` takes the user's ARRAY POSITION (swap-and-pop,
58949
+ // so ids shift on every claim — never cache them). `lockTime()` is
58950
+ // 10 days today but is admin-settable down to 2, and the ETH owed is
58951
+ // frozen at request time, so the position stops earning meanwhile.
58952
+ lst: "0xa2e3356610840701bdf5611a53974510ae27e2e1",
58953
+ brand: "Binance",
58954
+ symbol: "wBETH",
58955
+ reader: "binanceWbethQueue",
58956
+ withdrawalContract: "0x79973d557cd9dd87eb61e250cc2572c990e20196"
58957
+ },
58408
58958
  // Ankr ankrBNB — Ankr unstake queue; reader not yet implemented.
58409
58959
  {
58410
58960
  lst: "0x52f24a5e03aee338da5fd9df68d2b6fae1178827",
@@ -59321,7 +59871,7 @@ query LagoonVaults($where: VaultFilterInput!, $first: Int!, $skip: Int!) {
59321
59871
  }
59322
59872
  }
59323
59873
  }`;
59324
- var PAGE_SIZE2 = 200;
59874
+ var PAGE_SIZE3 = 200;
59325
59875
  async function fetchLagoonApiVaults(chainId) {
59326
59876
  const out = [];
59327
59877
  let skip = 0;
@@ -59333,7 +59883,7 @@ async function fetchLagoonApiVaults(chainId) {
59333
59883
  query: VAULTS_QUERY,
59334
59884
  variables: {
59335
59885
  where: { chainId_eq: Number(chainId), isVisible_eq: true },
59336
- first: PAGE_SIZE2,
59886
+ first: PAGE_SIZE3,
59337
59887
  skip
59338
59888
  }
59339
59889
  })
@@ -59351,8 +59901,8 @@ async function fetchLagoonApiVaults(chainId) {
59351
59901
  }
59352
59902
  const items = json?.data?.vaults?.items ?? [];
59353
59903
  out.push(...items);
59354
- if (items.length < PAGE_SIZE2) break;
59355
- skip += PAGE_SIZE2;
59904
+ if (items.length < PAGE_SIZE3) break;
59905
+ skip += PAGE_SIZE3;
59356
59906
  }
59357
59907
  return out;
59358
59908
  }
@@ -59472,7 +60022,7 @@ var Erc4626TotalAssetsAbi = [
59472
60022
 
59473
60023
  // src/vaults/aave-earn/api.ts
59474
60024
  var AAVE_EARN_API_URL = "https://api.v3.aave.com/graphql";
59475
- var PAGE_SIZE3 = "FIFTY";
60025
+ var PAGE_SIZE4 = "FIFTY";
59476
60026
  var VAULT_FIELDS = `
59477
60027
  address
59478
60028
  shareName
@@ -59530,7 +60080,7 @@ async function fetchAaveEarnApiVaults(chainId, targets) {
59530
60080
  for (let page = 0; page < 20; page++) {
59531
60081
  const request = {
59532
60082
  criteria: { ownedBy: [owner] },
59533
- pageSize: PAGE_SIZE3
60083
+ pageSize: PAGE_SIZE4
59534
60084
  };
59535
60085
  if (cursor) request.cursor = cursor;
59536
60086
  const data = await postGraphql(OWNED_VAULTS_QUERY, { request });
@@ -59789,47 +60339,6 @@ var fetchUpshiftVaults = async (chainId, prices = {}, tokenList = {}) => {
59789
60339
  return out;
59790
60340
  };
59791
60341
 
59792
- // src/vaults/yearn/api.ts
59793
- var YEARN_YDAEMON_BASE = "https://ydaemon.yearn.fi";
59794
- var YEARN_CHAIN_IDS = /* @__PURE__ */ new Set([
59795
- "1",
59796
- // Ethereum
59797
- "137",
59798
- // Polygon
59799
- "8453",
59800
- // Base
59801
- "42161",
59802
- // Arbitrum
59803
- "100",
59804
- // Gnosis
59805
- "146",
59806
- // Sonic
59807
- "747474"
59808
- // Katana
59809
- ]);
59810
- var hasYearnVaults = (chainId) => YEARN_CHAIN_IDS.has(String(chainId));
59811
- var isYearnV3 = (v) => typeof v?.version === "string" && v.version.startsWith("3.");
59812
- var PAGE_SIZE4 = 500;
59813
- async function fetchYearnApiVaults(chainId) {
59814
- const out = [];
59815
- for (let page = 1; page <= 20; page++) {
59816
- const url = `${YEARN_YDAEMON_BASE}/${chainId}/vaults/all?limit=${PAGE_SIZE4}&page=${page}`;
59817
- const response = await fetch(url);
59818
- if (!response.ok) {
59819
- throw new Error(
59820
- `Yearn yDaemon API failed: ${response.status} - ${response.statusText}`
59821
- );
59822
- }
59823
- const items = await response.json();
59824
- if (!Array.isArray(items)) break;
59825
- for (const v of items) {
59826
- if (isYearnV3(v) && v.endorsed) out.push(v);
59827
- }
59828
- if (items.length < PAGE_SIZE4) break;
59829
- }
59830
- return out;
59831
- }
59832
-
59833
60342
  // src/vaults/yearn/fetchPublic.ts
59834
60343
  var BRAND3 = "Yearn";
59835
60344
  var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
@@ -59875,7 +60384,7 @@ var asBig = (v) => {
59875
60384
  }
59876
60385
  };
59877
60386
  var num9 = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
59878
- var toPct = (v) => {
60387
+ var toPct2 = (v) => {
59879
60388
  const n = num9(v);
59880
60389
  return n == null ? 0 : n * 100;
59881
60390
  };
@@ -59911,9 +60420,9 @@ function parseVault9(v, chainId, prices, tokenList) {
59911
60420
  const realized = num9(v.apr?.netAPR);
59912
60421
  const forward = num9(v.apr?.forwardAPR?.netAPR);
59913
60422
  const isForwardApr = realized == null && forward != null;
59914
- const supplyRate = toPct(realized ?? forward);
59915
- const rewardsRate = toPct(v.apr?.extra?.stakingRewardsAPR);
59916
- 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);
59917
60426
  const oracleKey = toOracleKey(assetMeta?.assetGroup) ?? toGenericPriceKey(assetAddr, chainId);
59918
60427
  const apiPrice = num9(v.tvl?.price) ?? 0;
59919
60428
  const priceUsd = prices[oracleKey] ?? prices[assetAddr] ?? apiPrice;
@@ -64697,11 +65206,12 @@ var llamaLendAdapter = {
64697
65206
  /**
64698
65207
  * The band count as an editable TERM, not just a curve to read.
64699
65208
  *
64700
- * `bandLtv` alone cannot drive a control: it is four sampled points,
64701
- * and it is missing on any market whose curve could not be computed.
64702
- * The domain is always known — `MIN_TICKS`/`MAX_TICKS` are 4..50 on
64703
- * both generations — so the control works even where the curve does
64704
- * 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.
64705
65215
  *
64706
65216
  * `immutableAfterOpen` is what tells the UI to render this read-only
64707
65217
  * on an existing loan: `_add_collateral_borrow` reuses the tick
@@ -64766,7 +65276,16 @@ var frankencoinAdapter = {
64766
65276
  return {
64767
65277
  // Not a coverage hole: the protocol has no price feed by design.
64768
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
+ },
64769
65287
  borrow: {
65288
+ availability: { opensWith: "both-legs" },
64770
65289
  debtShape: "accruing",
64771
65290
  maturity: expiration > 0 ? {
64772
65291
  kind: "fixed-date",