@1delta/margin-fetcher 5.0.63 → 5.0.64

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
@@ -10951,9 +10951,9 @@ function applyPositionDelta(positions, targetMarketUid, depositsDeltaUSD, debtDe
10951
10951
  }
10952
10952
 
10953
10953
  // src/lending/margin/base/sumer/deposit.ts
10954
- function computeSumerDepositDelta(amount3, price2, targetMarketUid, balanceData2, positions, createNewSubAccount = false, apr, yieldParams) {
10954
+ function computeSumerDepositDelta(amount4, price2, targetMarketUid, balanceData2, positions, createNewSubAccount = false, apr, yieldParams) {
10955
10955
  const base = createNewSubAccount ? EMPTY_BALANCE : balanceData2;
10956
- const dollarAmount = amount3 * price2;
10956
+ const dollarAmount = amount4 * price2;
10957
10957
  const updatedPositions = applyPositionDelta(
10958
10958
  positions,
10959
10959
  targetMarketUid,
@@ -11019,8 +11019,8 @@ function computeSumerDepositDelta(amount3, price2, targetMarketUid, balanceData2
11019
11019
  }
11020
11020
 
11021
11021
  // src/lending/margin/base/sumer/withdraw.ts
11022
- function computeSumerWithdrawDelta(amount3, price2, targetMarketUid, balanceData2, positions, apr, yieldParams) {
11023
- const dollarAmount = amount3 * price2;
11022
+ function computeSumerWithdrawDelta(amount4, price2, targetMarketUid, balanceData2, positions, apr, yieldParams) {
11023
+ const dollarAmount = amount4 * price2;
11024
11024
  const updatedPositions = applyPositionDelta(
11025
11025
  positions,
11026
11026
  targetMarketUid,
@@ -11086,8 +11086,8 @@ function computeSumerWithdrawDelta(amount3, price2, targetMarketUid, balanceData
11086
11086
  }
11087
11087
 
11088
11088
  // src/lending/margin/base/sumer/borrow.ts
11089
- function computeSumerBorrowDelta(amount3, price2, targetMarketUid, balanceData2, positions, apr, yieldParams, irMode) {
11090
- const dollarAmount = amount3 * price2;
11089
+ function computeSumerBorrowDelta(amount4, price2, targetMarketUid, balanceData2, positions, apr, yieldParams, irMode) {
11090
+ const dollarAmount = amount4 * price2;
11091
11091
  const mode = irMode ?? 2;
11092
11092
  const updatedPositions = applyPositionDelta(
11093
11093
  positions,
@@ -11157,8 +11157,8 @@ function computeSumerBorrowDelta(amount3, price2, targetMarketUid, balanceData2,
11157
11157
  }
11158
11158
 
11159
11159
  // src/lending/margin/base/sumer/repay.ts
11160
- function computeSumerRepayDelta(amount3, price2, targetMarketUid, balanceData2, positions, apr, yieldParams, irMode) {
11161
- const dollarAmount = amount3 * price2;
11160
+ function computeSumerRepayDelta(amount4, price2, targetMarketUid, balanceData2, positions, apr, yieldParams, irMode) {
11161
+ const dollarAmount = amount4 * price2;
11162
11162
  const mode = irMode ?? 2;
11163
11163
  const updatedPositions = applyPositionDelta(
11164
11164
  positions,
@@ -16661,13 +16661,13 @@ var REPAIR_BACKOFF_JITTER_MS = 60;
16661
16661
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
16662
16662
  var backoffForRound = (round) => REPAIR_BACKOFF_BASE_MS * 2 ** round + Math.random() * REPAIR_BACKOFF_JITTER_MS;
16663
16663
  var endpointUrl = (client, rpcId) => client?.transport?.url ?? `rpc#${rpcId}`;
16664
- var resolveEndpoint = (chainId, getEvmClient18, startRpcId, tried, maxProbe = 12, timeoutMs) => {
16664
+ var resolveEndpoint = (chainId, getEvmClient19, startRpcId, tried, maxProbe = 12, timeoutMs) => {
16665
16665
  let fallback = null;
16666
16666
  for (let probe = 0; probe < maxProbe; probe++) {
16667
16667
  const rpcId = startRpcId + probe;
16668
16668
  let client;
16669
16669
  try {
16670
- client = getEvmClient18(chainId, rpcId, { timeoutMs });
16670
+ client = getEvmClient19(chainId, rpcId, { timeoutMs });
16671
16671
  } catch {
16672
16672
  break;
16673
16673
  }
@@ -16696,7 +16696,7 @@ var recordPermanentFailures = (slots, offset, collector) => {
16696
16696
  if (slots[i].permanent) collector.add(offset + i);
16697
16697
  }
16698
16698
  };
16699
- var repairFailedSlots = async (chainId, contracts, slots, getEvmClient18, nextRpcId, batchSize, logs, rounds = MULTICALL_REPAIR_ROUNDS, options) => {
16699
+ var repairFailedSlots = async (chainId, contracts, slots, getEvmClient19, nextRpcId, batchSize, logs, rounds = MULTICALL_REPAIR_ROUNDS, options) => {
16700
16700
  let searchFrom = nextRpcId;
16701
16701
  for (let round = 0; round < rounds; round++) {
16702
16702
  const failedIdx = [];
@@ -16706,7 +16706,7 @@ var repairFailedSlots = async (chainId, contracts, slots, getEvmClient18, nextRp
16706
16706
  if (failedIdx.length === 0) return slots;
16707
16707
  const endpoint = resolveEndpoint(
16708
16708
  chainId,
16709
- getEvmClient18,
16709
+ getEvmClient19,
16710
16710
  searchFrom,
16711
16711
  options?.tried
16712
16712
  );
@@ -16758,7 +16758,7 @@ var repairFailedSlots = async (chainId, contracts, slots, getEvmClient18, nextRp
16758
16758
  }
16759
16759
  return slots;
16760
16760
  };
16761
- var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient18, poolSize, retries = maxRetries, allowFailure = true, batchSize = MULTICALL_DEFAULT_BATCH_SIZE, logs = false, retryFailed = false, permanentFailures, options) => {
16761
+ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient19, poolSize, retries = maxRetries, allowFailure = true, batchSize = MULTICALL_DEFAULT_BATCH_SIZE, logs = false, retryFailed = false, permanentFailures, options) => {
16762
16762
  const abiIsArray = isArray(abi[0]);
16763
16763
  const contracts = calls.map(({ address, name, params }, i) => ({
16764
16764
  abi: abiIsArray ? abi?.[i] : abi,
@@ -16832,7 +16832,7 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient18, poolS
16832
16832
  const attemptHedged = async (items, startRpcId, tried) => {
16833
16833
  const primary = resolveEndpoint(
16834
16834
  chainId,
16835
- getEvmClient18,
16835
+ getEvmClient19,
16836
16836
  startRpcId,
16837
16837
  tried,
16838
16838
  12,
@@ -16856,7 +16856,7 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient18, poolS
16856
16856
  clearTimeout(timer);
16857
16857
  const alt = resolveEndpoint(
16858
16858
  chainId,
16859
- getEvmClient18,
16859
+ getEvmClient19,
16860
16860
  primary.rpcId + 1,
16861
16861
  tried,
16862
16862
  12,
@@ -16908,7 +16908,7 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient18, poolS
16908
16908
  chainId,
16909
16909
  batch.items,
16910
16910
  slots,
16911
- getEvmClient18,
16911
+ getEvmClient19,
16912
16912
  won.rpcId + 1,
16913
16913
  requestBytes,
16914
16914
  logs,
@@ -16930,7 +16930,7 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient18, poolS
16930
16930
  const tried = /* @__PURE__ */ new Set();
16931
16931
  const start = resolveEndpoint(
16932
16932
  chainId,
16933
- getEvmClient18,
16933
+ getEvmClient19,
16934
16934
  workerId,
16935
16935
  void 0,
16936
16936
  12,
@@ -29674,8 +29674,9 @@ var LENS_FAMILY = {
29674
29674
  */
29675
29675
  RESUPPLY: 15
29676
29676
  };
29677
+ var V6_WITHHELD = /* @__PURE__ */ new Set([LENS_FAMILY.AAVE_V4]);
29677
29678
  var ALL_FAMILIES = new Set(
29678
- Object.values(LENS_FAMILY)
29679
+ Object.values(LENS_FAMILY).filter((f) => !V6_WITHHELD.has(f))
29679
29680
  );
29680
29681
  var v6 = () => ({
29681
29682
  address: POSITION_LENS_V6,
@@ -34270,25 +34271,25 @@ var getFluidUserDataConverter = (lender, chainId, account, meta) => {
34270
34271
  legRaw(leg, supplyRaw).toString(),
34271
34272
  leg.decimals
34272
34273
  );
34273
- const amount3 = Number(amountStr);
34274
+ const amount4 = Number(amountStr);
34274
34275
  const e = entryFor(leg);
34275
34276
  e.deposits = amountStr;
34276
- e.depositsUSD = amount3 * leg.displayPrice;
34277
- e.depositsUSDOracle = amount3 * leg.oraclePrice;
34277
+ e.depositsUSD = amount4 * leg.displayPrice;
34278
+ e.depositsUSDOracle = amount4 * leg.oraclePrice;
34278
34279
  e.collateralEnabled = supplyRaw > 0n;
34279
- deposits24h += amount3 * leg.priceHist;
34280
+ deposits24h += amount4 * leg.priceHist;
34280
34281
  }
34281
34282
  for (const leg of debtLegs) {
34282
34283
  const amountStr = parseRawAmount(
34283
34284
  legRaw(leg, borrowRaw).toString(),
34284
34285
  leg.decimals
34285
34286
  );
34286
- const amount3 = Number(amountStr);
34287
+ const amount4 = Number(amountStr);
34287
34288
  const e = entryFor(leg);
34288
34289
  e.debt = amountStr;
34289
- e.debtUSD = amount3 * leg.displayPrice;
34290
- e.debtUSDOracle = amount3 * leg.oraclePrice;
34291
- debt24h += amount3 * leg.priceHist;
34290
+ e.debtUSD = amount4 * leg.displayPrice;
34291
+ e.debtUSDOracle = amount4 * leg.oraclePrice;
34292
+ debt24h += amount4 * leg.priceHist;
34292
34293
  }
34293
34294
  histData[nftId] = {
34294
34295
  totalDeposits24h: deposits24h,
@@ -34541,10 +34542,10 @@ var getDolomiteUserDataConverter = (lender, chainId, account, metaMap) => {
34541
34542
  const meta = metaMap?.[key3];
34542
34543
  if (!meta) return;
34543
34544
  const decimals = meta.asset?.decimals ?? 18;
34544
- const amount3 = parseRawAmount(wei.value, decimals);
34545
+ const amount4 = parseRawAmount(wei.value, decimals);
34545
34546
  const isSupply = wei.sign;
34546
- const deposits = isSupply ? amount3 : "0";
34547
- const debt = isSupply ? "0" : amount3;
34547
+ const deposits = isSupply ? amount4 : "0";
34548
+ const debt = isSupply ? "0" : amount4;
34548
34549
  const debtPar = isSupply ? "0" : pars[k]?.value?.toString() ?? "0";
34549
34550
  const price2 = getDisplayPrice(meta);
34550
34551
  const oPrice = getOraclePrice(meta);
@@ -35977,11 +35978,11 @@ var big13 = (v) => {
35977
35978
  };
35978
35979
  function fraxlendToAmount(shares, totalAmount, totalShares, roundUp) {
35979
35980
  if (totalShares === 0n) return shares;
35980
- let amount3 = shares * totalAmount / totalShares;
35981
- if (roundUp && totalAmount > 0n && amount3 * totalShares / totalAmount < shares) {
35982
- amount3 = amount3 + 1n;
35981
+ let amount4 = shares * totalAmount / totalShares;
35982
+ if (roundUp && totalAmount > 0n && amount4 * totalShares / totalAmount < shares) {
35983
+ amount4 = amount4 + 1n;
35983
35984
  }
35984
- return amount3;
35985
+ return amount4;
35985
35986
  }
35986
35987
  var getFraxlendUserDataConverter = (lender, chainId, account, meta) => {
35987
35988
  const pairs = fraxlendPairsFor(lender, chainId);
@@ -41897,7 +41898,7 @@ function unflattenLenderData(pools) {
41897
41898
  }
41898
41899
  return result;
41899
41900
  }
41900
- var getLenderUserDataResult = async (chainId, queriesRaw, getEvmClient18, allowFailure = true, batchSize = MULTICALL_DEFAULT_BATCH_SIZE, retries = 3, logs = false, concurrency = 1, permanentFailures, onEndpointFailure) => {
41901
+ var getLenderUserDataResult = async (chainId, queriesRaw, getEvmClient19, allowFailure = true, batchSize = MULTICALL_DEFAULT_BATCH_SIZE, retries = 3, logs = false, concurrency = 1, permanentFailures, onEndpointFailure) => {
41901
41902
  const queries = organizeUserQueries(queriesRaw);
41902
41903
  const builtCalls = await Promise.all(
41903
41904
  queries.map(async (query3) => {
@@ -41907,7 +41908,7 @@ var getLenderUserDataResult = async (chainId, queriesRaw, getEvmClient18, allowF
41907
41908
  query3.lender,
41908
41909
  query3.account,
41909
41910
  query3.params,
41910
- getEvmClient18
41911
+ getEvmClient19
41911
41912
  );
41912
41913
  return callData.map((call) => ({ call, abi: call.abi ?? abi }));
41913
41914
  })
@@ -41917,7 +41918,7 @@ var getLenderUserDataResult = async (chainId, queriesRaw, getEvmClient18, allowF
41917
41918
  chainId,
41918
41919
  calls.map((call) => call.abi),
41919
41920
  calls.map((call) => call.call),
41920
- getEvmClient18,
41921
+ getEvmClient19,
41921
41922
  concurrency,
41922
41923
  retries,
41923
41924
  allowFailure,
@@ -42216,29 +42217,29 @@ async function getMergedUserData(chainId, balanceQueries, permissionParams, lend
42216
42217
  });
42217
42218
  return parseMergedResult(chainId, rawResults, prepared, lenderState);
42218
42219
  }
42219
- function toCompoundV2Shares(entry, amount3) {
42220
+ function toCompoundV2Shares(entry, amount4) {
42220
42221
  const raw = entry.params?.exchangeRateCurrent;
42221
- if (!raw || raw === "0x") return amount3;
42222
+ if (!raw || raw === "0x") return amount4;
42222
42223
  try {
42223
42224
  const exRate = BigInt(raw);
42224
- if (exRate === 0n) return amount3;
42225
- return amount3 * 10n ** 18n / exRate * 1001n / 1000n;
42225
+ if (exRate === 0n) return amount4;
42226
+ return amount4 * 10n ** 18n / exRate * 1001n / 1000n;
42226
42227
  } catch {
42227
- return amount3;
42228
+ return amount4;
42228
42229
  }
42229
42230
  }
42230
- function toSiloShares(entry, amount3, chainId, lender, tokenAddress) {
42231
+ function toSiloShares(entry, amount4, chainId, lender, tokenAddress) {
42231
42232
  const raw = entry.params?.collateralRate;
42232
- if (!raw || raw === "0x") return amount3;
42233
+ if (!raw || raw === "0x") return amount4;
42233
42234
  try {
42234
42235
  const rate = BigInt(raw);
42235
- if (rate === 0n) return amount3;
42236
+ if (rate === 0n) return amount4;
42236
42237
  const half = getSiloHalfForUnderlying(chainId, lender, tokenAddress);
42237
- if (!half) return amount3;
42238
+ if (!half) return amount4;
42238
42239
  const oneUnit = BigInt(10) ** BigInt(half.decimals);
42239
- return amount3 * oneUnit / rate * 1001n / 1000n;
42240
+ return amount4 * oneUnit / rate * 1001n / 1000n;
42240
42241
  } catch {
42241
- return amount3;
42242
+ return amount4;
42242
42243
  }
42243
42244
  }
42244
42245
  function resolveDebitDataKey(chainId, lender, tokenAddress, cToken, isProtected) {
@@ -42274,7 +42275,7 @@ function resolveDebitDataKey(chainId, lender, tokenAddress, cToken, isProtected)
42274
42275
  return tokenAddress.toLowerCase();
42275
42276
  }
42276
42277
  function needsLenderApproval(params) {
42277
- const { lender, lenderDebitData, tokenAddress, amount: amount3, chainId, cToken, isProtected } = params;
42278
+ const { lender, lenderDebitData, tokenAddress, amount: amount4, chainId, cToken, isProtected } = params;
42278
42279
  if (!lenderDebitData) return true;
42279
42280
  if (isAaveV4Type(lender)) {
42280
42281
  const key4 = (params.aaveV4Spoke ?? tokenAddress).toLowerCase();
@@ -42291,33 +42292,33 @@ function needsLenderApproval(params) {
42291
42292
  const entry = lenderDebitData[key3];
42292
42293
  if (!entry || entry.amount === void 0) return true;
42293
42294
  if (isCompoundV2Type(lender) || isVenusType(lender)) {
42294
- const requiredShares = toCompoundV2Shares(entry, amount3);
42295
+ const requiredShares = toCompoundV2Shares(entry, amount4);
42295
42296
  return entry.amount < requiredShares;
42296
42297
  }
42297
42298
  if (isSiloV2Type(lender) || isSiloV3Type(lender)) {
42298
- const requiredShares = toSiloShares(entry, amount3, chainId, lender, tokenAddress);
42299
+ const requiredShares = toSiloShares(entry, amount4, chainId, lender, tokenAddress);
42299
42300
  return entry.amount < requiredShares;
42300
42301
  }
42301
42302
  if (isCompoundV3(lender)) {
42302
42303
  return entry.amount === 0n;
42303
42304
  }
42304
- return entry.amount < amount3;
42305
+ return entry.amount < amount4;
42305
42306
  }
42306
42307
  function needsTokenApproval(params) {
42307
- const { debitData, spender, amount: amount3, chainId, usePermit2 } = params;
42308
+ const { debitData, spender, amount: amount4, chainId, usePermit2 } = params;
42308
42309
  if (!debitData) return true;
42309
42310
  const spenderLower = spender.toLowerCase();
42310
42311
  if (usePermit2) {
42311
42312
  const p2 = debitData.permit2?.[spenderLower];
42312
- return !p2 || p2.amount < amount3;
42313
+ return !p2 || p2.amount < amount4;
42313
42314
  }
42314
42315
  const permit2Address = getPermit2ContractAddress(chainId).toLowerCase();
42315
42316
  if (spenderLower === permit2Address) {
42316
42317
  const p2 = debitData.permit2?.[spenderLower];
42317
- if (p2 && p2.amount >= amount3) return false;
42318
+ if (p2 && p2.amount >= amount4) return false;
42318
42319
  }
42319
42320
  const allowance = debitData.allowances?.[spenderLower] ?? 0n;
42320
- return allowance < amount3;
42321
+ return allowance < amount4;
42321
42322
  }
42322
42323
  var MORPHO_USER_SUBGRAPH_URLS = {
42323
42324
  [Chain.SEI_NETWORK]: "https://api.goldsky.com/api/public/project_cmiergfbv4vma01vb642yaeam/subgraphs/morphoblue-sei/1.0.1/gn",
@@ -42824,10 +42825,10 @@ function fuseLenderData(userDataResult, summary) {
42824
42825
  }
42825
42826
 
42826
42827
  // src/lending/margin/base/standard/deposit.ts
42827
- function computeDepositDelta(amount3, price2, balanceData2, config, modeId, createNewSubAccount = false, apr, yieldParams) {
42828
+ function computeDepositDelta(amount4, price2, balanceData2, config, modeId, createNewSubAccount = false, apr, yieldParams) {
42828
42829
  const { borrowCollateralFactor, collateralFactor } = getAssetConfig(config, modeId);
42829
42830
  const base = createNewSubAccount ? EMPTY_BALANCE : balanceData2;
42830
- const dollarAmount = amount3 * price2;
42831
+ const dollarAmount = amount4 * price2;
42831
42832
  const postTrade = {
42832
42833
  ...base,
42833
42834
  borrowDiscountedCollateral: base.borrowDiscountedCollateral + borrowCollateralFactor * dollarAmount,
@@ -42874,13 +42875,13 @@ function computeDepositDelta(amount3, price2, balanceData2, config, modeId, crea
42874
42875
  }
42875
42876
 
42876
42877
  // src/lending/margin/base/deposit.ts
42877
- function computeDepositDelta2(lender, amount3, price2, balanceData2, config, modeId, createNewSubAccount, apr, yieldParams, targetMarketUid, positions) {
42878
+ function computeDepositDelta2(lender, amount4, price2, balanceData2, config, modeId, createNewSubAccount, apr, yieldParams, targetMarketUid, positions) {
42878
42879
  if (isSumerType(lender)) {
42879
42880
  if (!positions || targetMarketUid === void 0) {
42880
42881
  throw new Error("Sumer operations require positions and targetMarketUid");
42881
42882
  }
42882
42883
  return computeSumerDepositDelta(
42883
- amount3,
42884
+ amount4,
42884
42885
  price2,
42885
42886
  targetMarketUid,
42886
42887
  balanceData2,
@@ -42894,7 +42895,7 @@ function computeDepositDelta2(lender, amount3, price2, balanceData2, config, mod
42894
42895
  throw new Error("Standard operations require config and modeId");
42895
42896
  }
42896
42897
  return computeDepositDelta(
42897
- amount3,
42898
+ amount4,
42898
42899
  price2,
42899
42900
  balanceData2,
42900
42901
  config,
@@ -42906,9 +42907,9 @@ function computeDepositDelta2(lender, amount3, price2, balanceData2, config, mod
42906
42907
  }
42907
42908
 
42908
42909
  // src/lending/margin/base/standard/withdraw.ts
42909
- function computeWithdrawDelta(amount3, price2, balanceData2, config, modeId, apr, yieldParams) {
42910
+ function computeWithdrawDelta(amount4, price2, balanceData2, config, modeId, apr, yieldParams) {
42910
42911
  const { borrowCollateralFactor, collateralFactor } = getAssetConfig(config, modeId);
42911
- const dollarAmount = amount3 * price2;
42912
+ const dollarAmount = amount4 * price2;
42912
42913
  const postTrade = {
42913
42914
  ...balanceData2,
42914
42915
  borrowDiscountedCollateral: balanceData2.borrowDiscountedCollateral - borrowCollateralFactor * dollarAmount,
@@ -42955,13 +42956,13 @@ function computeWithdrawDelta(amount3, price2, balanceData2, config, modeId, apr
42955
42956
  }
42956
42957
 
42957
42958
  // src/lending/margin/base/withdraw.ts
42958
- function computeWithdrawDelta2(lender, amount3, price2, balanceData2, config, modeId, apr, yieldParams, targetMarketUid, positions) {
42959
+ function computeWithdrawDelta2(lender, amount4, price2, balanceData2, config, modeId, apr, yieldParams, targetMarketUid, positions) {
42959
42960
  if (isSumerType(lender)) {
42960
42961
  if (!positions || targetMarketUid === void 0) {
42961
42962
  throw new Error("Sumer operations require positions and targetMarketUid");
42962
42963
  }
42963
42964
  return computeSumerWithdrawDelta(
42964
- amount3,
42965
+ amount4,
42965
42966
  price2,
42966
42967
  targetMarketUid,
42967
42968
  balanceData2,
@@ -42974,7 +42975,7 @@ function computeWithdrawDelta2(lender, amount3, price2, balanceData2, config, mo
42974
42975
  throw new Error("Standard operations require config and modeId");
42975
42976
  }
42976
42977
  return computeWithdrawDelta(
42977
- amount3,
42978
+ amount4,
42978
42979
  price2,
42979
42980
  balanceData2,
42980
42981
  config,
@@ -42985,9 +42986,9 @@ function computeWithdrawDelta2(lender, amount3, price2, balanceData2, config, mo
42985
42986
  }
42986
42987
 
42987
42988
  // src/lending/margin/base/standard/borrow.ts
42988
- function computeBorrowDelta(amount3, price2, balanceData2, config, modeId, apr, yieldParams, irMode) {
42989
+ function computeBorrowDelta(amount4, price2, balanceData2, config, modeId, apr, yieldParams, irMode) {
42989
42990
  const { borrowFactor } = getAssetConfig(config, modeId);
42990
- const dollarAmount = amount3 * price2;
42991
+ const dollarAmount = amount4 * price2;
42991
42992
  const postTrade = {
42992
42993
  ...balanceData2,
42993
42994
  debt: balanceData2.debt + dollarAmount,
@@ -43036,13 +43037,13 @@ function computeBorrowDelta(amount3, price2, balanceData2, config, modeId, apr,
43036
43037
  }
43037
43038
 
43038
43039
  // src/lending/margin/base/borrow.ts
43039
- function computeBorrowDelta2(lender, amount3, price2, balanceData2, config, modeId, apr, yieldParams, irMode, targetMarketUid, positions) {
43040
+ function computeBorrowDelta2(lender, amount4, price2, balanceData2, config, modeId, apr, yieldParams, irMode, targetMarketUid, positions) {
43040
43041
  if (isSumerType(lender)) {
43041
43042
  if (!positions || targetMarketUid === void 0) {
43042
43043
  throw new Error("Sumer operations require positions and targetMarketUid");
43043
43044
  }
43044
43045
  return computeSumerBorrowDelta(
43045
- amount3,
43046
+ amount4,
43046
43047
  price2,
43047
43048
  targetMarketUid,
43048
43049
  balanceData2,
@@ -43056,7 +43057,7 @@ function computeBorrowDelta2(lender, amount3, price2, balanceData2, config, mode
43056
43057
  throw new Error("Standard operations require config and modeId");
43057
43058
  }
43058
43059
  return computeBorrowDelta(
43059
- amount3,
43060
+ amount4,
43060
43061
  price2,
43061
43062
  balanceData2,
43062
43063
  config,
@@ -43068,9 +43069,9 @@ function computeBorrowDelta2(lender, amount3, price2, balanceData2, config, mode
43068
43069
  }
43069
43070
 
43070
43071
  // src/lending/margin/base/standard/repay.ts
43071
- function computeRepayDelta(amount3, price2, balanceData2, config, modeId, apr, yieldParams, irMode) {
43072
+ function computeRepayDelta(amount4, price2, balanceData2, config, modeId, apr, yieldParams, irMode) {
43072
43073
  const { borrowFactor } = getAssetConfig(config, modeId);
43073
- const dollarAmount = amount3 * price2;
43074
+ const dollarAmount = amount4 * price2;
43074
43075
  const postTrade = {
43075
43076
  ...balanceData2,
43076
43077
  debt: balanceData2.debt - dollarAmount,
@@ -43119,13 +43120,13 @@ function computeRepayDelta(amount3, price2, balanceData2, config, modeId, apr, y
43119
43120
  }
43120
43121
 
43121
43122
  // src/lending/margin/base/repay.ts
43122
- function computeRepayDelta2(lender, amount3, price2, balanceData2, config, modeId, apr, yieldParams, irMode, targetMarketUid, positions) {
43123
+ function computeRepayDelta2(lender, amount4, price2, balanceData2, config, modeId, apr, yieldParams, irMode, targetMarketUid, positions) {
43123
43124
  if (isSumerType(lender)) {
43124
43125
  if (!positions || targetMarketUid === void 0) {
43125
43126
  throw new Error("Sumer operations require positions and targetMarketUid");
43126
43127
  }
43127
43128
  return computeSumerRepayDelta(
43128
- amount3,
43129
+ amount4,
43129
43130
  price2,
43130
43131
  targetMarketUid,
43131
43132
  balanceData2,
@@ -43139,7 +43140,7 @@ function computeRepayDelta2(lender, amount3, price2, balanceData2, config, modeI
43139
43140
  throw new Error("Standard operations require config and modeId");
43140
43141
  }
43141
43142
  return computeRepayDelta(
43142
- amount3,
43143
+ amount4,
43143
43144
  price2,
43144
43145
  balanceData2,
43145
43146
  config,
@@ -45056,6 +45057,42 @@ var wbethFetcher = {
45056
45057
  }
45057
45058
  };
45058
45059
 
45060
+ // src/yields/intrinsic/fetchers/treehouse.ts
45061
+ var TETH_POOL_ID = "5762f4a8-bb48-45d6-90ed-2d93d1777169";
45062
+ var TAVAX_POOL_ID = "40a1e447-058d-4c5b-b567-30543ea185c3";
45063
+ var TREEHOUSE_APY_URL = "https://api.treehouse.finance/apy";
45064
+ var TETH_KEYS = ["Treehouse ETH::TETH", "Treehouse ETH::tETH"];
45065
+ var fetchTethApr = async () => {
45066
+ try {
45067
+ const res = await fetch(TREEHOUSE_APY_URL, {
45068
+ headers: { accept: "application/json" },
45069
+ signal: AbortSignal.timeout(8e3)
45070
+ });
45071
+ if (!res.ok) throw new Error(`Treehouse HTTP ${res.status}`);
45072
+ const json = await res.json();
45073
+ const apr = json?.total_apr_teth;
45074
+ if (typeof apr === "number" && Number.isFinite(apr) && apr > 0) return apr;
45075
+ throw new Error("Treehouse: no total_apr_teth");
45076
+ } catch {
45077
+ return apyToAprPercent(await fetchDefiLlamaApy(TETH_POOL_ID));
45078
+ }
45079
+ };
45080
+ var tethFetcher = {
45081
+ label: "TETH",
45082
+ fetch: async () => {
45083
+ const apr = await fetchTethApr();
45084
+ return Object.fromEntries(TETH_KEYS.map((k) => [k, apr]));
45085
+ }
45086
+ };
45087
+ var tavaxFetcher = {
45088
+ label: "TAVAX",
45089
+ fetch: async () => ({
45090
+ "Treehouse AVAX::tAVAX": apyToAprPercent(
45091
+ await fetchDefiLlamaApy(TAVAX_POOL_ID)
45092
+ )
45093
+ })
45094
+ };
45095
+
45059
45096
  // src/vaults/lst/registry.ts
45060
45097
  var LST_REGISTRY = {
45061
45098
  // Monad (143) — native-MON LSTs. shMON / aprMON are ERC-4626 over native
@@ -45144,6 +45181,50 @@ var LST_REGISTRY = {
45144
45181
  }
45145
45182
  ],
45146
45183
  "1": [
45184
+ {
45185
+ // Treehouse tETH — an ERC-4626 share over wstETH that runs a
45186
+ // leveraged staking carry (wstETH collateral on Aave Core / Aave
45187
+ // Prime / Spark, ETH borrowed against it, converted back to wstETH).
45188
+ //
45189
+ // THREE things the generic 4626 path gets wrong here:
45190
+ // 1. `asset()` is NOT wstETH — it returns
45191
+ // `InternalAccountingUnit_Wrapped liquid staked Ether 2.0`
45192
+ // (`IAU_wstETH`, 0x1b6238e9…), a synthetic unit minted 1:1
45193
+ // against the real underlying. The underlying below is
45194
+ // `Vault.getUnderlying()`; never read `asset()`.
45195
+ // 2. Entry is the `TreehouseRouter`, not the share token. It takes
45196
+ // native ETH (`depositETH()`) or any `Vault.isAllowableAsset`
45197
+ // ERC-20 (stETH / WETH / wstETH) via `deposit(asset, amount)`,
45198
+ // and **credits `msg.sender`** — there is no receiver argument.
45199
+ // 3. Exit is neither of the share's own 4626 legs: `maxWithdraw`
45200
+ // and `maxRedeem` both read 0. It is the Fastlane (instant,
45201
+ // 50 bps) or RedemptionV2 (queued, 5 bps) — hence
45202
+ // `fee-or-queued`, with `liquidity` measured by the reader off
45203
+ // `Fastlane.getRedeemableAmount()`.
45204
+ address: "0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8",
45205
+ underlying: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",
45206
+ // wstETH
45207
+ symbol: "tETH",
45208
+ brand: "Treehouse",
45209
+ decimals: 18,
45210
+ reader: "treehouseTAsset",
45211
+ isErc4626: true,
45212
+ isRebasing: false,
45213
+ isMintable: true,
45214
+ isNativeUnderlying: true,
45215
+ mintContract: "0xefa3fa8e85d2b3cfdb250cdea156c2c6c90628f5",
45216
+ // TreehouseRouter
45217
+ mintInputAsset: "native",
45218
+ withdrawalMode: "fee-or-queued",
45219
+ // Read live by the reader (7 d today, and it is an owner dial).
45220
+ withdrawalCooldownSeconds: 7 * 86400,
45221
+ yieldFetcher: tethFetcher,
45222
+ yieldKey: "Treehouse ETH::TETH",
45223
+ readerExtras: {
45224
+ treehouseFastlane: "0x829525417cd78cba0f99a8736426fc299506c0d6",
45225
+ treehouseRedemption: "0xcd63a29faff07130d3af89bb4f40778938aabb85"
45226
+ }
45227
+ },
45147
45228
  {
45148
45229
  address: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",
45149
45230
  underlying: "0x0000000000000000000000000000000000000000",
@@ -45816,6 +45897,38 @@ var LST_REGISTRY = {
45816
45897
  }
45817
45898
  ],
45818
45899
  "43114": [
45900
+ {
45901
+ // Treehouse tAVAX — the tETH design on Avalanche, denominated in
45902
+ // BENQI sAVAX. Same Router / Vault / RedemptionController stack; see
45903
+ // the tETH entry on chain 1 for the three traps.
45904
+ //
45905
+ // The Avalanche Router carries NO `depositETH()` (selector-scanned
45906
+ // against the live runtime), so native AVAX is not an entry here —
45907
+ // the allowable set is sAVAX and WAVAX only.
45908
+ address: "0x14a84f1a61ccd7d1be596a6cc11fe33a36bc1646",
45909
+ underlying: "0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be",
45910
+ // sAVAX
45911
+ symbol: "tAVAX",
45912
+ brand: "Treehouse",
45913
+ decimals: 18,
45914
+ reader: "treehouseTAsset",
45915
+ isErc4626: true,
45916
+ isRebasing: false,
45917
+ isMintable: true,
45918
+ isNativeUnderlying: false,
45919
+ mintContract: "0x5f4d2e6c118b5e3c74f0b61de40f627ca9873d6e",
45920
+ // TreehouseRouter
45921
+ mintInputAsset: "0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be",
45922
+ withdrawalMode: "fee-or-queued",
45923
+ // 17 d live — NOT the 7 d the docs describe. Read live by the reader.
45924
+ withdrawalCooldownSeconds: 17 * 86400,
45925
+ yieldFetcher: tavaxFetcher,
45926
+ yieldKey: "Treehouse AVAX::tAVAX",
45927
+ readerExtras: {
45928
+ treehouseFastlane: "0x3d00a639183b07e35efef044ee6cc14e8598a01c",
45929
+ treehouseRedemption: "0x765f6dc8496ca7ef1e4a391be10185229aacf04b"
45930
+ }
45931
+ },
45819
45932
  {
45820
45933
  address: "0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be",
45821
45934
  underlying: "0x0000000000000000000000000000000000000000",
@@ -45857,6 +45970,40 @@ var LST_REGISTRY = {
45857
45970
  }
45858
45971
  ],
45859
45972
  "999": [
45973
+ {
45974
+ // Treehouse tHYPE — the tETH design on HyperEVM, denominated in
45975
+ // Kinetiq kHYPE. Same stack; see the tETH entry on chain 1.
45976
+ //
45977
+ // No `depositETH()` on this Router either (selector-scanned), so the
45978
+ // entry set is kHYPE and WHYPE (0x5555…), never native HYPE.
45979
+ //
45980
+ // NO yield fetcher: DefiLlama carries no `treehouse-protocol` pool
45981
+ // for tHYPE and Treehouse's own `/apy` route serves tETH only, so the
45982
+ // row reports a `supplyRate` of 0 rather than an invented number.
45983
+ // The vault launched recently (share price 1.0007) — revisit with an
45984
+ // archival `convertToAssets` delta once it has real history.
45985
+ address: "0xb83d27f0f4133a7b0377a88089583131a903fdc7",
45986
+ underlying: "0xfd739d4e423301ce9385c1fb8850539d657c296d",
45987
+ // kHYPE
45988
+ symbol: "tHYPE",
45989
+ brand: "Treehouse",
45990
+ decimals: 18,
45991
+ reader: "treehouseTAsset",
45992
+ isErc4626: true,
45993
+ isRebasing: false,
45994
+ isMintable: true,
45995
+ isNativeUnderlying: false,
45996
+ mintContract: "0x1620b7e5c9099c8bcbc326b55e0b8440eda9d30a",
45997
+ // TreehouseRouter
45998
+ mintInputAsset: "0xfd739d4e423301ce9385c1fb8850539d657c296d",
45999
+ withdrawalMode: "fee-or-queued",
46000
+ // 10 d live — again not the docs' 7 d. Read live by the reader.
46001
+ withdrawalCooldownSeconds: 10 * 86400,
46002
+ readerExtras: {
46003
+ treehouseFastlane: "0x3018e0747549835faa8d06cf4809b1a2519fab31",
46004
+ treehouseRedemption: "0xcebbb011c2f29b0ed25895c54662bd1ccad30280"
46005
+ }
46006
+ },
45860
46007
  {
45861
46008
  // hbHYPE Ultra — Hyperbeat's ERC-4626 staked HYPE vault.
45862
46009
  // TokenizedAccount impl, 4626-compliant, accepts WHYPE.
@@ -47525,6 +47672,84 @@ var strataFetcher = {
47525
47672
  return out;
47526
47673
  }
47527
47674
  };
47675
+ var thBill = "Theo Short Duration US Treasury Fund::THBILL";
47676
+ var THBILL_DEFILLAMA_POOL = "e17dab50-85b1-45e8-b4a5-b8ae7898c4a3";
47677
+ var thbillFetcher = {
47678
+ label: "THBILL",
47679
+ fetch: async () => {
47680
+ const apy = await fetchDefiLlamaApy(THBILL_DEFILLAMA_POOL);
47681
+ return { [thBill]: apyToAprPercent(apy) };
47682
+ }
47683
+ };
47684
+ var STHUSD_VAULT = "0xa808bc9775cb41c52c7842f8b50427fe7a770326";
47685
+ var STHUSD_UNIT = 10n ** 6n;
47686
+ var STHUSD_WINDOW_SECONDS = 30 * 24 * 60 * 60;
47687
+ var ETH_BLOCK_TIME_SECONDS = 12;
47688
+ var STHUSD_WINDOW_BLOCKS = BigInt(
47689
+ Math.floor(STHUSD_WINDOW_SECONDS / ETH_BLOCK_TIME_SECONDS)
47690
+ );
47691
+ var STHUSD_MAX_RPC_TRIES = 2;
47692
+ var STHUSD_DEADLINE_MS = 12e3;
47693
+ var STHUSD_KEY = "STHUSD";
47694
+ var STHUSD_GROUP_KEY = "Staked thUSD::STHUSD";
47695
+ var STHUSD_GROUP_KEY_OFT = "sthUSD::sthUSD";
47696
+ var CONVERT_TO_ASSETS_ABI4 = [
47697
+ {
47698
+ name: "convertToAssets",
47699
+ type: "function",
47700
+ stateMutability: "view",
47701
+ inputs: [{ type: "uint256" }],
47702
+ outputs: [{ type: "uint256" }]
47703
+ }
47704
+ ];
47705
+ var readSthUsdRateAt = (client, blockNumber) => client.readContract({
47706
+ address: STHUSD_VAULT,
47707
+ abi: CONVERT_TO_ASSETS_ABI4,
47708
+ functionName: "convertToAssets",
47709
+ args: [STHUSD_UNIT],
47710
+ ...blockNumber !== void 0 ? { blockNumber } : {}
47711
+ });
47712
+ var computeSthUsdApr = async () => {
47713
+ let lastErr;
47714
+ const deadline = Date.now() + STHUSD_DEADLINE_MS;
47715
+ for (let rpcId = 0; rpcId < STHUSD_MAX_RPC_TRIES; rpcId++) {
47716
+ if (Date.now() > deadline) break;
47717
+ try {
47718
+ const client = getEvmClient("1", rpcId);
47719
+ const head = await client.getBlockNumber();
47720
+ const pastBlock = head > STHUSD_WINDOW_BLOCKS ? head - STHUSD_WINDOW_BLOCKS : 0n;
47721
+ const [rateNow, rateThen, headBlock, thenBlock] = await Promise.all([
47722
+ readSthUsdRateAt(client),
47723
+ readSthUsdRateAt(client, pastBlock),
47724
+ client.getBlock({ blockNumber: head }),
47725
+ client.getBlock({ blockNumber: pastBlock })
47726
+ ]);
47727
+ if (rateNow < STHUSD_UNIT || rateThen < STHUSD_UNIT) {
47728
+ throw new Error("sthUSD: share price below par \u2014 pruned or bad read");
47729
+ }
47730
+ if (rateNow < rateThen) {
47731
+ throw new Error("sthUSD: share price decreased \u2014 non-archival RPC");
47732
+ }
47733
+ const elapsed = Number(headBlock.timestamp - thenBlock.timestamp);
47734
+ if (elapsed <= 0) throw new Error("sthUSD: non-positive window");
47735
+ return annualizeRateDeltaPercent(rateNow, rateThen, elapsed);
47736
+ } catch (e) {
47737
+ lastErr = e;
47738
+ }
47739
+ }
47740
+ throw lastErr ?? new Error("sthUSD: no RPC served archive state");
47741
+ };
47742
+ var sthusdFetcher = {
47743
+ label: "STHUSD",
47744
+ fetch: async () => {
47745
+ const apr = await computeSthUsdApr().catch(() => 0);
47746
+ return {
47747
+ [STHUSD_KEY]: apr,
47748
+ [STHUSD_GROUP_KEY]: apr,
47749
+ [STHUSD_GROUP_KEY_OFT]: apr
47750
+ };
47751
+ }
47752
+ };
47528
47753
  var RAY9 = 10n ** 27n;
47529
47754
  var YEAR_SECONDS11 = 31536e3;
47530
47755
  var DSR_ABI = [
@@ -50518,6 +50743,87 @@ var SINGLE_CHAIN_ENTRIES = {
50518
50743
  solvency: "overcollateralized",
50519
50744
  yieldFetcher: frankencoinSavingsFetcher,
50520
50745
  yieldKey: zchfSavingsKey(Chain.ETHEREUM_MAINNET)
50746
+ },
50747
+ {
50748
+ // Theo **sthUSD** — the ERC-4626 staked wrapper over `thUSD`, Theo's
50749
+ // gold-yield stablecoin. Source Sourcify-verified on chain 1
50750
+ // (`contracts/thusd/SthUSD.sol` behind the UUPS proxy, impl
50751
+ // `0x342487f9…`), so every claim below is read off the code rather
50752
+ // than inferred.
50753
+ //
50754
+ // $24.20M held / 23.83M shares, price 1.015636 (2026-08-25). Ethereum
50755
+ // is the ONLY vault: `0xd1db2090…` is a LayerZero OFT Adapter that
50756
+ // LOCKS these shares and mints a bare OFT at the same address on
50757
+ // Stable (988), Arbitrum, BNB and Mantle — and **97 % of the supply
50758
+ // has left for Stable**, where the mirror is the largest Morpho Blue
50759
+ // collateral on the chain. See the `'988'` row for that side.
50760
+ //
50761
+ // **The yield is a PUSH, not a stream, and the row must not imply
50762
+ // otherwise.** A `yieldDistributor` calls `setYield(amount)`, which
50763
+ // reverts while `getUnvestedAmount() > 0`, so batches cannot overlap;
50764
+ // each vests over `vestingDuration` (10,800 s = 3 h) and then nothing
50765
+ // accrues until the next push. Measured daily on-chain, the share
50766
+ // price steps by at most +142e-6/day (≈ 5.10 % APR) with flat
50767
+ // stretches of up to nine days. `sthusdFetcher` reads a 30-day
50768
+ // trailing delta for exactly this reason — see its docstring before
50769
+ // reaching for a shorter window.
50770
+ //
50771
+ // **The exit is asynchronous and the wait is not free.**
50772
+ // `initiateRedeem(shares, owner)` is permissionless, but it BURNS the
50773
+ // shares immediately and snapshots `assets = convertToAssets(shares)`
50774
+ // at that block; the claim (`redeem`/`withdraw`) unlocks
50775
+ // `lockupPeriod` later and pays that frozen figure, so the five days
50776
+ // earn nothing and there is no cancel path. The claim additionally
50777
+ // requires `msg.sender == owner_` (`OwnerMustCompleteRedeem`), and
50778
+ // `initiateRedeem` books the request under the CALLER — so a
50779
+ // third-party redeem via allowance lands the request on the caller,
50780
+ // not the holder. Hence `request-based`, never `instant`.
50781
+ //
50782
+ // TRAP: **`previewRedeem` and `previewWithdraw` are hardcoded
50783
+ // `return 0`** — the protocol deprecated them in favour of
50784
+ // `maxWithdraw`/`maxRedeem`, which describe only a CLAIMABLE REQUEST
50785
+ // and so read 0 for every ordinary holder, including one sitting on
50786
+ // 23.2M shares for 27 days. Neither view can size an exit here, and
50787
+ // both are the obvious thing to reach for. `previewDeposit` is fine.
50788
+ //
50789
+ // `erc4626-cooldown` with `cooldownGetter: 'lockupPeriod'`: the pin
50790
+ // below is a snapshot of an `onlyOwner` dial with no timelock whose
50791
+ // `MAX_LOCKUP_PERIOD` is **30 days** — six times what is configured
50792
+ // today. Read it live, like Brix's.
50793
+ //
50794
+ // `nav-attested`, CURATED — the one field on this row that must not be
50795
+ // left to the builder's default. `totalAssets()` is honest about the
50796
+ // vault (its own thUSD balance, net of unvested yield and pending
50797
+ // redemptions), so the SHARES are backed by the asset; the trust
50798
+ // question sits one level down, in what backs thUSD — a gold-leasing
50799
+ // and CME-futures-roll book run off-chain and marked by the issuer,
50800
+ // with no on-chain solvency invariant anywhere. Absent, the builder
50801
+ // falls back to `overcollateralized` and only whispers the doubt
50802
+ // through `coverage.pending.counterparty`: a row that says
50803
+ // "overcollateralized" about attested gold is wrong in the one field a
50804
+ // reader checks before trusting the rate. Naming it also earns the
50805
+ // consequences — `nav-drawdown` in `principal.risks`, the
50806
+ // `nav-accrual` rate kind — which are all true here: the share price
50807
+ // moves only when a distributor chooses to push a batch. Same call as
50808
+ // Brix wiTRY.
50809
+ reader: "erc4626-cooldown",
50810
+ solvency: "nav-attested",
50811
+ cooldownGetter: "lockupPeriod",
50812
+ address: "0xa808bc9775cb41c52c7842f8b50427fe7a770326",
50813
+ underlying: "0xa3fe5c7596024e6811e14f029937d5bd8ae485b3",
50814
+ // thUSD
50815
+ symbol: "sthUSD",
50816
+ brand: "Theo",
50817
+ description: "thUSD is Theo's gold-yield stablecoin \u2014 the return comes from leasing physical gold and rolling CME futures, not from lending, and the book runs off-chain with no on-chain solvency invariant. sthUSD stakes it with an appreciating share price, but a distributor PUSHES each batch, so the price steps rather than streams and can sit flat for a week. Deposits are permissionless and uncapped. Exiting is a two-step request: initiating burns the shares and fixes the payout at that block's price, and the claim unlocks 5 days later \u2014 the wait earns nothing and cannot be cancelled.",
50818
+ decimals: 6,
50819
+ isRebasing: false,
50820
+ isMintable: true,
50821
+ withdrawalMode: "request-based",
50822
+ // `lockupPeriod()` = 432,000 s on 2026-08-25. Re-read live by the
50823
+ // reader; this is only the fallback if that call fails.
50824
+ withdrawalCooldownSeconds: 432e3,
50825
+ yieldFetcher: sthusdFetcher,
50826
+ yieldKey: STHUSD_KEY
50521
50827
  }
50522
50828
  ],
50523
50829
  "100": [
@@ -50743,6 +51049,70 @@ var SINGLE_CHAIN_ENTRIES = {
50743
51049
  yieldKey: sparkVaultV2Key("43114", "spUSDC")
50744
51050
  }
50745
51051
  ],
51052
+ // ---------------------------------------------------------------------
51053
+ // Stable (988) — a Tether-adjacent L1 whose whole lending surface is one
51054
+ // Morpho Blue deployment (`0xa40103…`), and whose largest market by an
51055
+ // order of magnitude is sthUSD/USDT0: $23.03M of collateral against
51056
+ // $18.59M supplied / $16.81M borrowed at 86 % LLTV. The earn row and that
51057
+ // collateral leg are the same asset, so registering it here is also what
51058
+ // gives the pair book a non-zero carry on the chain's biggest position.
51059
+ // ---------------------------------------------------------------------
51060
+ "988": [
51061
+ {
51062
+ // Theo **sthUSD**, bridged. This address is NOT the vault — it is the
51063
+ // LayerZero OFT minted against shares locked in the Ethereum OFT
51064
+ // Adapter at the same address, and it is a bare ERC-20 with a
51065
+ // blacklist and a pause: `asset()`, `totalAssets()`, `convertToAssets()`
51066
+ // and every `max*` view revert. See the chain-1 row for the vault,
51067
+ // its push-yield mechanism and its two-step exit.
51068
+ //
51069
+ // Priced by RedStone's **"RedStone Price Feed for sthUSD_FUNDAMENTAL"**
51070
+ // `0xb81131b6…` — the same feed Morpho's own oracle for this market
51071
+ // reads (`BASE_FEED_1` under a 1e28 `SCALE_FACTOR`), so the earn row
51072
+ // and the lending row cannot disagree about what a share is worth. It
51073
+ // is Chainlink-shaped and fresh, but answers in **8 decimals** (hence
51074
+ // `priceOracleDecimals`) and pins `roundId` at 1, i.e. NO round
51075
+ // history — an on-chain rate delta is impossible here, which is one
51076
+ // more reason `sthusdFetcher` measures the Ethereum vault instead.
51077
+ //
51078
+ // `underlying` is USDT0, the chain's dollar and this market's loan
51079
+ // asset: thUSD itself was never bridged to Stable, so there is no
51080
+ // local asset to name. Same call as Re's rows, which point at USDC.
51081
+ //
51082
+ // `isMintable: false` states a plain fact rather than a permission:
51083
+ // there is no mint function on an OFT and no vault on this chain, so
51084
+ // the only way in is to bridge from Ethereum. And it is deliberately
51085
+ // NOT `secondaryMarketOnly` — that flag promises a tradeable market,
51086
+ // and there isn't one: **23,216,009 of the 23,216,028 supply
51087
+ // (99.99997 %) sits inside Morpho Blue as collateral**, leaving ~19
51088
+ // sthUSD of free float across the whole chain. A `via: 'swap'` route
51089
+ // would advertise depth that does not exist.
51090
+ reader: "nav-oracle",
51091
+ // Same curated answer as the chain-1 row — one asset cannot have two
51092
+ // trust stories. Here it is doubly right: this side's price IS an
51093
+ // attestation, published by RedStone rather than derived from anything
51094
+ // the chain holds.
51095
+ solvency: "nav-attested",
51096
+ address: "0xd1db209087516883ec705cfeb99e80bb6032d540",
51097
+ underlying: "0x779ded0c9e1022225f8e0630b35a9b54be713736",
51098
+ // USDT0
51099
+ priceOracle: "0xb81131b6368b3f0a83af09db4e39ac23da96c2db",
51100
+ priceOracleDecimals: 8,
51101
+ symbol: "sthUSD",
51102
+ brand: "Theo",
51103
+ description: "Bridged sthUSD \u2014 Theo's staked gold-yield dollar, whose vault lives on Ethereum; this is the LayerZero token minted against shares locked there, priced by RedStone's sthUSD NAV feed. It holds 97 % of the supply and is the largest Morpho Blue collateral on Stable. There is no vault on this chain: entering and exiting both mean bridging to Ethereum, where redemption is a request that burns the shares at today's price and pays out 5 days later.",
51104
+ decimals: 6,
51105
+ underlyingDecimals: 6,
51106
+ isRebasing: false,
51107
+ isMintable: false,
51108
+ withdrawalMode: "request-based",
51109
+ // The Ethereum vault's `lockupPeriod`, plus a bridge hop each way that
51110
+ // this number does not include.
51111
+ withdrawalCooldownSeconds: 432e3,
51112
+ yieldFetcher: sthusdFetcher,
51113
+ yieldKey: STHUSD_KEY
51114
+ }
51115
+ ],
50746
51116
  "4663": [
50747
51117
  {
50748
51118
  // Spark Vaults V2 spUSDG on Robinhood Chain — the first V2 vault
@@ -50861,17 +51231,6 @@ var jitoSolFetcher = {
50861
51231
  }
50862
51232
  };
50863
51233
 
50864
- // src/yields/intrinsic/fetchers/theo.ts
50865
- var thBill = "Theo Short Duration US Treasury Fund::THBILL";
50866
- var THBILL_DEFILLAMA_POOL = "e17dab50-85b1-45e8-b4a5-b8ae7898c4a3";
50867
- var thbillFetcher = {
50868
- label: "THBILL",
50869
- fetch: async () => {
50870
- const apy = await fetchDefiLlamaApy(THBILL_DEFILLAMA_POOL);
50871
- return { [thBill]: apyToAprPercent(apy) };
50872
- }
50873
- };
50874
-
50875
51234
  // src/yields/intrinsic/fetchers/superreturn.ts
50876
51235
  var SSUPERUSD_URL = "https://www.superreturn.ai/api/trpc/cmc.getUSDCPrice,rewardBackend.getYield,rewardBackend.getTvlChange?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%2C%221%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%2C%222%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%7D";
50877
51236
  var ssuperusd = "SuperReturn sSuperUSD::SSUPERUSD";
@@ -51586,25 +51945,14 @@ var lsethFetcher = {
51586
51945
  }
51587
51946
  };
51588
51947
 
51589
- // src/yields/intrinsic/fetchers/treehouse.ts
51590
- var TETH_POOL_ID = "5762f4a8-bb48-45d6-90ed-2d93d1777169";
51591
- var KEYS2 = ["Treehouse ETH::tETH"];
51592
- var tethFetcher = {
51593
- label: "TETH",
51594
- fetch: async () => {
51595
- const apr = apyToAprPercent(await fetchDefiLlamaApy(TETH_POOL_ID));
51596
- return Object.fromEntries(KEYS2.map((k) => [k, apr]));
51597
- }
51598
- };
51599
-
51600
51948
  // src/yields/intrinsic/fetchers/anzen.ts
51601
51949
  var SUSDZ_POOL_ID = "46518e93-8197-4539-9861-2340c2ac965a";
51602
- var KEYS3 = ["Anzen Staked USDz::SUSDZ", "Anzen Staked USDz::sUSDz", "Staked USDz::sUSDz"];
51950
+ var KEYS2 = ["Anzen Staked USDz::SUSDZ", "Anzen Staked USDz::sUSDz", "Staked USDz::sUSDz"];
51603
51951
  var susdzFetcher = {
51604
51952
  label: "SUSDZ",
51605
51953
  fetch: async () => {
51606
51954
  const apr = apyToAprPercent(await fetchDefiLlamaApy(SUSDZ_POOL_ID));
51607
- return Object.fromEntries(KEYS3.map((k) => [k, apr]));
51955
+ return Object.fromEntries(KEYS2.map((k) => [k, apr]));
51608
51956
  }
51609
51957
  };
51610
51958
 
@@ -51617,6 +51965,7 @@ var LENDING_ONLY_FETCHERS = [
51617
51965
  ethZeroFetcher,
51618
51966
  jitoSolFetcher,
51619
51967
  thbillFetcher,
51968
+ sthusdFetcher,
51620
51969
  ssuperusdFetcher,
51621
51970
  hlpFetcher,
51622
51971
  hwhlpFetcher,
@@ -51631,7 +51980,10 @@ var LENDING_ONLY_FETCHERS = [
51631
51980
  // it reaches the global map via `collectVaultFetchers` (disjointness rule).
51632
51981
  syzusdFetcher,
51633
51982
  midasFetcher,
51634
- capFetcher,
51983
+ // capFetcher is NOT here: the Cap stcUSD savings row vault-wires it, so it
51984
+ // reaches the global map via `collectVaultFetchers` (disjointness rule).
51985
+ // It was double-listed, which the registry test flags — the union dedupes
51986
+ // by reference, so removing it here changes nothing at runtime.
51635
51987
  nestFetcher,
51636
51988
  accountableFetcher,
51637
51989
  upshiftFetcher,
@@ -51640,7 +51992,9 @@ var LENDING_ONLY_FETCHERS = [
51640
51992
  yearnFetcher,
51641
51993
  // DefiLlama-mapped intrinsic yields for classifier assets lacking a bespoke source
51642
51994
  lsethFetcher,
51643
- tethFetcher,
51995
+ // tethFetcher / tavaxFetcher are NOT here: the Treehouse tAsset LST rows
51996
+ // vault-wire them, so they reach the global map via `collectVaultFetchers`
51997
+ // (disjointness rule).
51644
51998
  // strataFetcher is NOT here: the Strata tranche savings rows vault-wire
51645
51999
  // it, so it reaches the global map via `collectVaultFetchers`.
51646
52000
  susdzFetcher
@@ -53467,9 +53821,9 @@ function getTermCalls(chainId) {
53467
53821
  }
53468
53822
  }
53469
53823
  const results = [];
53470
- for (const [token, { amount: amount3, repoKeys }] of byToken) {
53824
+ for (const [token, { amount: amount4, repoKeys }] of byToken) {
53471
53825
  const calls = [
53472
- { address: oracle, name: "usdValueOfTokens", params: [token, amount3] }
53826
+ { address: oracle, name: "usdValueOfTokens", params: [token, amount4] }
53473
53827
  ];
53474
53828
  results.push({
53475
53829
  calls,
@@ -61680,6 +62034,165 @@ var readerKintsuSMon = (entry) => ({
61680
62034
  }
61681
62035
  });
61682
62036
 
62037
+ // src/vaults/lst/abis/treehouse.ts
62038
+ var TreehouseFastlaneAbi = [
62039
+ {
62040
+ name: "getRedeemableAmount",
62041
+ type: "function",
62042
+ stateMutability: "view",
62043
+ inputs: [],
62044
+ outputs: [{ type: "uint256" }]
62045
+ },
62046
+ {
62047
+ // Delegates to a swappable fee contract, so it is a live read even
62048
+ // though the Fastlane itself is behind the timelock.
62049
+ name: "redemptionFee",
62050
+ type: "function",
62051
+ stateMutability: "view",
62052
+ inputs: [],
62053
+ outputs: [{ type: "uint256" }]
62054
+ },
62055
+ {
62056
+ name: "minRedeemInUnderlying",
62057
+ type: "function",
62058
+ stateMutability: "view",
62059
+ inputs: [],
62060
+ outputs: [{ type: "uint96" }]
62061
+ }
62062
+ ];
62063
+ var TreehouseRedemptionAbi = [
62064
+ {
62065
+ name: "waitingPeriod",
62066
+ type: "function",
62067
+ stateMutability: "view",
62068
+ inputs: [],
62069
+ outputs: [{ type: "uint32" }]
62070
+ },
62071
+ {
62072
+ // The QUEUED leg charges too (5 bps). That contradicts the convention
62073
+ // `SavingsVault.withdrawFeeBps` documents — "the queued leg pays at par"
62074
+ // — which is exactly why the route list carries a fee PER LEG instead of
62075
+ // one field plus an assumption.
62076
+ name: "redemptionFee",
62077
+ type: "function",
62078
+ stateMutability: "view",
62079
+ inputs: [],
62080
+ outputs: [{ type: "uint32" }]
62081
+ },
62082
+ {
62083
+ // The gate that decides whether the cheap leg exists for a given holder:
62084
+ // 50 wstETH / 4,000 sAVAX / 1,000 kHYPE, checked against
62085
+ // `previewRedeem(shares)`, i.e. in UNDERLYING units.
62086
+ name: "minRedeemInUnderlying",
62087
+ type: "function",
62088
+ stateMutability: "view",
62089
+ inputs: [],
62090
+ outputs: [{ type: "uint96" }]
62091
+ }
62092
+ ];
62093
+
62094
+ // src/vaults/lst/readers/treehouse.ts
62095
+ var asBps = (v) => v === void 0 ? void 0 : Number(v);
62096
+ var fmt = (raw, decimals) => raw === void 0 ? void 0 : Number(raw) / 10 ** decimals;
62097
+ var readerTreehouseTAsset = (entry) => {
62098
+ const fastlane = entry.readerExtras?.treehouseFastlane;
62099
+ const redemption = entry.readerExtras?.treehouseRedemption;
62100
+ const fl = fastlane ?? entry.address;
62101
+ const rd = redemption ?? entry.address;
62102
+ const underlyingDec = entry.underlyingDecimals ?? entry.decimals;
62103
+ return {
62104
+ calls: [
62105
+ { address: entry.address, name: "totalAssets", params: [] },
62106
+ { address: entry.address, name: "totalSupply", params: [] },
62107
+ { address: entry.address, name: "convertToAssets", params: [ONE_E1811] },
62108
+ { address: fl, name: "getRedeemableAmount", params: [] },
62109
+ { address: fl, name: "redemptionFee", params: [] },
62110
+ { address: fl, name: "minRedeemInUnderlying", params: [] },
62111
+ { address: rd, name: "waitingPeriod", params: [] },
62112
+ { address: rd, name: "redemptionFee", params: [] },
62113
+ { address: rd, name: "minRedeemInUnderlying", params: [] }
62114
+ ],
62115
+ abis: [
62116
+ Erc4626ReadAbi,
62117
+ TotalSupplyAbi,
62118
+ Erc4626ReadAbi,
62119
+ TreehouseFastlaneAbi,
62120
+ TreehouseFastlaneAbi,
62121
+ TreehouseFastlaneAbi,
62122
+ TreehouseRedemptionAbi,
62123
+ TreehouseRedemptionAbi,
62124
+ TreehouseRedemptionAbi
62125
+ ],
62126
+ parse: (slice2) => {
62127
+ const [
62128
+ assets,
62129
+ supply,
62130
+ rate,
62131
+ redeemable,
62132
+ fastFee,
62133
+ fastMin,
62134
+ waiting,
62135
+ queueFee,
62136
+ queueMin
62137
+ ] = slice2;
62138
+ const totalAssets = toBigInt13(assets);
62139
+ const totalSupply = toBigInt13(supply);
62140
+ const exchangeRate = toBigInt13(rate);
62141
+ if (totalAssets === void 0 || totalSupply === void 0 || exchangeRate === void 0) {
62142
+ return void 0;
62143
+ }
62144
+ const liquidity = toBigInt13(redeemable) ?? 0n;
62145
+ const wait = toBigInt13(waiting);
62146
+ const instantFeeBps = asBps(toBigInt13(fastFee));
62147
+ const queuedFeeBps = asBps(toBigInt13(queueFee));
62148
+ const instantMin = toBigInt13(fastMin);
62149
+ const queuedMin = toBigInt13(queueMin);
62150
+ const instant = {
62151
+ id: "instant",
62152
+ kind: "instant",
62153
+ label: "Fast redemption",
62154
+ settlement: "sync",
62155
+ feeBps: instantFeeBps,
62156
+ waitSeconds: 0,
62157
+ // `0` is the live value on all three chains and means "no floor" —
62158
+ // emitted rather than dropped, because on this protocol the ABSENCE
62159
+ // of a minimum on the expensive leg is the reason it is the only exit
62160
+ // most holders have.
62161
+ minAmount: instantMin?.toString(),
62162
+ minAmountFormatted: fmt(instantMin, underlyingDec),
62163
+ capacity: liquidity.toString(),
62164
+ capacityFormatted: Number(liquidity) / 10 ** underlyingDec,
62165
+ selfOnly: true,
62166
+ description: "Burns the tAsset and pays the underlying in the same transaction, out of the vault balance not already earmarked by queued requests. Bounded by that balance \u2014 a larger redemption reverts rather than partially filling."
62167
+ };
62168
+ const queued = {
62169
+ id: "queued",
62170
+ kind: "queued",
62171
+ label: wait !== void 0 ? `${Math.round(Number(wait) / 86400)}-day queue` : "Queued redemption",
62172
+ settlement: "async",
62173
+ feeBps: queuedFeeBps,
62174
+ waitSeconds: wait !== void 0 ? Number(wait) : void 0,
62175
+ minAmount: queuedMin?.toString(),
62176
+ minAmountFormatted: fmt(queuedMin, underlyingDec),
62177
+ selfOnly: true,
62178
+ description: "Escrows the shares now and pays after the wait. Far cheaper, but it enforces a minimum size, and it settles at the WORSE of the rate at request and at claim \u2014 so the amount quoted when you file it is a ceiling, not a quote."
62179
+ };
62180
+ return {
62181
+ totalAssets,
62182
+ totalSupply,
62183
+ exchangeRate,
62184
+ liquidity,
62185
+ withdrawalCooldownSeconds: wait !== void 0 ? Number(wait) : void 0,
62186
+ withdrawFeeBps: instantFeeBps,
62187
+ // Cheapest reachable leg first is the list's ordering rule, and here
62188
+ // the cheap one is second on purpose: it is gated by a minimum that
62189
+ // most holders do not clear, so the leg anyone can take leads.
62190
+ exitRoutes: [instant, queued]
62191
+ };
62192
+ }
62193
+ };
62194
+ };
62195
+
61683
62196
  // src/vaults/lst/readers/index.ts
61684
62197
  var buildReader = (entry) => {
61685
62198
  switch (entry.reader) {
@@ -61741,6 +62254,8 @@ var buildReader = (entry) => {
61741
62254
  return readerCoreStakedRatio(entry);
61742
62255
  case "kintsuSMon":
61743
62256
  return readerKintsuSMon(entry);
62257
+ case "treehouseTAsset":
62258
+ return readerTreehouseTAsset(entry);
61744
62259
  }
61745
62260
  };
61746
62261
 
@@ -62110,6 +62625,98 @@ var getLstValidators = async (chainId, shareToken) => {
62110
62625
  return [];
62111
62626
  };
62112
62627
 
62628
+ // src/vaults/exitRoutes.ts
62629
+ var asyncLabel = (secs) => {
62630
+ if (!secs || secs <= 0) return "Queued";
62631
+ const d = secs / 86400;
62632
+ if (d >= 1) return `${Number.isInteger(d) ? d : d.toFixed(1)}-day queue`;
62633
+ const h = secs / 3600;
62634
+ return h >= 1 ? `${Number.isInteger(h) ? h : h.toFixed(1)}-hour queue` : `${Math.round(secs / 60)}-minute queue`;
62635
+ };
62636
+ var instantRoute = (i, feeBps) => ({
62637
+ id: "instant",
62638
+ kind: "instant",
62639
+ label: feeBps ? "Instant (fee)" : "Instant",
62640
+ settlement: "sync",
62641
+ feeBps,
62642
+ waitSeconds: 0,
62643
+ capacity: i.liquidity,
62644
+ capacityFormatted: i.liquidityFormatted,
62645
+ capacityUsd: i.liquidityUsd
62646
+ });
62647
+ var queuedRoute = (i) => ({
62648
+ id: "queued",
62649
+ kind: "queued",
62650
+ label: asyncLabel(i.withdrawalCooldownSeconds),
62651
+ settlement: "async",
62652
+ waitSeconds: i.withdrawalCooldownSeconds
62653
+ });
62654
+ var deriveExitRoutes = (i) => {
62655
+ switch (i.withdrawalMode) {
62656
+ // The instant leg pays the fee; the queue pays at par. That is the
62657
+ // convention `SavingsVault.withdrawFeeBps` already documents — and
62658
+ // Treehouse is the counter-example, which is why its reader overrides
62659
+ // this rather than being described by it.
62660
+ case "fee-or-queued":
62661
+ return [
62662
+ {
62663
+ ...instantRoute(i, i.withdrawFeeBps),
62664
+ // The mode's whole meaning is that THIS leg costs something. Say so
62665
+ // even when the size is unknown: an absent `feeBps` renders as
62666
+ // "leave instantly", which reads as free — the same failure the
62667
+ // term-sheet rules call out for `fees: []`.
62668
+ ...i.withdrawFeeBps == null ? { feeUnknown: true, label: "Instant (fee)" } : {}
62669
+ },
62670
+ { ...queuedRoute(i), feeBps: 0 }
62671
+ ];
62672
+ // Same split with no fee on either leg — the instant leg is bounded by
62673
+ // inventory instead.
62674
+ case "instant-or-queued":
62675
+ return [instantRoute(i, i.withdrawFeeBps ?? 0), queuedRoute(i)];
62676
+ case "instant":
62677
+ return [
62678
+ {
62679
+ id: "instant",
62680
+ kind: "instant",
62681
+ label: "Instant",
62682
+ settlement: "sync",
62683
+ feeBps: i.withdrawFeeBps ?? 0,
62684
+ waitSeconds: 0
62685
+ }
62686
+ ];
62687
+ case "instant-capped":
62688
+ return [instantRoute(i, i.withdrawFeeBps ?? 0)];
62689
+ case "fixed-cooldown":
62690
+ case "queued":
62691
+ case "request-based":
62692
+ return [{ ...queuedRoute(i), feeBps: i.withdrawFeeBps }];
62693
+ case "off-chain":
62694
+ return [
62695
+ {
62696
+ id: "off-chain",
62697
+ kind: "queued",
62698
+ label: "Off-chain redemption",
62699
+ settlement: "async",
62700
+ waitSeconds: i.withdrawalCooldownSeconds,
62701
+ description: "Redemption is handled by the issuer off-chain; there is no on-chain exit to call."
62702
+ }
62703
+ ];
62704
+ case "dex-only":
62705
+ case "market-sale":
62706
+ return [
62707
+ {
62708
+ id: "market",
62709
+ kind: "market",
62710
+ label: "Sell on the market",
62711
+ settlement: "market",
62712
+ description: "There is no redemption \u2014 you leave by selling the token, at whatever price is bid and with price impact that grows with size."
62713
+ }
62714
+ ];
62715
+ default:
62716
+ return [];
62717
+ }
62718
+ };
62719
+
62113
62720
  // src/vaults/lst/fetchPublic.ts
62114
62721
  var ONE_E1812 = 10n ** 18n;
62115
62722
  var ERC20_BALANCE_ABI = parseAbi([
@@ -62237,6 +62844,7 @@ var fetchLstShareTokens = async (chainId, multicallRetry, prices = {}, tokenList
62237
62844
  }
62238
62845
  const liquidityFormatted = Number(liquidityRaw) / 10 ** underlyingDec;
62239
62846
  const liquidityUsd = priceUsd !== void 0 ? liquidityFormatted * priceUsd : 0;
62847
+ const cooldownSecs = state.withdrawalCooldownSeconds ?? entry.withdrawalCooldownSeconds;
62240
62848
  const displayName = composeVaultDisplayName(
62241
62849
  entry.brand,
62242
62850
  entry.brand,
@@ -62274,7 +62882,22 @@ var fetchLstShareTokens = async (chainId, multicallRetry, prices = {}, tokenList
62274
62882
  delegation: delegationByAddress.get(addressLc),
62275
62883
  validators: validatorsByAddress.get(addressLc),
62276
62884
  withdrawalMode: entry.withdrawalMode,
62277
- withdrawalCooldownSeconds: entry.withdrawalCooldownSeconds,
62885
+ // A reader-measured wait wins over the registry's static one — see
62886
+ // `LstOnChainState.withdrawalCooldownSeconds`.
62887
+ withdrawalCooldownSeconds: cooldownSecs,
62888
+ withdrawFeeBps: state.withdrawFeeBps,
62889
+ // A reader that reads its protocol's own per-leg dials publishes the
62890
+ // list itself; everything else gets the coarse derivation, which is
62891
+ // still strictly better than making every consumer reassemble the two
62892
+ // legs out of `withdrawalMode` + one cooldown + one liquidity number.
62893
+ exitRoutes: state.exitRoutes ?? deriveExitRoutes({
62894
+ withdrawalMode: entry.withdrawalMode,
62895
+ withdrawFeeBps: state.withdrawFeeBps,
62896
+ withdrawalCooldownSeconds: cooldownSecs,
62897
+ liquidity: liquidityRaw.toString(),
62898
+ liquidityFormatted,
62899
+ liquidityUsd
62900
+ }),
62278
62901
  asset,
62279
62902
  priceUsd,
62280
62903
  totalAssetsFormatted,
@@ -62376,17 +62999,17 @@ var readerBeets = {
62376
62999
  const out = [];
62377
63000
  for (let i = 0; i < reqs.length; i++) {
62378
63001
  const r = reqs[i];
62379
- const amount3 = toBigInt14(r.assetAmount);
63002
+ const amount4 = toBigInt14(r.assetAmount);
62380
63003
  const ts = toNumber(r.requestTimestamp);
62381
63004
  const isWithdrawn = Boolean(r.isWithdrawn);
62382
- if (amount3 === void 0 || ts === void 0) continue;
63005
+ if (amount4 === void 0 || ts === void 0) continue;
62383
63006
  const readyAt = ts + delay;
62384
63007
  out.push({
62385
63008
  lst: entry.lst,
62386
63009
  brand: entry.brand,
62387
63010
  symbol: entry.symbol,
62388
63011
  requestId: String(i),
62389
- amountUnderlying: amount3.toString(),
63012
+ amountUnderlying: amount4.toString(),
62390
63013
  status: isWithdrawn ? "claimed" : computeStatus(readyAt),
62391
63014
  readyAt: isWithdrawn ? void 0 : readyAt
62392
63015
  });
@@ -62837,8 +63460,8 @@ var readerIbera = {
62837
63460
  const r = stage2[k];
62838
63461
  if (!r) continue;
62839
63462
  const receiver = Array.isArray(r) ? r[2] : r.receiver;
62840
- const amount3 = Array.isArray(r) ? toBigInt14(r[3]) : toBigInt14(r.amount);
62841
- if (typeof receiver !== "string" || receiver.toLowerCase() !== target || amount3 === void 0 || amount3 === 0n) {
63463
+ const amount4 = Array.isArray(r) ? toBigInt14(r[3]) : toBigInt14(r.amount);
63464
+ if (typeof receiver !== "string" || receiver.toLowerCase() !== target || amount4 === void 0 || amount4 === 0n) {
62842
63465
  continue;
62843
63466
  }
62844
63467
  const id = BigInt(ids[k]);
@@ -62847,7 +63470,7 @@ var readerIbera = {
62847
63470
  brand: entry.brand,
62848
63471
  symbol: entry.symbol,
62849
63472
  requestId: String(ids[k]),
62850
- amountUnderlying: amount3.toString(),
63473
+ amountUnderlying: amount4.toString(),
62851
63474
  status: id < frontier ? "claimable" : "pending"
62852
63475
  });
62853
63476
  }
@@ -63020,13 +63643,13 @@ var readerErc7540 = {
63020
63643
  ],
63021
63644
  abi: [Erc7540Abi]
63022
63645
  });
63023
- const amount3 = toBigInt14(stage2[0]) ?? claimable;
63646
+ const amount4 = toBigInt14(stage2[0]) ?? claimable;
63024
63647
  out.push({
63025
63648
  lst: entry.lst,
63026
63649
  brand: entry.brand,
63027
63650
  symbol: entry.symbol,
63028
63651
  requestId: "0",
63029
- amountUnderlying: amount3.toString(),
63652
+ amountUnderlying: amount4.toString(),
63030
63653
  // The 7540 claim (`redeem`) takes the share amount.
63031
63654
  shares: claimable.toString(),
63032
63655
  status: "claimable"
@@ -63075,15 +63698,15 @@ var readerEthenaCooldown = {
63075
63698
  });
63076
63699
  const cell = res[0];
63077
63700
  let cooldownEnd;
63078
- let amount3;
63701
+ let amount4;
63079
63702
  if (Array.isArray(cell)) {
63080
63703
  cooldownEnd = toBigInt14(cell[0]);
63081
- amount3 = toBigInt14(cell[1]);
63704
+ amount4 = toBigInt14(cell[1]);
63082
63705
  } else if (cell && typeof cell === "object") {
63083
63706
  cooldownEnd = toBigInt14(cell.cooldownEnd);
63084
- amount3 = toBigInt14(cell.underlyingAmount);
63707
+ amount4 = toBigInt14(cell.underlyingAmount);
63085
63708
  }
63086
- if (!amount3 || amount3 === 0n) return [];
63709
+ if (!amount4 || amount4 === 0n) return [];
63087
63710
  const readyAt = Number(cooldownEnd ?? 0n);
63088
63711
  const out = [
63089
63712
  {
@@ -63091,7 +63714,7 @@ var readerEthenaCooldown = {
63091
63714
  brand: entry.brand,
63092
63715
  symbol: entry.symbol,
63093
63716
  requestId: "0",
63094
- amountUnderlying: amount3.toString(),
63717
+ amountUnderlying: amount4.toString(),
63095
63718
  status: computeStatus(readyAt),
63096
63719
  readyAt
63097
63720
  }
@@ -63146,7 +63769,7 @@ var readerSusd3Cooldown = {
63146
63769
  shares = toBigInt14(cell.shares);
63147
63770
  }
63148
63771
  if (!shares || shares === 0n) return [];
63149
- let amount3 = shares;
63772
+ let amount4 = shares;
63150
63773
  try {
63151
63774
  const stage2 = await multicallRetry({
63152
63775
  chain: chainId,
@@ -63155,7 +63778,7 @@ var readerSusd3Cooldown = {
63155
63778
  ],
63156
63779
  abi: [Susd3CooldownStatusAbi]
63157
63780
  });
63158
- amount3 = toBigInt14(stage2[0]) ?? shares;
63781
+ amount4 = toBigInt14(stage2[0]) ?? shares;
63159
63782
  } catch {
63160
63783
  }
63161
63784
  const readyAt = Number(cooldownEnd ?? 0n);
@@ -63166,7 +63789,7 @@ var readerSusd3Cooldown = {
63166
63789
  brand: entry.brand,
63167
63790
  symbol: entry.symbol,
63168
63791
  requestId: "0",
63169
- amountUnderlying: amount3.toString(),
63792
+ amountUnderlying: amount4.toString(),
63170
63793
  shares: shares.toString(),
63171
63794
  status: computeStatus(readyAt, expiresAt),
63172
63795
  readyAt,
@@ -63362,14 +63985,14 @@ var readerEtherFi = {
63362
63985
  const isValid = Boolean(stage3[i * 3 + 2]);
63363
63986
  if (!req) continue;
63364
63987
  if (!isValid) continue;
63365
- const amount3 = toBigInt14(req.amountOfEEth);
63366
- if (amount3 === void 0) continue;
63988
+ const amount4 = toBigInt14(req.amountOfEEth);
63989
+ if (amount4 === void 0) continue;
63367
63990
  out.push({
63368
63991
  lst: entry.lst,
63369
63992
  brand: entry.brand,
63370
63993
  symbol: entry.symbol,
63371
63994
  requestId: id.toString(),
63372
- amountUnderlying: amount3.toString(),
63995
+ amountUnderlying: amount4.toString(),
63373
63996
  status: isFinalized ? "claimable" : "pending"
63374
63997
  });
63375
63998
  }
@@ -63699,15 +64322,15 @@ var readerLista = {
63699
64322
  const isClaimable = Array.isArray(status) ? Boolean(status[0]) : Boolean(status._isClaimable);
63700
64323
  const bnbAmount = Array.isArray(status) ? toBigInt14(status[1]) : toBigInt14(status._amount);
63701
64324
  const snbnbAmount = Array.isArray(req) ? toBigInt14(req[1]) : toBigInt14(req?.amountInSnBnb);
63702
- const amount3 = bnbAmount ?? snbnbAmount;
63703
- if (amount3 === void 0) continue;
64325
+ const amount4 = bnbAmount ?? snbnbAmount;
64326
+ if (amount4 === void 0) continue;
63704
64327
  const uuid = Array.isArray(req) ? toBigInt14(req[0]) : toBigInt14(req?.uuid);
63705
64328
  out.push({
63706
64329
  lst: entry.lst,
63707
64330
  brand: entry.brand,
63708
64331
  symbol: entry.symbol,
63709
64332
  requestId: String(uuid ?? i),
63710
- amountUnderlying: amount3.toString(),
64333
+ amountUnderlying: amount4.toString(),
63711
64334
  status: isClaimable ? "claimable" : "pending"
63712
64335
  });
63713
64336
  }
@@ -63962,7 +64585,7 @@ var readerStaderMaticX2 = {
63962
64585
  const out = [];
63963
64586
  for (let i = 0; i < arr.length; i++) {
63964
64587
  const r = arr[i];
63965
- const amount3 = toBigInt14(r.amount) ?? 0n;
64588
+ const amount4 = toBigInt14(r.amount) ?? 0n;
63966
64589
  const requestEpoch = toBigInt14(r.requestEpoch);
63967
64590
  if (requestEpoch === void 0) continue;
63968
64591
  const ready = stakeManager ? currentEpoch >= requestEpoch + delay : false;
@@ -63973,7 +64596,7 @@ var readerStaderMaticX2 = {
63973
64596
  // Use array index as the request id (consistent with
63974
64597
  // `claimWithdrawal(uint256 _idx)`).
63975
64598
  requestId: String(i),
63976
- amountUnderlying: amount3.toString(),
64599
+ amountUnderlying: amount4.toString(),
63977
64600
  status: ready ? "claimable" : "pending"
63978
64601
  });
63979
64602
  }
@@ -64374,7 +64997,7 @@ var readerStakeWise = {
64374
64997
  const out = [];
64375
64998
  for (let i = 0; i < requests.length; i++) {
64376
64999
  const r = requests[i];
64377
- const amount3 = toBigInt14(r.totalAssets) ?? 0n;
65000
+ const amount4 = toBigInt14(r.totalAssets) ?? 0n;
64378
65001
  const onChainClaimed = claimedByIdx.get(i);
64379
65002
  const claimable = onChainClaimed !== void 0 || r.isClaimable;
64380
65003
  out.push({
@@ -64382,7 +65005,7 @@ var readerStakeWise = {
64382
65005
  brand: entry.brand,
64383
65006
  symbol: entry.symbol,
64384
65007
  requestId: r.positionTicket,
64385
- amountUnderlying: (onChainClaimed ?? amount3).toString(),
65008
+ amountUnderlying: (onChainClaimed ?? amount4).toString(),
64386
65009
  status: claimable ? "claimable" : "pending"
64387
65010
  });
64388
65011
  }
@@ -64431,15 +65054,15 @@ var readerStCelo2 = {
64431
65054
  const timestamps = cell[1] ?? [];
64432
65055
  const out = [];
64433
65056
  for (let i = 0; i < values.length; i++) {
64434
- const amount3 = toBigInt14(values[i]);
65057
+ const amount4 = toBigInt14(values[i]);
64435
65058
  const readyAt = toNumber(timestamps[i]);
64436
- if (amount3 === void 0 || readyAt === void 0) continue;
65059
+ if (amount4 === void 0 || readyAt === void 0) continue;
64437
65060
  out.push({
64438
65061
  lst: entry.lst,
64439
65062
  brand: entry.brand,
64440
65063
  symbol: entry.symbol,
64441
65064
  requestId: String(i),
64442
- amountUnderlying: amount3.toString(),
65065
+ amountUnderlying: amount4.toString(),
64443
65066
  status: computeStatus(readyAt),
64444
65067
  readyAt
64445
65068
  });
@@ -64540,15 +65163,15 @@ var readerSwell = {
64540
65163
  const id = ids[i];
64541
65164
  const r = stage3[i];
64542
65165
  if (!r) continue;
64543
- const amount3 = toBigInt14(r.amount);
64544
- if (amount3 === void 0) continue;
65166
+ const amount4 = toBigInt14(r.amount);
65167
+ if (amount4 === void 0) continue;
64545
65168
  const claimable = id <= lastProcessed;
64546
65169
  out.push({
64547
65170
  lst: entry.lst,
64548
65171
  brand: entry.brand,
64549
65172
  symbol: entry.symbol,
64550
65173
  requestId: id.toString(),
64551
- amountUnderlying: amount3.toString(),
65174
+ amountUnderlying: amount4.toString(),
64552
65175
  status: claimable ? "claimable" : "pending"
64553
65176
  });
64554
65177
  }
@@ -64614,15 +65237,15 @@ var readerValantis = {
64614
65237
  for (let i = 0; i < burns.length; i++) {
64615
65238
  const b = burns[i];
64616
65239
  if (!b) continue;
64617
- const amount3 = Array.isArray(b) ? toBigInt14(b[0]) : toBigInt14(b.amount);
65240
+ const amount4 = Array.isArray(b) ? toBigInt14(b[0]) : toBigInt14(b.amount);
64618
65241
  const completed = Array.isArray(b) ? Boolean(b[2]) : Boolean(b.completed);
64619
- if (amount3 === void 0 || completed) continue;
65242
+ if (amount4 === void 0 || completed) continue;
64620
65243
  out.push({
64621
65244
  lst: entry.lst,
64622
65245
  brand: entry.brand,
64623
65246
  symbol: entry.symbol,
64624
65247
  requestId: String(toBigInt14(ids[i]) ?? i),
64625
- amountUnderlying: amount3.toString(),
65248
+ amountUnderlying: amount4.toString(),
64626
65249
  status: redeemable[i] ? "claimable" : "pending"
64627
65250
  });
64628
65251
  }
@@ -64630,6 +65253,84 @@ var readerValantis = {
64630
65253
  }
64631
65254
  };
64632
65255
 
65256
+ // src/vaults/lst/withdrawals/abis/treehouse.ts
65257
+ var TreehouseRedemptionQueueAbi = [
65258
+ {
65259
+ name: "getPendingRedeems",
65260
+ type: "function",
65261
+ stateMutability: "view",
65262
+ inputs: [{ type: "address", name: "user" }],
65263
+ outputs: [
65264
+ {
65265
+ type: "tuple[]",
65266
+ components: [
65267
+ { type: "uint64", name: "startTime" },
65268
+ { type: "uint96", name: "shares" },
65269
+ { type: "uint128", name: "assets" },
65270
+ { type: "uint128", name: "baseRate" }
65271
+ ]
65272
+ }
65273
+ ]
65274
+ },
65275
+ {
65276
+ name: "waitingPeriod",
65277
+ type: "function",
65278
+ stateMutability: "view",
65279
+ inputs: [],
65280
+ outputs: [{ type: "uint32" }]
65281
+ }
65282
+ ];
65283
+
65284
+ // src/vaults/lst/withdrawals/readers/treehouse.ts
65285
+ var readerTreehouseRedemption = {
65286
+ fetch: async (user, multicallRetry, chainId, entry) => {
65287
+ if (!entry.withdrawalContract) return [];
65288
+ const res = await multicallRetry({
65289
+ chain: chainId,
65290
+ calls: [
65291
+ {
65292
+ address: entry.withdrawalContract,
65293
+ name: "getPendingRedeems",
65294
+ params: [user]
65295
+ },
65296
+ {
65297
+ address: entry.withdrawalContract,
65298
+ name: "waitingPeriod",
65299
+ params: []
65300
+ }
65301
+ ],
65302
+ abi: [
65303
+ TreehouseRedemptionQueueAbi,
65304
+ TreehouseRedemptionQueueAbi
65305
+ ]
65306
+ });
65307
+ const reqs = res[0];
65308
+ const waitingPeriod = toNumber(res[1]);
65309
+ if (!Array.isArray(reqs) || waitingPeriod === void 0) return [];
65310
+ const out = [];
65311
+ for (let i = 0; i < reqs.length; i++) {
65312
+ const r = reqs[i];
65313
+ const startTime = toNumber(r.startTime);
65314
+ const assets = toBigInt14(r.assets);
65315
+ const shares = toBigInt14(r.shares);
65316
+ if (startTime === void 0 || assets === void 0) continue;
65317
+ const readyAt = startTime + waitingPeriod;
65318
+ out.push({
65319
+ lst: entry.lst,
65320
+ brand: entry.brand,
65321
+ symbol: entry.symbol,
65322
+ // Positional — see note 1 above.
65323
+ requestId: String(i),
65324
+ amountUnderlying: assets.toString(),
65325
+ shares: shares?.toString(),
65326
+ status: computeStatus(readyAt),
65327
+ readyAt
65328
+ });
65329
+ }
65330
+ return out;
65331
+ }
65332
+ };
65333
+
64633
65334
  // src/vaults/lst/withdrawals/abis/trufin.ts
64634
65335
  var TruFinVaultAbi = [
64635
65336
  {
@@ -64694,15 +65395,15 @@ var readerTruFin = {
64694
65395
  if (typeof recipient === "string" && recipient.toLowerCase() !== lcUser) {
64695
65396
  continue;
64696
65397
  }
64697
- const amount3 = Array.isArray(w) ? toBigInt14(w[1]) : toBigInt14(w.amount);
64698
- if (amount3 === void 0 || amount3 === 0n) continue;
65398
+ const amount4 = Array.isArray(w) ? toBigInt14(w[1]) : toBigInt14(w.amount);
65399
+ if (amount4 === void 0 || amount4 === 0n) continue;
64699
65400
  out.push({
64700
65401
  lst: entry.lst,
64701
65402
  brand: entry.brand,
64702
65403
  symbol: entry.symbol,
64703
65404
  // Composite id — same shape as the input.
64704
65405
  requestId: `${pairs[i].validator}:${pairs[i].nonce.toString()}`,
64705
- amountUnderlying: amount3.toString(),
65406
+ amountUnderlying: amount4.toString(),
64706
65407
  status: claimable ? "claimable" : "pending"
64707
65408
  });
64708
65409
  }
@@ -64809,17 +65510,17 @@ var readerYieldNest = {
64809
65510
  const id = ids[i];
64810
65511
  const r = stage3[i];
64811
65512
  if (!r) continue;
64812
- const amount3 = toBigInt14(r.amount);
65513
+ const amount4 = toBigInt14(r.amount);
64813
65514
  const created = toNumber(r.creationTimestamp);
64814
65515
  const processed = Boolean(r.processed);
64815
- if (amount3 === void 0 || created === void 0) continue;
65516
+ if (amount4 === void 0 || created === void 0) continue;
64816
65517
  const readyAt = created + finalizationSeconds;
64817
65518
  out.push({
64818
65519
  lst: entry.lst,
64819
65520
  brand: entry.brand,
64820
65521
  symbol: entry.symbol,
64821
65522
  requestId: id.toString(),
64822
- amountUnderlying: amount3.toString(),
65523
+ amountUnderlying: amount4.toString(),
64823
65524
  status: processed ? "claimed" : computeStatus(readyAt),
64824
65525
  readyAt: processed ? void 0 : readyAt
64825
65526
  });
@@ -64888,6 +65589,8 @@ var buildWithdrawalReader = (entry) => {
64888
65589
  return readerPrimeStaking;
64889
65590
  case "berapawRedeemQueue":
64890
65591
  return readerBeraPaw;
65592
+ case "treehouseRedemptionQueue":
65593
+ return readerTreehouseRedemption;
64891
65594
  case "eventsOnly":
64892
65595
  case "noQueue":
64893
65596
  case "unverified":
@@ -64898,6 +65601,18 @@ var buildWithdrawalReader = (entry) => {
64898
65601
  // src/vaults/lst/withdrawals/registry.ts
64899
65602
  var LST_WITHDRAWAL_REGISTRY = {
64900
65603
  "1": [
65604
+ {
65605
+ // Treehouse tETH — `TreehouseRedemptionV2`, the queued (5 bps / 7 d)
65606
+ // exit. The instant Fastlane leg settles in the same transaction and
65607
+ // so never produces a pending request. Undocumented address,
65608
+ // recovered from `Vault.redemption()` →
65609
+ // `RedemptionController.getRedemptionContracts()`.
65610
+ lst: "0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8",
65611
+ brand: "Treehouse",
65612
+ symbol: "tETH",
65613
+ reader: "treehouseRedemptionQueue",
65614
+ withdrawalContract: "0xcd63a29faff07130d3af89bb4f40778938aabb85"
65615
+ },
64901
65616
  {
64902
65617
  // wBETH — Binance's UnwrapTokenV1 queue, the SAME contract address
64903
65618
  // on Ethereum and BNB. Entered via `wBETH.requestWithdrawEth`;
@@ -65361,6 +66076,14 @@ var LST_WITHDRAWAL_REGISTRY = {
65361
66076
  }
65362
66077
  ],
65363
66078
  "43114": [
66079
+ {
66080
+ // Treehouse tAVAX — same stack as tETH; 17-day wait here.
66081
+ lst: "0x14a84f1a61ccd7d1be596a6cc11fe33a36bc1646",
66082
+ brand: "Treehouse",
66083
+ symbol: "tAVAX",
66084
+ reader: "treehouseRedemptionQueue",
66085
+ withdrawalContract: "0x765f6dc8496ca7ef1e4a391be10185229aacf04b"
66086
+ },
65364
66087
  // Ankr ankrAVAX — Ankr unstake queue; reader not yet implemented.
65365
66088
  {
65366
66089
  lst: "0xc3344870d52688874b06d844e0c36cc39fc727f6",
@@ -65431,6 +66154,14 @@ var LST_WITHDRAWAL_REGISTRY = {
65431
66154
  }
65432
66155
  ],
65433
66156
  "999": [
66157
+ {
66158
+ // Treehouse tHYPE — same stack as tETH; 10-day wait here.
66159
+ lst: "0xb83d27f0f4133a7b0377a88089583131a903fdc7",
66160
+ brand: "Treehouse",
66161
+ symbol: "tHYPE",
66162
+ reader: "treehouseRedemptionQueue",
66163
+ withdrawalContract: "0xcebbb011c2f29b0ed25895c54662bd1ccad30280"
66164
+ },
65434
66165
  {
65435
66166
  // Hyperbeat hbHYPE Ultra — ERC-7540 async-redeem.
65436
66167
  lst: "0x96c6cbb6251ee1c257b2162ca0f39aa5fa44b1fb",
@@ -65670,6 +66401,15 @@ var Erc4626ReadAbi2 = [
65670
66401
  outputs: [{ type: "uint256", name: "assets" }]
65671
66402
  }
65672
66403
  ];
66404
+ var LockupPeriodAbi = [
66405
+ {
66406
+ name: "lockupPeriod",
66407
+ type: "function",
66408
+ stateMutability: "view",
66409
+ inputs: [],
66410
+ outputs: [{ type: "uint256" }]
66411
+ }
66412
+ ];
65673
66413
  var CooldownDurationAbi = [
65674
66414
  {
65675
66415
  name: "cooldownDuration",
@@ -65770,14 +66510,16 @@ var readerErc46262 = (entry) => {
65770
66510
  var readerErc4626Cooldown = (entry) => {
65771
66511
  const shareUnit = 10n ** BigInt(entry.decimals);
65772
66512
  const underlyingUnit = 10n ** BigInt(entry.underlyingDecimals ?? entry.decimals);
66513
+ const cooldownGetter = entry.cooldownGetter ?? "cooldownDuration";
66514
+ const cooldownAbi = cooldownGetter === "lockupPeriod" ? LockupPeriodAbi : CooldownDurationAbi;
65773
66515
  return {
65774
66516
  calls: [
65775
66517
  { address: entry.address, name: "totalAssets", params: [] },
65776
66518
  { address: entry.address, name: "totalSupply", params: [] },
65777
66519
  { address: entry.address, name: "convertToAssets", params: [shareUnit] },
65778
- { address: entry.address, name: "cooldownDuration", params: [] }
66520
+ { address: entry.address, name: cooldownGetter, params: [] }
65779
66521
  ],
65780
- abis: [Erc4626ReadAbi2, TotalSupplyAbi2, Erc4626ReadAbi2, CooldownDurationAbi],
66522
+ abis: [Erc4626ReadAbi2, TotalSupplyAbi2, Erc4626ReadAbi2, cooldownAbi],
65781
66523
  parse: ([assets, supply, rate, cooldown]) => {
65782
66524
  const totalAssets = toBigInt15(assets);
65783
66525
  const totalSupply = toBigInt15(supply);
@@ -66255,6 +66997,8 @@ var readerHyperbeatVault = (entry) => {
66255
66997
  var readerNavOracle = (entry) => {
66256
66998
  const shareUnit = 10n ** BigInt(entry.decimals);
66257
66999
  const underlyingUnit = 10n ** BigInt(entry.underlyingDecimals ?? entry.decimals);
67000
+ const oracleDecimals = entry.priceOracleDecimals ?? 18;
67001
+ const oracleScale = 10n ** BigInt(oracleDecimals);
66258
67002
  return {
66259
67003
  calls: [
66260
67004
  { address: entry.address, name: "totalSupply", params: [] },
@@ -66269,8 +67013,9 @@ var readerNavOracle = (entry) => {
66269
67013
  const totalSupply = toBigInt15(supply);
66270
67014
  if (totalSupply === void 0) return void 0;
66271
67015
  const raw = Array.isArray(round) ? round[1] : round?.answer;
66272
- const exchangeRate = toBigInt15(raw);
66273
- if (exchangeRate === void 0 || exchangeRate <= 0n) return void 0;
67016
+ const answer = toBigInt15(raw);
67017
+ if (answer === void 0 || answer <= 0n) return void 0;
67018
+ const exchangeRate = answer * ONE_E1813 / oracleScale;
66274
67019
  return {
66275
67020
  totalAssets: totalSupply * exchangeRate * underlyingUnit / (ONE_E1813 * shareUnit),
66276
67021
  totalSupply,
@@ -66570,6 +67315,19 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
66570
67315
  instantRedeemEnabled: state.instantRedeemEnabled,
66571
67316
  inventoryContract: entry.inventoryContract?.toLowerCase(),
66572
67317
  withdrawQueue: state.withdrawQueue ?? entry.withdrawQueue?.toLowerCase(),
67318
+ // Both legs of a two-legged exit, spelled out — see `VaultExitRoute`.
67319
+ // Derived from the flat fields the readers already establish, so every
67320
+ // savings row gets it without a per-provider change; the queued leg's
67321
+ // fee is left ABSENT rather than 0 because "not published" and "free"
67322
+ // are different claims and only some protocols make the second.
67323
+ exitRoutes: deriveExitRoutes({
67324
+ withdrawalMode: entry.withdrawalMode,
67325
+ withdrawFeeBps: state.withdrawFeeBps,
67326
+ withdrawalCooldownSeconds: state.withdrawalCooldownSeconds ?? entry.withdrawalCooldownSeconds,
67327
+ liquidity: liquidityRaw,
67328
+ liquidityFormatted,
67329
+ liquidityUsd
67330
+ }),
66573
67331
  depositCapacity,
66574
67332
  depositCapacityFormatted,
66575
67333
  depositCapacityUsd,
@@ -67186,6 +67944,34 @@ var fetchUpshiftVaults = async (chainId, prices = {}, tokenList = {}) => {
67186
67944
  return out;
67187
67945
  };
67188
67946
 
67947
+ // src/vaults/rateSanity.ts
67948
+ var NOISE_DEVIATION_BPS = 10;
67949
+ var IMPLAUSIBLE_APR_PERCENT2 = 1e3;
67950
+ var DUST_TVL_USD = 1e3;
67951
+ var impliedDeviationBps = (c) => {
67952
+ const secs = c.expiry - c.nowSecs;
67953
+ if (!(secs > 0)) return 0;
67954
+ const years = secs / 31536e3;
67955
+ return (Math.pow(1 + c.aprPercent / 100, years) - 1) * 1e4;
67956
+ };
67957
+ var isAnnualizationNoise = (c) => {
67958
+ if (!Number.isFinite(c.aprPercent)) return false;
67959
+ if (!(c.expiry > c.nowSecs)) return false;
67960
+ return Math.abs(impliedDeviationBps(c)) < NOISE_DEVIATION_BPS;
67961
+ };
67962
+ var isImplausibleMagnitude = (aprPercent, totalAssetsUsd) => {
67963
+ if (!Number.isFinite(aprPercent)) return false;
67964
+ if (Math.abs(aprPercent) <= IMPLAUSIBLE_APR_PERCENT2) return false;
67965
+ return (totalAssetsUsd ?? 0) < DUST_TVL_USD;
67966
+ };
67967
+ var isUnearnableRate = (args) => {
67968
+ const { aprPercent, totalAssetsUsd, expiry } = args;
67969
+ if (isImplausibleMagnitude(aprPercent, totalAssetsUsd)) return true;
67970
+ if (expiry === void 0) return false;
67971
+ const nowSecs = args.nowSecs ?? Math.floor(Date.now() / 1e3);
67972
+ return (totalAssetsUsd ?? 0) < DUST_TVL_USD && isAnnualizationNoise({ aprPercent, expiry, nowSecs });
67973
+ };
67974
+
67189
67975
  // src/vaults/yearn/fetchPublic.ts
67190
67976
  var BRAND3 = "Yearn";
67191
67977
  var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
@@ -67375,7 +68161,14 @@ var fetchYearnVaults = async (chainId, multicallRetry, prices = {}, tokenList =
67375
68161
  const out = {};
67376
68162
  for (const item of items) {
67377
68163
  const parsed = parseVault9(item, chainId, prices, tokenList);
67378
- if (parsed) out[parsed.address] = parsed;
68164
+ if (!parsed) continue;
68165
+ if (isUnearnableRate({
68166
+ aprPercent: parsed.supplyRate,
68167
+ totalAssetsUsd: parsed.totalAssetsUsd
68168
+ })) {
68169
+ continue;
68170
+ }
68171
+ out[parsed.address] = parsed;
67379
68172
  }
67380
68173
  try {
67381
68174
  await attachYearnLiquidity(chainId, multicallRetry, out);
@@ -69137,7 +69930,16 @@ var fetchPendlePtMarkets = async (chainId, multicallRetry, prices = {}, tokenLis
69137
69930
  decimalsOnChain,
69138
69931
  nowSecs
69139
69932
  );
69140
- if (parsed) out[parsed.address] = parsed;
69933
+ if (!parsed) continue;
69934
+ if (isUnearnableRate({
69935
+ aprPercent: parsed.supplyRate,
69936
+ totalAssetsUsd: parsed.totalAssetsUsd,
69937
+ expiry: parsed.expiry,
69938
+ nowSecs
69939
+ })) {
69940
+ continue;
69941
+ }
69942
+ out[parsed.address] = parsed;
69141
69943
  }
69142
69944
  return out;
69143
69945
  };
@@ -69386,7 +70188,16 @@ var fetchSpectraPtMarkets = async (chainId, multicallRetry, prices = {}, tokenLi
69386
70188
  decimalsOnChain,
69387
70189
  nowSecs
69388
70190
  );
69389
- if (parsed) out[parsed.address] = parsed;
70191
+ if (!parsed) continue;
70192
+ if (isUnearnableRate({
70193
+ aprPercent: parsed.supplyRate,
70194
+ totalAssetsUsd: parsed.totalAssetsUsd,
70195
+ expiry: parsed.expiry,
70196
+ nowSecs
70197
+ })) {
70198
+ continue;
70199
+ }
70200
+ out[parsed.address] = parsed;
69390
70201
  }
69391
70202
  return out;
69392
70203
  };
@@ -69714,24 +70525,24 @@ function shortDate(unixSecs) {
69714
70525
  }
69715
70526
  function feePhrase(fee) {
69716
70527
  const magnitude = Math.abs(fee.value);
69717
- let amount3;
70528
+ let amount4;
69718
70529
  switch (fee.unit) {
69719
70530
  case "bps":
69720
- amount3 = `${magnitude} bps`;
70531
+ amount4 = `${magnitude} bps`;
69721
70532
  break;
69722
70533
  case "apr-percent":
69723
- amount3 = `${pct(magnitude)}/yr`;
70534
+ amount4 = `${pct(magnitude)}/yr`;
69724
70535
  break;
69725
70536
  case "percent":
69726
- amount3 = pct(magnitude);
70537
+ amount4 = pct(magnitude);
69727
70538
  break;
69728
70539
  default:
69729
- amount3 = String(magnitude);
70540
+ amount4 = String(magnitude);
69730
70541
  }
69731
70542
  const rebate = fee.value < 0 ? " rebate" : "";
69732
70543
  const qualifier = fee.indicative ? " (estimated, resolved at execution)" : "";
69733
70544
  const bound = fee.mutable && fee.cap != null ? `, governance-set up to a maximum of ${fee.unit === "bps" ? `${fee.cap} bps` : pct(fee.cap)}` : fee.mutable ? ", governance-set" : "";
69734
- return `${fee.label}: ${amount3}${rebate}${bound}${qualifier}`;
70545
+ return `${fee.label}: ${amount4}${rebate}${bound}${qualifier}`;
69735
70546
  }
69736
70547
  function maturityPhrase(m) {
69737
70548
  if (m.kind === "fixed-date" && m.maturity)
@@ -69817,12 +70628,40 @@ function provenance(rate) {
69817
70628
  }
69818
70629
  return ` (incl. ${pct(intrinsic)} from the asset)`;
69819
70630
  }
69820
- function supplyHeadline(s) {
70631
+ function routePhrase(r, symbol) {
70632
+ const when = r.kind === "instant" ? "instantly" : r.waitSeconds ? `after ${duration(r.waitSeconds)}` : r.kind === "market" ? "by selling it" : "after a wait";
70633
+ const cost = r.feeBps === 0 ? " free" : r.feeBps != null ? ` for ${(r.feeBps / 100).toFixed(2)} %` : r.feeUnknown ? " for a fee" : "";
70634
+ const floor = r.minAmountFormatted != null && r.minAmountFormatted > 0 ? ` (min ${amount(r.minAmountFormatted)}${symbol ? ` ${symbol}` : ""})` : "";
70635
+ const cap = r.kind !== "instant" || r.capacityFormatted == null ? "" : r.capacityFormatted <= 0 ? ", though nothing is available right now" : `, up to ${amount(r.capacityFormatted)}${symbol ? ` ${symbol}` : ""} right now`;
70636
+ return `${when}${cost}${floor}${cap}`;
70637
+ }
70638
+ function routesSentence(routes2, symbol) {
70639
+ const legs = routes2.map((r) => routePhrase(r, symbol));
70640
+ const body = legs.length === 1 ? legs[0] : `${legs.slice(0, -1).join(", ")}, or ${legs[legs.length - 1]}`;
70641
+ return `You can leave ${body}.`;
70642
+ }
70643
+ function amount(v) {
70644
+ if (v >= 1e3) return v.toLocaleString("en-US", { maximumFractionDigits: 0 });
70645
+ if (v >= 1) return v.toFixed(2).replace(/\.?0+$/, "");
70646
+ return v.toPrecision(3);
70647
+ }
70648
+ function headlineExitFromRoutes(routes2, symbol) {
70649
+ if (!routes2 || routes2.length < 2) return void 0;
70650
+ const fast = routes2.find((r) => r.kind === "instant");
70651
+ const slow = routes2.find((r) => r.kind === "queued");
70652
+ if (!fast || !slow) return void 0;
70653
+ const fastPart = fast.feeBps == null ? "instant exit" : fast.feeBps === 0 ? "free instant exit" : `instant exit for ${(fast.feeBps / 100).toFixed(2)} %`;
70654
+ const slowCost = slow.feeBps === 0 ? "free" : slow.feeBps != null ? `${(slow.feeBps / 100).toFixed(2)} %` : void 0;
70655
+ const floor = slow.minAmountFormatted != null && slow.minAmountFormatted > 0 ? ` above ${amount(slow.minAmountFormatted)}${symbol ? ` ${symbol}` : ""}` : "";
70656
+ const slowPart = slowCost ? `queue${floor} for ${slowCost}` : `queue${floor}`;
70657
+ return `${fastPart}, or ${slowPart}`;
70658
+ }
70659
+ function supplyHeadline(s, sheet = {}) {
69821
70660
  if (s.role === "collateral") {
69822
70661
  return `Collateral only \xB7 ${maturityPhrase(s.maturity)}`;
69823
70662
  }
69824
70663
  const rate = `${rateLabel(s)} ${pct(s.rate.aprTotal)}${windowNote(s.rate)}${provenance(s.rate)}`;
69825
- const exit = exitPhrase[String(s.exit.mode)] ?? (s.exit.settlement === "sync" ? "withdraw any time" : "delayed withdrawal");
70664
+ const exit = headlineExitFromRoutes(s.exit.routes, sheet.asset?.symbol) ?? exitPhrase[String(s.exit.mode)] ?? (s.exit.settlement === "sync" ? "withdraw any time" : "delayed withdrawal");
69826
70665
  const cooldown = s.exit.cooldownSecs ? ` (${duration(s.exit.cooldownSecs)})` : "";
69827
70666
  const mat = s.maturity.kind === "perpetual" ? "" : ` ${maturityPhrase(s.maturity)}`;
69828
70667
  const warmup = s.rate.warmupSecs ? ` \xB7 earns after ${duration(s.rate.warmupSecs)}` : "";
@@ -69858,7 +70697,9 @@ function supplyDescription(s, sheet = {}) {
69858
70697
  `You earn ${pct(s.rate.aprTotal)} (${comp.join(" + ")}), ${rateBehaviour(s.rate)}.`
69859
70698
  );
69860
70699
  }
69861
- if (s.exit.mode === "instant") {
70700
+ if (s.exit.routes && s.exit.routes.length > 1) {
70701
+ parts.push(routesSentence(s.exit.routes, sheet.asset?.symbol));
70702
+ } else if (s.exit.mode === "instant") {
69862
70703
  const util = sheet.utilization?.utilization;
69863
70704
  parts.push(
69864
70705
  util != null && util >= 0.95 ? `Withdrawals are instant in principle, but utilization is ${pct(util * 100)} \u2014 there may be little or no liquidity to withdraw right now.` : "Withdrawals are instant, up to the liquidity available in the market."
@@ -70991,7 +71832,7 @@ function finalizeInfo(sheet) {
70991
71832
  sheet.borrow.info = { headline: "", description: "", tags: [] };
70992
71833
  if (sheet.supply) {
70993
71834
  sheet.supply.info = {
70994
- headline: sheet.supply.info.headline || supplyHeadline(sheet.supply),
71835
+ headline: sheet.supply.info.headline || supplyHeadline(sheet.supply, sheet),
70995
71836
  description: sheet.supply.info.description || supplyDescription(sheet.supply, sheet),
70996
71837
  implications: sheet.supply.info.implications,
70997
71838
  tags: deriveSupplyTags(sheet.supply, market)
@@ -71495,6 +72336,10 @@ function resolveExitMode(input, t) {
71495
72336
  if (totalAssets <= 0) return "instant";
71496
72337
  return liquidity >= totalAssets ? "instant" : "instant-capped";
71497
72338
  }
72339
+ var TWO_LEGGED_MODES = /* @__PURE__ */ new Set([
72340
+ "fee-or-queued",
72341
+ "instant-or-queued"
72342
+ ]);
71498
72343
  var ASYNC_MODES = /* @__PURE__ */ new Set([
71499
72344
  "fixed-cooldown",
71500
72345
  "queued",
@@ -71508,12 +72353,29 @@ function settlementFor(mode, priceRisk) {
71508
72353
  return "market";
71509
72354
  return ASYNC_MODES.has(mode) ? "async" : "sync";
71510
72355
  }
72356
+ function derivedRoutes(input, mode) {
72357
+ const routes2 = deriveExitRoutes({
72358
+ withdrawalMode: mode,
72359
+ withdrawFeeBps: input.withdrawFeeBps,
72360
+ withdrawalCooldownSeconds: input.withdrawalCooldownSeconds,
72361
+ liquidity: isNum2(input.liquidity) ? String(input.liquidity) : void 0,
72362
+ liquidityFormatted: input.liquidity,
72363
+ liquidityUsd: input.liquidityUsd
72364
+ });
72365
+ return routes2.length > 0 ? routes2 : void 0;
72366
+ }
71511
72367
  function buildExit(input, t, fees) {
71512
72368
  const mode = resolveExitMode(input, t);
71513
72369
  const priceRisk = resolvePriceRisk(input, t);
71514
72370
  const ratio = input.instantLiquidityRatio ?? (isNum2(input.liquidity) && isNum2(input.totalAssets) && input.totalAssets > 0 ? clamp014(input.liquidity / input.totalAssets) : void 0);
71515
72371
  return {
71516
72372
  mode,
72373
+ // The row's own list wins — providers that read their protocol's exit
72374
+ // modules attach live per-leg fees and minimums. Otherwise derive the
72375
+ // same split from the flat fields, because the alternative is a sheet
72376
+ // that states one wait and one fee belonging to DIFFERENT legs. Only a
72377
+ // row whose mode we cannot map at all ends up with no routes.
72378
+ routes: input.exitRoutes ?? derivedRoutes(input, mode),
71517
72379
  settlement: settlementFor(mode, priceRisk),
71518
72380
  cooldownSecs: input.withdrawalCooldownSeconds,
71519
72381
  liquidity: isNum2(input.liquidity) || isNum2(input.liquidityUsd) ? {
@@ -71720,6 +72582,10 @@ function buildCoverage2(sheet, input, t) {
71720
72582
  else pending.counterparty = "solvency-not-curated-default-assumed";
71721
72583
  if (sheet.supply?.exit.liquidity) present.push("exitLiquidity");
71722
72584
  else pending.exitLiquidity = "provider-does-not-publish-liquidity";
72585
+ if (sheet.supply?.exit.routes?.length) present.push("exitRoutes");
72586
+ else if (TWO_LEGGED_MODES.has(String(sheet.supply?.exit.mode)))
72587
+ pending.exitRoutes = "provider-does-not-publish-per-leg-terms";
72588
+ else notApplicable.exitRoutes = "single-exit-route";
71723
72589
  return {
71724
72590
  present,
71725
72591
  notApplicable: Object.keys(notApplicable).length ? notApplicable : void 0,
@@ -72448,13 +73314,13 @@ function earnMarketFromVault(row, chainId, opts = {}) {
72448
73314
  // The vault origin reports RAW base units + a pre-formatted convenience
72449
73315
  // field. Carry both; fall back to formatting the raw amount ourselves so
72450
73316
  // `formatted` — the field consumers sort on — is never silently absent.
72451
- tvl: amount(
73317
+ tvl: amount2(
72452
73318
  tvl.totalAssets,
72453
73319
  tvl.totalAssetsFormatted,
72454
73320
  tvl.totalAssetsUsd,
72455
73321
  assetDecimals
72456
73322
  ),
72457
- liquidity: liq.liquidity != null || liq.liquidityUsd != null ? amount(
73323
+ liquidity: liq.liquidity != null || liq.liquidityUsd != null ? amount2(
72458
73324
  liq.liquidity,
72459
73325
  liq.liquidityFormatted,
72460
73326
  liq.liquidityUsd,
@@ -72466,7 +73332,10 @@ function earnMarketFromVault(row, chainId, opts = {}) {
72466
73332
  mode: exitMode,
72467
73333
  settlement: str6(meta.redemptionType),
72468
73334
  cooldownSecs: num13(meta.withdrawalCooldownSeconds),
72469
- feeBps: num13(meta.withdrawFeeBps)
73335
+ feeBps: num13(meta.withdrawFeeBps),
73336
+ // Passed through, never re-derived: the earn row and the vault row must
73337
+ // not disagree about what the legs out are.
73338
+ routes: Array.isArray(meta.exitRoutes) ? meta.exitRoutes : void 0
72470
73339
  },
72471
73340
  availability,
72472
73341
  risk: {
@@ -72567,7 +73436,7 @@ function isMatured(maturity) {
72567
73436
  if (!maturity?.maturity) return false;
72568
73437
  return maturity.maturity <= Math.floor(Date.now() / 1e3);
72569
73438
  }
72570
- function amount(raw, formatted, usd, decimals) {
73439
+ function amount2(raw, formatted, usd, decimals) {
72571
73440
  const rawStr = raw != null ? String(raw) : void 0;
72572
73441
  return {
72573
73442
  raw: rawStr,
@@ -72595,6 +73464,13 @@ var num14 = (v) => {
72595
73464
  };
72596
73465
  var str7 = (v) => typeof v === "string" && v.trim() !== "" ? v : void 0;
72597
73466
  var bool2 = (v) => typeof v === "boolean" ? v : void 0;
73467
+ var routes = (v) => {
73468
+ if (!Array.isArray(v)) return void 0;
73469
+ const out = v.filter(
73470
+ (r) => !!r && typeof r === "object" && typeof r.id === "string" && typeof r.kind === "string"
73471
+ );
73472
+ return out.length > 0 ? out : void 0;
73473
+ };
72598
73474
  var strArray = (v) => {
72599
73475
  if (!Array.isArray(v)) return void 0;
72600
73476
  const out = v.filter((x) => typeof x === "string" && x !== "");
@@ -72665,6 +73541,7 @@ function toVaultTermInput(vault, provider, chainId) {
72665
73541
  withdrawalMode: str7(row.withdrawalMode),
72666
73542
  withdrawalCooldownSeconds: num14(row.withdrawalCooldownSeconds),
72667
73543
  withdrawFeeBps: num14(row.withdrawFeeBps),
73544
+ exitRoutes: routes(row.exitRoutes),
72668
73545
  fee: num14(row.fee),
72669
73546
  managementFee: num14(row.managementFee),
72670
73547
  swapFeeRate: num14(row.feeRate),
@@ -72731,6 +73608,7 @@ function vaultTermInputFromEarnMarket(m) {
72731
73608
  withdrawalMode: m.exit.mode,
72732
73609
  withdrawalCooldownSeconds: m.exit.cooldownSecs,
72733
73610
  withdrawFeeBps: m.exit.feeBps,
73611
+ exitRoutes: routes(m.exit.routes),
72734
73612
  fee: m.rate.fee,
72735
73613
  managementFee: num14(meta.managementFee),
72736
73614
  swapFeeRate: num14(meta.feeRate),
@@ -72813,6 +73691,7 @@ function vaultTermInputFromSourceRow(row, chainId) {
72813
73691
  withdrawalMode: str7(meta.withdrawalMode),
72814
73692
  withdrawalCooldownSeconds: num14(meta.withdrawalCooldownSeconds),
72815
73693
  withdrawFeeBps: num14(meta.withdrawFeeBps),
73694
+ exitRoutes: routes(meta.exitRoutes),
72816
73695
  fee: num14(rates.fee),
72817
73696
  managementFee: num14(meta.managementFee),
72818
73697
  swapFeeRate: num14(meta.feeRate),
@@ -76063,11 +76942,11 @@ function earnMarketFromPool(row, fallbackChainId, venueCollaterals) {
76063
76942
  // so `formatted` is populated and `raw` is genuinely absent. Do NOT
76064
76943
  // back-fill `raw` by re-scaling — the round-trip loses precision and would
76065
76944
  // fabricate base units the origin never sent.
76066
- tvl: amount2(
76945
+ tvl: amount3(
76067
76946
  row.totalDeposits,
76068
76947
  row.totalDepositsUSD ?? row.totalDepositsUsd
76069
76948
  ),
76070
- liquidity: amount2(
76949
+ liquidity: amount3(
76071
76950
  row.totalLiquidity,
76072
76951
  row.totalLiquidityUSD ?? row.totalLiquidityUsd
76073
76952
  ),
@@ -76167,7 +77046,7 @@ function sumRewardAprs(rewards) {
76167
77046
  }
76168
77047
  return seen ? total : void 0;
76169
77048
  }
76170
- function amount2(formatted, usd) {
77049
+ function amount3(formatted, usd) {
76171
77050
  return { formatted: num13(formatted), usd: num13(usd) };
76172
77051
  }
76173
77052
  function sum2(...parts) {