@1delta/margin-fetcher 0.0.340 → 0.0.341

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.
Files changed (30) hide show
  1. package/dist/abis/oracle/AaveOracle.d.ts +14 -0
  2. package/dist/abis/oracle/AaveOracle.d.ts.map +1 -1
  3. package/dist/index.d.ts +3 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +561 -87
  6. package/dist/index.js.map +1 -1
  7. package/dist/prices/oracle-prices/derivation.d.ts +25 -0
  8. package/dist/prices/oracle-prices/derivation.d.ts.map +1 -0
  9. package/dist/prices/oracle-prices/feedStaleness.d.ts +44 -0
  10. package/dist/prices/oracle-prices/feedStaleness.d.ts.map +1 -0
  11. package/dist/prices/oracle-prices/feedStats.d.ts +145 -0
  12. package/dist/prices/oracle-prices/feedStats.d.ts.map +1 -0
  13. package/dist/prices/oracle-prices/fetchOraclePrices.d.ts +14 -2
  14. package/dist/prices/oracle-prices/fetchOraclePrices.d.ts.map +1 -1
  15. package/dist/prices/oracle-prices/fetchers/aave.d.ts +14 -0
  16. package/dist/prices/oracle-prices/fetchers/aave.d.ts.map +1 -1
  17. package/dist/prices/oracle-prices/fetchers/compoundV3.d.ts.map +1 -1
  18. package/dist/prices/oracle-prices/fetchers/euler.d.ts.map +1 -1
  19. package/dist/prices/oracle-prices/fetchers/morpho.d.ts.map +1 -1
  20. package/dist/prices/oracle-prices/index.d.ts +7 -2
  21. package/dist/prices/oracle-prices/index.d.ts.map +1 -1
  22. package/dist/prices/oracle-prices/lenderKeys.d.ts +18 -0
  23. package/dist/prices/oracle-prices/lenderKeys.d.ts.map +1 -0
  24. package/dist/prices/oracle-prices/outlierGuard.d.ts +43 -0
  25. package/dist/prices/oracle-prices/outlierGuard.d.ts.map +1 -0
  26. package/dist/prices/oracle-prices/selectAssetGroupPrices.d.ts +45 -4
  27. package/dist/prices/oracle-prices/selectAssetGroupPrices.d.ts.map +1 -1
  28. package/dist/prices/oracle-prices/types.d.ts +114 -1
  29. package/dist/prices/oracle-prices/types.d.ts.map +1 -1
  30. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import './chunk-BYTNVMX7.js';
3
3
  import './chunk-PR4QN5HX.js';
4
4
  import { Lender, LIQUITY_FAMILY_LENDERS, isAaveType, isCompoundV3, isMultiMarket, isSiloV2Type, isSiloV3Type, isDolomite, isEulerType, isAaveV4Type, isInit, isMorphoType, isCompoundV2Type, isVenusType, isSumerType, AAVE_V3_LENDERS, AAVE_V2_LENDERS, isAaveV2Type, isAaveV32Type, isAaveV3Type, isFluid, isGearboxV3, isMidnight, isTerm, isExactly, isLiquityFamily, isRiver, isTeller, isYLDR, isCompoundV3Type, isLista, isTectonicType, isKineticType, isBenqiType } from '@1delta/lender-registry';
5
5
  export { isAaveType, isAaveV2Type, isAaveV32Type, isAaveV3Type, isCompoundV3, isCompoundV3Type, isInit, isMorphoType, isMultiMarket, isYLDR } from '@1delta/lender-registry';
6
- import { fetchTokenLists, fetchTokenList, aavePools, compoundV3Pools, initConfig, morphoPools, compoundV2Pools, eulerVaults, siloMarkets, siloV2LenderKey, siloMarketsV3, siloV3LenderKey, aaveV4Spokes, aaveV4SpokeLenderKey, fluidResolvers, gearboxResolvers, dolomiteConfigs, midnightConfig, termConfig, termMarketsByChain, exactlyConfig, liquityLendersByChain, riverLendersByChain, tellerConfigByChain, tellerPoolsByChain, aaveReserves, compoundV3Reserves, liquityConfigFor, liquityBranchesByChain, riverConfigFor, riverChainData, morphoTypeMarkets, resolveMidnightApiBase, resolveTermApiBase, getSiloV2MarketEntry, getSiloV3MarketEntry, dolomiteEmode, aaveTokens, morphoTypeVaults, eulerConfigs, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxV3LenderKey, midnightMarketsByChain, parseAaveV4SpokeLenderKey, gearboxAccountCompressor, compoundV2Tokens, exactlyMarketsByChain, aaveOracles, aaveOraclesConfig, compoundV2Oracles, compoundV2TokenArray, compoundV2OraclesConfig, compoundV3OraclesData, compoundV3BaseData, compoundV2Reserves, aaveV4Oracles } from '@1delta/data-sdk';
6
+ import { fetchTokenLists, fetchTokenList, aavePools, compoundV3Pools, initConfig, morphoPools, compoundV2Pools, eulerVaults, siloMarkets, siloV2LenderKey, siloMarketsV3, siloV3LenderKey, aaveV4Spokes, aaveV4SpokeLenderKey, fluidResolvers, gearboxResolvers, dolomiteConfigs, midnightConfig, termConfig, termMarketsByChain, exactlyConfig, liquityLendersByChain, riverLendersByChain, tellerConfigByChain, tellerPoolsByChain, aaveReserves, compoundV3Reserves, liquityConfigFor, liquityBranchesByChain, riverConfigFor, riverChainData, aaveOracles, morphoTypeMarkets, resolveMidnightApiBase, resolveTermApiBase, getSiloV2MarketEntry, getSiloV3MarketEntry, dolomiteEmode, aaveTokens, morphoTypeVaults, eulerConfigs, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxV3LenderKey, midnightMarketsByChain, parseAaveV4SpokeLenderKey, gearboxAccountCompressor, compoundV2Tokens, exactlyMarketsByChain, aaveOraclesConfig, compoundV2Oracles, compoundV2TokenArray, compoundV2OraclesConfig, compoundV3OraclesData, compoundV3BaseData, compoundV2Reserves, aaveV4Oracles } from '@1delta/data-sdk';
7
7
  import lodash from 'lodash';
8
8
  import { getEvmClient, multicallRetryUniversal, getEvmChain, getEvmClientUniversal } from '@1delta/providers';
9
9
  import { LiquityTroveManagerAbi, LiquityActivePoolAbi, LiquityStabilityPoolAbi, LiquityPriceFeedAbi, LiquitySortedTrovesAbi, RiverTroveManagerAbi, RiverStabilityPoolAbi, TellerMarketRegistryAbi, TellerV2Abi, MetaMorphoAbi, ExactlyPreviewerAbi, ExactlyAuditorAbi, LenderCommitmentGroupAbi, FluidLendingResolverAbi, FluidVaultResolverAbi, FluidLiquidityResolverAbi, MoolahVaultAbi, MorphoLensAbi, AaveV4SpokeAbi, AaveV4OracleAbi, AaveV4HubAbi, DolomiteMarginAbi, GearboxMarketCompressorV310Abi, MorphoBlueAbi, MidnightAbi, TermRepoTokenAbi, TermRepoServicerAbi, TermRepoCollateralManagerAbi, LiquityTroveNFTAbi, LiquityCollSurplusPoolAbi, TellerCollateralManagerAbi, GearboxCreditAccountCompressorV310Abi, TermPriceConsumerAbi } from '@1delta/abis';
@@ -39275,6 +39275,25 @@ var AaveOracleAbi = [
39275
39275
  ],
39276
39276
  stateMutability: "view",
39277
39277
  type: "function"
39278
+ },
39279
+ {
39280
+ inputs: [
39281
+ {
39282
+ internalType: "address",
39283
+ name: "asset",
39284
+ type: "address"
39285
+ }
39286
+ ],
39287
+ name: "getSourceOfAsset",
39288
+ outputs: [
39289
+ {
39290
+ internalType: "address",
39291
+ name: "",
39292
+ type: "address"
39293
+ }
39294
+ ],
39295
+ stateMutability: "view",
39296
+ type: "function"
39278
39297
  }
39279
39298
  ];
39280
39299
 
@@ -39631,6 +39650,8 @@ function processMarketsToEntries(chainId, allMarkets, source) {
39631
39650
  ) : 0;
39632
39651
  const resolvedLoanUSD = loanPriceUSD != null && loanPriceUSD > 0 ? loanPriceUSD : collateralPriceUSD != null && collateralPriceUSD > 0 && oracleRatio > 0 ? collateralPriceUSD / oracleRatio : 0;
39633
39652
  const resolvedCollateralUSD = collateralPriceUSD != null && collateralPriceUSD > 0 ? collateralPriceUSD : loanPriceUSD != null && loanPriceUSD > 0 && oracleRatio > 0 ? oracleRatio * loanPriceUSD : 0;
39653
+ const collateralFromApi = collateralPriceUSD != null && collateralPriceUSD > 0;
39654
+ const loanFromApi = loanPriceUSD != null && loanPriceUSD > 0;
39634
39655
  if (resolvedCollateralUSD > 0) {
39635
39656
  entries.push({
39636
39657
  asset: collateralAsset,
@@ -39639,7 +39660,8 @@ function processMarketsToEntries(chainId, allMarkets, source) {
39639
39660
  priceUSD: resolvedCollateralUSD,
39640
39661
  marketUid: createMarketUid(chainId, lenderKey, collateralAsset),
39641
39662
  targetLender: lenderKey,
39642
- baseAsset: loanAsset
39663
+ baseAsset: loanAsset,
39664
+ derivation: collateralFromApi ? "direct" : "derived"
39643
39665
  });
39644
39666
  }
39645
39667
  if (resolvedLoanUSD > 0) {
@@ -39651,7 +39673,8 @@ function processMarketsToEntries(chainId, allMarkets, source) {
39651
39673
  targetLender: lenderKey,
39652
39674
  description: "Morpho loan asset",
39653
39675
  staticBase: true,
39654
- baseAsset: loanAsset
39676
+ baseAsset: loanAsset,
39677
+ derivation: loanFromApi ? "direct" : "derived"
39655
39678
  });
39656
39679
  }
39657
39680
  }
@@ -40200,6 +40223,7 @@ var ChainlinkAggregatorAbi = [
40200
40223
  }
40201
40224
  ];
40202
40225
  var ANSWER_INDEX = 1;
40226
+ var UPDATED_AT_INDEX = 3;
40203
40227
  var CHAINLINK_DECIMALS = 8;
40204
40228
  var USD_PRICED_OVERRIDES = {
40205
40229
  [Lender.COMPOUND_V3_AERO]: /* @__PURE__ */ new Set(["8453"]),
@@ -40271,6 +40295,7 @@ function parseCompoundV3Results(data, meta, context) {
40271
40295
  priceUSD = rawPrice * baseAssetUSD;
40272
40296
  }
40273
40297
  }
40298
+ const updatedAtRaw = Array.isArray(rawResult) ? Number(rawResult[UPDATED_AT_INDEX]) : 0;
40274
40299
  entries.push({
40275
40300
  asset: assetLc,
40276
40301
  price: rawPrice,
@@ -40279,7 +40304,9 @@ function parseCompoundV3Results(data, meta, context) {
40279
40304
  marketUid: createMarketUid(chainId, queryMeta.lender, queryMeta.asset),
40280
40305
  description: queryMeta.description,
40281
40306
  staticBase: queryMeta.staticBase || void 0,
40282
- baseAsset: queryMeta.baseAsset
40307
+ baseAsset: queryMeta.baseAsset,
40308
+ updatedAt: updatedAtRaw > 0 ? updatedAtRaw : void 0,
40309
+ feedSource: updatedAtRaw > 0 ? queryMeta.oracle : void 0
40283
40310
  });
40284
40311
  } catch {
40285
40312
  }
@@ -40527,12 +40554,15 @@ function parseEulerV2Results(data, meta, context) {
40527
40554
  const priceInUoA = Number(amountOutMid) / Number(10n ** unitOfAccountDecimals);
40528
40555
  if (isNaN(priceInUoA) || priceInUoA === 0) return;
40529
40556
  let priceUSD;
40557
+ let derivation;
40530
40558
  if (isUsdUnitOfAccount(unitOfAccount)) {
40531
40559
  priceUSD = priceInUoA;
40560
+ derivation = "direct";
40532
40561
  } else {
40533
40562
  const uoaUSD = context.usdPrices[unitOfAccount] ?? 0;
40534
40563
  if (uoaUSD === 0) return;
40535
40564
  priceUSD = priceInUoA * uoaUSD;
40565
+ derivation = "quoted";
40536
40566
  }
40537
40567
  if (isNaN(priceUSD) || priceUSD === 0) return;
40538
40568
  if (priceUSD > MAX_SANE_PRICE_USD) return;
@@ -40544,7 +40574,8 @@ function parseEulerV2Results(data, meta, context) {
40544
40574
  context.chainId,
40545
40575
  queryMeta.lender,
40546
40576
  queryMeta.vault
40547
- )
40577
+ ),
40578
+ derivation
40548
40579
  });
40549
40580
  } catch {
40550
40581
  }
@@ -41257,7 +41288,7 @@ function lookupUSD3(context, asset) {
41257
41288
  return context.usdPrices[groupKey] ?? context.usdPrices[lc] ?? 0;
41258
41289
  }
41259
41290
  function parseSiloV2GraphQLResults(items, context) {
41260
- const ZERO_ADDRESS7 = "0x0000000000000000000000000000000000000000";
41291
+ const ZERO_ADDRESS8 = "0x0000000000000000000000000000000000000000";
41261
41292
  const out = [];
41262
41293
  const seen = /* @__PURE__ */ new Set();
41263
41294
  for (const item of items) {
@@ -41280,8 +41311,8 @@ function parseSiloV2GraphQLResults(items, context) {
41280
41311
  const partnerTokenLc = other.token.toLowerCase();
41281
41312
  const dedupKey = `${lenderKey}:${tokenLc}`;
41282
41313
  if (seen.has(dedupKey)) continue;
41283
- const oracleAddr = (side.solvencyOracleAddress ?? side.solvencyOracle?.id ?? self.solvencyOracle ?? ZERO_ADDRESS7).toLowerCase();
41284
- const isStatic = !oracleAddr || oracleAddr === ZERO_ADDRESS7;
41314
+ const oracleAddr = (side.solvencyOracleAddress ?? side.solvencyOracle?.id ?? self.solvencyOracle ?? ZERO_ADDRESS8).toLowerCase();
41315
+ const isStatic = !oracleAddr || oracleAddr === ZERO_ADDRESS8;
41285
41316
  const siloAddrLc = self.silo.toLowerCase();
41286
41317
  try {
41287
41318
  let priceUSD = 0;
@@ -41399,7 +41430,7 @@ function lookupUSD4(context, asset) {
41399
41430
  return context.usdPrices[groupKey] ?? context.usdPrices[lc] ?? 0;
41400
41431
  }
41401
41432
  function parseSiloV3GraphQLResults(items, context) {
41402
- const ZERO_ADDRESS7 = "0x0000000000000000000000000000000000000000";
41433
+ const ZERO_ADDRESS8 = "0x0000000000000000000000000000000000000000";
41403
41434
  const out = [];
41404
41435
  const seen = /* @__PURE__ */ new Set();
41405
41436
  for (const item of items) {
@@ -41422,8 +41453,8 @@ function parseSiloV3GraphQLResults(items, context) {
41422
41453
  const partnerTokenLc = other.token.toLowerCase();
41423
41454
  const dedupKey = `${lenderKey}:${tokenLc}`;
41424
41455
  if (seen.has(dedupKey)) continue;
41425
- const oracleAddr = (side.solvencyOracleAddress ?? side.solvencyOracle?.id ?? self.solvencyOracle ?? ZERO_ADDRESS7).toLowerCase();
41426
- const isStatic = !oracleAddr || oracleAddr === ZERO_ADDRESS7;
41456
+ const oracleAddr = (side.solvencyOracleAddress ?? side.solvencyOracle?.id ?? self.solvencyOracle ?? ZERO_ADDRESS8).toLowerCase();
41457
+ const isStatic = !oracleAddr || oracleAddr === ZERO_ADDRESS8;
41427
41458
  const siloAddrLc = self.silo.toLowerCase();
41428
41459
  try {
41429
41460
  let priceUSD = 0;
@@ -41532,6 +41563,116 @@ async function fetchMorphoMysticPrices(chainId) {
41532
41563
  return null;
41533
41564
  }
41534
41565
  }
41566
+ var DESCRIPTION_ABI = [
41567
+ {
41568
+ inputs: [],
41569
+ name: "description",
41570
+ outputs: [{ internalType: "string", name: "", type: "string" }],
41571
+ stateMutability: "view",
41572
+ type: "function"
41573
+ }
41574
+ ];
41575
+ var AGGREGATOR_ABI = [...ChainlinkAggregatorAbi, ...DESCRIPTION_ABI];
41576
+ function isFailed(r) {
41577
+ return r === "0x" || r === void 0 || r === null;
41578
+ }
41579
+ var ZERO_ADDRESS7 = "0x0000000000000000000000000000000000000000";
41580
+ async function probeAaveFeedTimestamps(chainId, options = {}) {
41581
+ const { rpcOverrides, batchSize, retries = 2 } = options;
41582
+ const result = {};
41583
+ const oracles = aaveOracles() ?? {};
41584
+ const reserves = aaveReserves() ?? {};
41585
+ const jobs = [];
41586
+ for (const [lender, chains] of Object.entries(oracles)) {
41587
+ const oracle = chains?.[chainId];
41588
+ const assets = reserves[lender]?.[chainId] ?? [];
41589
+ if (!oracle || assets.length === 0) continue;
41590
+ for (const asset of assets) jobs.push({ lender, oracle, asset });
41591
+ }
41592
+ if (jobs.length === 0) return result;
41593
+ try {
41594
+ const sourceResults = await multicallRetryUniversal({
41595
+ chain: chainId,
41596
+ calls: jobs.map((j) => ({
41597
+ address: j.oracle,
41598
+ name: "getSourceOfAsset",
41599
+ params: [j.asset]
41600
+ })),
41601
+ abi: AaveOracleAbi,
41602
+ batchSize,
41603
+ maxRetries: retries,
41604
+ providerId: void 0,
41605
+ allowFailure: true,
41606
+ overrdies: rpcOverrides,
41607
+ logErrors: false
41608
+ });
41609
+ const sourceOf = /* @__PURE__ */ new Map();
41610
+ const uniqueSources = [];
41611
+ const sourceIndex = /* @__PURE__ */ new Map();
41612
+ jobs.forEach((_3, i) => {
41613
+ const raw = sourceResults[i];
41614
+ if (isFailed(raw)) return;
41615
+ const source = String(raw).toLowerCase();
41616
+ if (source === ZERO_ADDRESS7) return;
41617
+ sourceOf.set(i, source);
41618
+ if (!sourceIndex.has(source)) {
41619
+ sourceIndex.set(source, uniqueSources.length);
41620
+ uniqueSources.push(source);
41621
+ }
41622
+ });
41623
+ if (uniqueSources.length === 0) return result;
41624
+ const roundResults = await multicallRetryUniversal({
41625
+ chain: chainId,
41626
+ calls: uniqueSources.flatMap((source) => [
41627
+ { address: source, name: "latestRoundData", params: [] },
41628
+ { address: source, name: "description", params: [] }
41629
+ ]),
41630
+ abi: AGGREGATOR_ABI,
41631
+ batchSize,
41632
+ maxRetries: retries,
41633
+ providerId: void 0,
41634
+ allowFailure: true,
41635
+ overrdies: rpcOverrides,
41636
+ logErrors: false
41637
+ });
41638
+ const infoOfSource = /* @__PURE__ */ new Map();
41639
+ uniqueSources.forEach((source, i) => {
41640
+ const round = roundResults[i * 2];
41641
+ const description = roundResults[i * 2 + 1];
41642
+ if (isFailed(round) || !Array.isArray(round)) return;
41643
+ const updatedAt = Number(round[3]);
41644
+ if (!(updatedAt > 0)) return;
41645
+ infoOfSource.set(source, {
41646
+ source,
41647
+ updatedAt,
41648
+ description: isFailed(description) ? void 0 : String(description)
41649
+ });
41650
+ });
41651
+ for (const [i, source] of sourceOf) {
41652
+ const info = infoOfSource.get(source);
41653
+ if (!info) continue;
41654
+ const { lender, asset } = jobs[i];
41655
+ if (!result[lender]) result[lender] = {};
41656
+ result[lender][asset.toLowerCase()] = info;
41657
+ }
41658
+ } catch {
41659
+ return result;
41660
+ }
41661
+ return result;
41662
+ }
41663
+
41664
+ // src/prices/oracle-prices/derivation.ts
41665
+ var DERIVATION_RANK = {
41666
+ direct: 0,
41667
+ quoted: 1,
41668
+ derived: 2
41669
+ };
41670
+ function resolveDerivation(entry, groupDefault) {
41671
+ if (entry.derivation) return entry.derivation;
41672
+ if (groupDefault === "derived") return "derived";
41673
+ if (entry.baseAsset && !entry.staticBase) return "quoted";
41674
+ return groupDefault ?? "direct";
41675
+ }
41535
41676
 
41536
41677
  // src/prices/oracle-prices/fetchOraclePrices.ts
41537
41678
  function countFailures(data, offset, count) {
@@ -41549,11 +41690,12 @@ function safeGetCalls(fetcherName, fn, errors) {
41549
41690
  return [];
41550
41691
  }
41551
41692
  }
41552
- function buildGroup(fetcherName, results, parse, abi) {
41693
+ function buildGroup(fetcherName, results, parse, abi, derivation) {
41553
41694
  const group = {
41554
41695
  fetcherName,
41555
41696
  calls: [],
41556
41697
  abi,
41698
+ derivation,
41557
41699
  trackers: []
41558
41700
  };
41559
41701
  for (const fr of results) {
@@ -41568,7 +41710,7 @@ function buildGroup(fetcherName, results, parse, abi) {
41568
41710
  }
41569
41711
  return group;
41570
41712
  }
41571
- function isFailed(r) {
41713
+ function isFailed2(r) {
41572
41714
  return r === "0x" || r === void 0;
41573
41715
  }
41574
41716
  async function executeGroup(group, chainId, chainBatchSize, retries, allowFailure, rpcOverrides, failRetries = 2) {
@@ -41587,7 +41729,7 @@ async function executeGroup(group, chainId, chainBatchSize, retries, allowFailur
41587
41729
  overrdies: rpcOverrides,
41588
41730
  logErrors: true
41589
41731
  });
41590
- let failedIndices = results.map((r, i) => isFailed(r) ? i : -1).filter((i) => i >= 0);
41732
+ let failedIndices = results.map((r, i) => isFailed2(r) ? i : -1).filter((i) => i >= 0);
41591
41733
  for (let round = 0; round < failRetries && failedIndices.length > 0; round++) {
41592
41734
  const retryCalls = failedIndices.map((i) => group.calls[i]);
41593
41735
  try {
@@ -41605,7 +41747,7 @@ async function executeGroup(group, chainId, chainBatchSize, retries, allowFailur
41605
41747
  });
41606
41748
  const stillFailed = [];
41607
41749
  for (let j = 0; j < failedIndices.length; j++) {
41608
- if (!isFailed(retryResults[j])) {
41750
+ if (!isFailed2(retryResults[j])) {
41609
41751
  results[failedIndices[j]] = retryResults[j];
41610
41752
  } else {
41611
41753
  stillFailed.push(failedIndices[j]);
@@ -41616,7 +41758,7 @@ async function executeGroup(group, chainId, chainBatchSize, retries, allowFailur
41616
41758
  break;
41617
41759
  }
41618
41760
  }
41619
- const failCount = results.filter((r) => isFailed(r)).length;
41761
+ const failCount = results.filter((r) => isFailed2(r)).length;
41620
41762
  const allFailed = failCount === results.length && results.length > 0;
41621
41763
  return {
41622
41764
  results,
@@ -41630,7 +41772,7 @@ async function executeGroup(group, chainId, chainBatchSize, retries, allowFailur
41630
41772
  };
41631
41773
  }
41632
41774
  }
41633
- async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3, batchSize = void 0, allowFailure = true, basePrices = {}, morphoMarketOverrides, listaMarketOverrides, stalenessThresholdSeconds = 3600, onlyFetchers) {
41775
+ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3, batchSize = void 0, allowFailure = true, basePrices = {}, morphoMarketOverrides, listaMarketOverrides, stalenessThresholdSeconds = 3600, onlyFetchers, probeFeedStaleness = true) {
41634
41776
  const totalStart = Date.now();
41635
41777
  const result = {};
41636
41778
  const chainPromises = chainIds.map(async (chainId) => {
@@ -41698,7 +41840,11 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
41698
41840
  () => exactlyFetcher.getCalls(chainId),
41699
41841
  getCallsErrors
41700
41842
  ) : [];
41701
- const termResults = isActive("term") ? safeGetCalls("term", () => termFetcher.getCalls(chainId), getCallsErrors) : [];
41843
+ const termResults = isActive("term") ? safeGetCalls(
41844
+ "term",
41845
+ () => termFetcher.getCalls(chainId),
41846
+ getCallsErrors
41847
+ ) : [];
41702
41848
  const liquityResults = isActive("liquity") ? safeGetCalls(
41703
41849
  "liquity",
41704
41850
  () => liquityFetcher.getCalls(chainId),
@@ -41743,109 +41889,127 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
41743
41889
  "aave",
41744
41890
  aaveResults,
41745
41891
  aaveFetcher.parse,
41746
- AaveOracleAbi
41892
+ AaveOracleAbi,
41893
+ "direct"
41747
41894
  );
41748
41895
  const compoundV2Group = buildGroup(
41749
41896
  "compoundV2",
41750
41897
  compoundV2Results,
41751
41898
  compoundV2Fetcher.parse,
41752
- CompoundV2OracleAbi
41899
+ CompoundV2OracleAbi,
41900
+ "direct"
41753
41901
  );
41754
41902
  const compoundV3Group = buildGroup(
41755
41903
  "compoundV3",
41756
41904
  compoundV3Results,
41757
41905
  compoundV3Fetcher.parse,
41758
- ChainlinkAggregatorAbi
41906
+ ChainlinkAggregatorAbi,
41907
+ "direct"
41759
41908
  );
41760
41909
  const listaGroup = buildGroup(
41761
41910
  "lista",
41762
41911
  listaResults,
41763
41912
  listaFetcher.parse,
41764
- ProxyOracleAbi
41913
+ ProxyOracleAbi,
41914
+ "direct"
41765
41915
  );
41766
41916
  const eulerGroup = buildGroup(
41767
41917
  "eulerV2",
41768
41918
  eulerResults,
41769
41919
  eulerV2Fetcher.parse,
41770
- getEulerV2Abi()
41920
+ getEulerV2Abi(),
41921
+ "direct"
41771
41922
  );
41772
41923
  const aaveV4Group = buildGroup(
41773
41924
  "aaveV4",
41774
41925
  aaveV4Results,
41775
41926
  aaveV4Fetcher.parse,
41776
- getAaveV4Abi()
41927
+ getAaveV4Abi(),
41928
+ "direct"
41777
41929
  );
41778
41930
  const morphoGroup = buildGroup(
41779
41931
  "morpho",
41780
41932
  morphoResults,
41781
41933
  morphoFetcher.parse,
41782
- ProxyOracleAbi
41934
+ ProxyOracleAbi,
41935
+ "derived"
41783
41936
  );
41784
41937
  const midnightGroup = buildGroup(
41785
41938
  "midnight",
41786
41939
  midnightResults,
41787
41940
  midnightFetcher.parse,
41788
- ProxyOracleAbi
41941
+ ProxyOracleAbi,
41942
+ "derived"
41789
41943
  );
41790
41944
  const exactlyGroup = buildGroup(
41791
41945
  "exactly",
41792
41946
  exactlyResults,
41793
41947
  exactlyFetcher.parse,
41794
- getExactlyAbi()
41948
+ getExactlyAbi(),
41949
+ "direct"
41795
41950
  );
41796
41951
  const termGroup = buildGroup(
41797
41952
  "term",
41798
41953
  termResults,
41799
41954
  termFetcher.parse,
41800
- getTermAbi()
41955
+ getTermAbi(),
41956
+ "direct"
41801
41957
  );
41802
41958
  const liquityGroup = buildGroup(
41803
41959
  "liquity",
41804
41960
  liquityResults,
41805
41961
  liquityFetcher.parse,
41806
- getLiquityAbi()
41962
+ getLiquityAbi(),
41963
+ "direct"
41807
41964
  );
41808
41965
  const riverGroup = buildGroup(
41809
41966
  "river",
41810
41967
  riverResults,
41811
41968
  riverFetcher.parse,
41812
- getRiverAbi()
41969
+ getRiverAbi(),
41970
+ "direct"
41813
41971
  );
41814
41972
  const tellerGroup = buildGroup(
41815
41973
  "teller",
41816
41974
  tellerResults,
41817
41975
  tellerFetcher.parse,
41818
- getTellerAbi()
41976
+ getTellerAbi(),
41977
+ "derived"
41819
41978
  );
41820
41979
  const siloV2Group = buildGroup(
41821
41980
  "siloV2",
41822
41981
  siloV2Results,
41823
41982
  siloV2Fetcher.parse,
41824
- getSiloV2Abi()
41983
+ getSiloV2Abi(),
41984
+ "derived"
41825
41985
  );
41826
41986
  const siloV3Group = buildGroup(
41827
41987
  "siloV3",
41828
41988
  siloV3Results,
41829
41989
  siloV3Fetcher.parse,
41830
- getSiloV3Abi()
41990
+ getSiloV3Abi(),
41991
+ "derived"
41831
41992
  );
41832
41993
  const fluidGroup = buildGroup(
41833
41994
  "fluid",
41834
41995
  fluidResults,
41835
41996
  fluidFetcher.parse,
41836
- getFluidAbi()
41997
+ getFluidAbi(),
41998
+ "derived"
41837
41999
  );
41838
42000
  const gearboxV3Group = buildGroup(
41839
42001
  "gearboxV3",
41840
42002
  gearboxV3Results,
41841
42003
  gearboxV3Fetcher.parse,
41842
- getGearboxV3Abi()
42004
+ getGearboxV3Abi(),
42005
+ "derived"
41843
42006
  );
41844
42007
  const dolomiteGroup = buildGroup(
41845
42008
  "dolomite",
41846
42009
  dolomiteResults,
41847
42010
  dolomiteFetcher.parse,
41848
- getDolomiteAbi()
42011
+ getDolomiteAbi(),
42012
+ "direct"
41849
42013
  );
41850
42014
  const allGroups = [
41851
42015
  aaveGroup,
@@ -41886,6 +42050,11 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
41886
42050
  const morphoGqlPromise = morphoUseMystic ? fetchMorphoMysticPrices(chainId) : morphoUseApi ? fetchMorphoGraphQLPrices(chainId) : Promise.resolve(null);
41887
42051
  const siloV2GqlPromise = isActive("silov2") ? fetchSiloV2GraphQLPrices(chainId, basePrices, tokenList) : Promise.resolve(null);
41888
42052
  const siloV3GqlPromise = isActive("silov3") ? fetchSiloV3GraphQLPrices(chainId, basePrices, tokenList) : Promise.resolve(null);
42053
+ const feedTimestampsPromise = stalenessThresholdSeconds > 0 && probeFeedStaleness ? probeAaveFeedTimestamps(chainId, {
42054
+ rpcOverrides,
42055
+ batchSize: chainBatchSize,
42056
+ retries
42057
+ }) : Promise.resolve({});
41889
42058
  const [
41890
42059
  aaveData,
41891
42060
  compoundV2Data,
@@ -42112,6 +42281,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42112
42281
  if (!chainResult[lender]) {
42113
42282
  chainResult[lender] = [];
42114
42283
  }
42284
+ entry.derivation = resolveDerivation(entry, group.derivation);
42115
42285
  chainResult[lender].push(entry);
42116
42286
  if (updatePrices) {
42117
42287
  const oracleKey = tokenList[entry.asset]?.assetGroup ?? `${chainId}-${entry.asset}`;
@@ -42136,29 +42306,6 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42136
42306
  parseTrackers(listaGroup, listaData.results);
42137
42307
  parseTrackers(eulerGroup, eulerData.results);
42138
42308
  parseTrackers(aaveV4Group, aaveV4Data.results);
42139
- if (stalenessThresholdSeconds > 0) {
42140
- const nowSeconds = Math.floor(Date.now() / 1e3);
42141
- for (const tracker of compoundV3Group.trackers) {
42142
- const meta = tracker.meta;
42143
- for (let i = 0; i < tracker.count; i++) {
42144
- const raw = compoundV3Data.results[tracker.offset + i];
42145
- if (!raw || raw === "0x" || !Array.isArray(raw)) continue;
42146
- const updatedAt = Number(raw[3]);
42147
- if (updatedAt > 0) {
42148
- const staleSeconds = nowSeconds - updatedAt;
42149
- if (staleSeconds > stalenessThresholdSeconds) {
42150
- const queryMeta = meta[i];
42151
- staleFeeds.push({
42152
- asset: queryMeta?.asset?.toLowerCase() ?? "unknown",
42153
- lender: queryMeta?.lender ?? tracker.lender,
42154
- oracle: queryMeta?.oracle ?? "unknown",
42155
- staleSeconds
42156
- });
42157
- }
42158
- }
42159
- }
42160
- }
42161
- }
42162
42309
  parseTrackers(
42163
42310
  aaveGroup,
42164
42311
  aaveData.results,
@@ -42182,6 +42329,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42182
42329
  for (const entry of siloV2GqlEntries) {
42183
42330
  const lender = entry.targetLender ?? "SILO_V2";
42184
42331
  if (!chainResult[lender]) chainResult[lender] = [];
42332
+ entry.derivation = resolveDerivation(entry, "derived");
42185
42333
  chainResult[lender].push(entry);
42186
42334
  const oracleKey = tokenList[entry.asset]?.assetGroup ?? `${chainId}-${entry.asset}`;
42187
42335
  usdPrices[oracleKey] = entry.priceUSD;
@@ -42201,6 +42349,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42201
42349
  for (const entry of siloV3GqlEntries) {
42202
42350
  const lender = entry.targetLender ?? "SILO_V3";
42203
42351
  if (!chainResult[lender]) chainResult[lender] = [];
42352
+ entry.derivation = resolveDerivation(entry, "derived");
42204
42353
  chainResult[lender].push(entry);
42205
42354
  const oracleKey = tokenList[entry.asset]?.assetGroup ?? `${chainId}-${entry.asset}`;
42206
42355
  usdPrices[oracleKey] = entry.priceUSD;
@@ -42232,6 +42381,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42232
42381
  const key = `${lender}|${entry.marketUid}|${entry.asset}`;
42233
42382
  if (onChainKeys.has(key)) continue;
42234
42383
  if (!chainResult[lender]) chainResult[lender] = [];
42384
+ entry.derivation = resolveDerivation(entry, "derived");
42235
42385
  chainResult[lender].push(entry);
42236
42386
  gqlPushed++;
42237
42387
  }
@@ -42251,6 +42401,7 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42251
42401
  for (const entry of morphoGqlEntries) {
42252
42402
  const lender = entry.targetLender ?? "MORPHO_BLUE";
42253
42403
  if (!chainResult[lender]) chainResult[lender] = [];
42404
+ entry.derivation = resolveDerivation(entry, "derived");
42254
42405
  chainResult[lender].push(entry);
42255
42406
  }
42256
42407
  trackerDiags.push(morphoGqlDiag);
@@ -42277,6 +42428,39 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42277
42428
  }
42278
42429
  parseTrackers(midnightGroup, midnightData.results, false);
42279
42430
  parseTrackers(tellerGroup, tellerData.results, false);
42431
+ if (stalenessThresholdSeconds > 0) {
42432
+ const feedTimestamps = await feedTimestampsPromise;
42433
+ for (const [lender, assetMap] of Object.entries(feedTimestamps)) {
42434
+ for (const entry of chainResult[lender] ?? []) {
42435
+ const info = assetMap[entry.asset];
42436
+ if (!info) continue;
42437
+ entry.updatedAt = info.updatedAt;
42438
+ entry.feedSource = info.source;
42439
+ if (!entry.description && info.description) {
42440
+ entry.description = info.description;
42441
+ }
42442
+ }
42443
+ }
42444
+ const nowSeconds = Math.floor(Date.now() / 1e3);
42445
+ const reported = /* @__PURE__ */ new Set();
42446
+ for (const [lender, entries] of Object.entries(chainResult)) {
42447
+ for (const entry of entries) {
42448
+ if (!entry.updatedAt || entry.updatedAt <= 0) continue;
42449
+ const staleSeconds = nowSeconds - entry.updatedAt;
42450
+ if (staleSeconds <= stalenessThresholdSeconds) continue;
42451
+ const key = `${lender}|${entry.asset}|${entry.feedSource ?? ""}`;
42452
+ if (reported.has(key)) continue;
42453
+ reported.add(key);
42454
+ staleFeeds.push({
42455
+ asset: entry.asset,
42456
+ lender,
42457
+ oracle: entry.feedSource ?? "unknown",
42458
+ staleSeconds,
42459
+ description: entry.description
42460
+ });
42461
+ }
42462
+ }
42463
+ }
42280
42464
  const totalFailedCalls = trackerDiags.reduce(
42281
42465
  (sum, d) => sum + d.failedCalls,
42282
42466
  0
@@ -42332,6 +42516,217 @@ async function fetchOraclePrices(chainIds, rpcOverrides, lists = {}, retries = 3
42332
42516
  };
42333
42517
  return { prices: result, diagnostics };
42334
42518
  }
42519
+
42520
+ // src/prices/oracle-prices/lenderKeys.ts
42521
+ function lenderFamily(lender) {
42522
+ return lender.replace(/_(0X)?[0-9A-F]{6,}$/i, "");
42523
+ }
42524
+
42525
+ // src/prices/oracle-prices/outlierGuard.ts
42526
+ var DEFAULT_OUTLIER_GUARD = {
42527
+ minAgreeingFamilies: 3,
42528
+ agreementTolerance: 0.05,
42529
+ rejectFactor: 2
42530
+ };
42531
+ function median(values) {
42532
+ const sorted = [...values].sort((a, b) => a - b);
42533
+ const mid = Math.floor(sorted.length / 2);
42534
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
42535
+ }
42536
+ function consensusReference(candidates, guard = DEFAULT_OUTLIER_GUARD) {
42537
+ if (candidates.length < guard.minAgreeingFamilies) return void 0;
42538
+ const perFeed = /* @__PURE__ */ new Map();
42539
+ for (const c of candidates) {
42540
+ const bucket = perFeed.get(c.feedKey);
42541
+ if (bucket) bucket.push(c.priceUSD);
42542
+ else perFeed.set(c.feedKey, [c.priceUSD]);
42543
+ }
42544
+ if (perFeed.size < guard.minAgreeingFamilies) return void 0;
42545
+ const feedPrices = [...perFeed.values()].map(median);
42546
+ const reference = median(feedPrices);
42547
+ if (!(reference > 0)) return void 0;
42548
+ const agreeing = feedPrices.filter(
42549
+ (p) => Math.abs(p / reference - 1) <= guard.agreementTolerance
42550
+ ).length;
42551
+ if (agreeing < guard.minAgreeingFamilies) return void 0;
42552
+ return reference;
42553
+ }
42554
+ function rejectOutliers(candidates, guard = DEFAULT_OUTLIER_GUARD) {
42555
+ const reference = consensusReference(candidates, guard);
42556
+ if (reference === void 0) return candidates;
42557
+ const upper = reference * guard.rejectFactor;
42558
+ const lower = reference / guard.rejectFactor;
42559
+ const kept = candidates.filter(
42560
+ (c) => c.priceUSD <= upper && c.priceUSD >= lower
42561
+ );
42562
+ return kept.length > 0 ? kept : candidates;
42563
+ }
42564
+
42565
+ // src/prices/oracle-prices/feedStats.ts
42566
+ var EWMA_ALPHA = 0.1;
42567
+ var MIN_DEVIATION_SAMPLES = 5;
42568
+ var BAD_DEVIATION = 0.25;
42569
+ var DEGRADED_DEVIATION = 0.05;
42570
+ var STALE_INTERVAL_MULTIPLE = 4;
42571
+ var MIN_LEARNED_STALE_SECONDS = 6 * 3600;
42572
+ var MIN_UPDATES_FOR_INTERVAL = 4;
42573
+ var FRESHNESS_RANK = {
42574
+ fresh: 0,
42575
+ unknown: 0,
42576
+ // absence of evidence must not demote
42577
+ stale: 1
42578
+ };
42579
+ var RELIABILITY_RANK = {
42580
+ good: 0,
42581
+ unknown: 0,
42582
+ // absence of evidence must not demote
42583
+ degraded: 1,
42584
+ bad: 2
42585
+ };
42586
+ function feedKeyOf(entry, lender) {
42587
+ if (entry.feedSource) return entry.feedSource.toLowerCase();
42588
+ return `${lenderFamily(lender)}|${entry.asset.toLowerCase()}`;
42589
+ }
42590
+ function feedStatKey(chainId, feedKey) {
42591
+ return `${chainId}|${feedKey}`;
42592
+ }
42593
+ function ewma(prev, sample) {
42594
+ if (prev === void 0 || !(prev > 0)) return sample;
42595
+ return prev * (1 - EWMA_ALPHA) + sample * EWMA_ALPHA;
42596
+ }
42597
+ function updateFeedStats(prev, observations, nowSeconds = Math.floor(Date.now() / 1e3)) {
42598
+ const next = { ...prev };
42599
+ for (const obs of observations) {
42600
+ const key = feedStatKey(obs.chainId, obs.feedKey);
42601
+ const existing = next[key];
42602
+ const stat = existing ? { ...existing } : {
42603
+ chainId: obs.chainId,
42604
+ feedKey: obs.feedKey,
42605
+ lastUpdatedAt: 0,
42606
+ lastObservedAt: nowSeconds,
42607
+ updates: 0,
42608
+ intervalEwmaSeconds: 0,
42609
+ intervalMaxSeconds: 0,
42610
+ deviationSamples: 0
42611
+ };
42612
+ if (obs.updatedAt && obs.updatedAt > 0) {
42613
+ if (obs.updatedAt > stat.lastUpdatedAt) {
42614
+ if (stat.lastUpdatedAt > 0) {
42615
+ const interval = obs.updatedAt - stat.lastUpdatedAt;
42616
+ stat.intervalEwmaSeconds = ewma(stat.intervalEwmaSeconds, interval);
42617
+ stat.intervalMaxSeconds = Math.max(stat.intervalMaxSeconds, interval);
42618
+ }
42619
+ stat.lastUpdatedAt = obs.updatedAt;
42620
+ stat.updates += 1;
42621
+ }
42622
+ }
42623
+ if (obs.consensusDeviation !== void 0) {
42624
+ stat.deviationEwma = ewma(stat.deviationEwma, obs.consensusDeviation);
42625
+ stat.deviationSamples += 1;
42626
+ }
42627
+ stat.lastObservedAt = nowSeconds;
42628
+ next[key] = stat;
42629
+ }
42630
+ return next;
42631
+ }
42632
+ function classifyFreshness(updatedAt, stat, defaultStaleSeconds, nowSeconds = Math.floor(Date.now() / 1e3)) {
42633
+ if (!updatedAt || updatedAt <= 0) return "unknown";
42634
+ const age = nowSeconds - updatedAt;
42635
+ if (age < 0) return "fresh";
42636
+ const learned = stat && stat.updates >= MIN_UPDATES_FOR_INTERVAL ? Math.max(
42637
+ stat.intervalEwmaSeconds * STALE_INTERVAL_MULTIPLE,
42638
+ stat.intervalMaxSeconds * 1.5,
42639
+ MIN_LEARNED_STALE_SECONDS
42640
+ ) : void 0;
42641
+ const threshold = learned === void 0 ? defaultStaleSeconds : Math.min(learned, defaultStaleSeconds * 2);
42642
+ return age > threshold ? "stale" : "fresh";
42643
+ }
42644
+ function classifyReliability(stat) {
42645
+ if (!stat) return "unknown";
42646
+ if (stat.deviationEwma === void 0 || stat.deviationSamples < MIN_DEVIATION_SAMPLES) {
42647
+ return "unknown";
42648
+ }
42649
+ if (stat.deviationEwma > BAD_DEVIATION) return "bad";
42650
+ if (stat.deviationEwma > DEGRADED_DEVIATION) return "degraded";
42651
+ return "good";
42652
+ }
42653
+ function collectFeedObservations(structuredPrices, lists = {}, guard = DEFAULT_OUTLIER_GUARD) {
42654
+ const byGroup = /* @__PURE__ */ new Map();
42655
+ const samples = [];
42656
+ for (const [chainId, lenders] of Object.entries(structuredPrices)) {
42657
+ const tokenList = lists[chainId] ?? {};
42658
+ for (const [lender, entries] of Object.entries(lenders)) {
42659
+ if (!Array.isArray(entries)) continue;
42660
+ for (const entry of entries) {
42661
+ if (!entry?.asset || !(entry.priceUSD > 0)) continue;
42662
+ const sample = {
42663
+ chainId,
42664
+ feedKey: feedKeyOf(entry, lender),
42665
+ priceUSD: entry.priceUSD,
42666
+ updatedAt: entry.updatedAt
42667
+ };
42668
+ samples.push(sample);
42669
+ const groupKey = tokenList[entry.asset]?.assetGroup ?? `${chainId}-${entry.asset.toLowerCase()}`;
42670
+ const bucket = byGroup.get(groupKey);
42671
+ if (bucket) bucket.push(sample);
42672
+ else byGroup.set(groupKey, [sample]);
42673
+ }
42674
+ }
42675
+ }
42676
+ const observations = [];
42677
+ const seen = /* @__PURE__ */ new Set();
42678
+ for (const [, group] of byGroup) {
42679
+ const perFeed = /* @__PURE__ */ new Map();
42680
+ for (const s of group) {
42681
+ const bucket = perFeed.get(s.feedKey);
42682
+ if (bucket) bucket.push(s.priceUSD);
42683
+ else perFeed.set(s.feedKey, [s.priceUSD]);
42684
+ }
42685
+ if (perFeed.size < 2) continue;
42686
+ const feedPrices = [...perFeed.entries()].map(([feedKey, prices]) => ({
42687
+ feedKey,
42688
+ price: median(prices)
42689
+ }));
42690
+ for (const { feedKey, price: price2 } of feedPrices) {
42691
+ const others = feedPrices.filter((f) => f.feedKey !== feedKey).map((f) => ({ feedKey: f.feedKey, priceUSD: f.price }));
42692
+ const reference = consensusReference(others, guard);
42693
+ const sample = group.find((s) => s.feedKey === feedKey);
42694
+ const dedupKey = `${sample.chainId}|${feedKey}`;
42695
+ if (seen.has(dedupKey)) continue;
42696
+ seen.add(dedupKey);
42697
+ observations.push({
42698
+ chainId: sample.chainId,
42699
+ feedKey,
42700
+ updatedAt: sample.updatedAt,
42701
+ // Undefined when the rest of the group does not agree on anything —
42702
+ // no verdict is recorded rather than a meaningless one.
42703
+ consensusDeviation: reference === void 0 ? void 0 : Math.abs(price2 / reference - 1)
42704
+ });
42705
+ }
42706
+ }
42707
+ for (const s of samples) {
42708
+ const dedupKey = `${s.chainId}|${s.feedKey}`;
42709
+ if (seen.has(dedupKey)) continue;
42710
+ seen.add(dedupKey);
42711
+ observations.push({
42712
+ chainId: s.chainId,
42713
+ feedKey: s.feedKey,
42714
+ updatedAt: s.updatedAt
42715
+ });
42716
+ }
42717
+ return observations;
42718
+ }
42719
+ function pruneFeedStats(stats, maxAgeSeconds = 30 * 24 * 3600, nowSeconds = Math.floor(Date.now() / 1e3)) {
42720
+ const out = {};
42721
+ for (const [key, stat] of Object.entries(stats)) {
42722
+ if (nowSeconds - stat.lastObservedAt <= maxAgeSeconds) out[key] = stat;
42723
+ }
42724
+ return out;
42725
+ }
42726
+
42727
+ // src/prices/oracle-prices/selectAssetGroupPrices.ts
42728
+ var DEFAULT_STALE_REJECT_SECONDS = 172800;
42729
+ var AAVE_V4_PREFIX = "AAVE_V4";
42335
42730
  var DEFAULT_PRIORITY = {
42336
42731
  chainPriority: [
42337
42732
  Chain.ETHEREUM_MAINNET,
@@ -42340,7 +42735,10 @@ var DEFAULT_PRIORITY = {
42340
42735
  Chain.BASE,
42341
42736
  Chain.AVALANCHE_C_CHAIN
42342
42737
  ],
42343
- lenderPriority: [Lender.AAVE_V3, "COMPOUND_V3"],
42738
+ // Aave V4 has no enum member — every key is the runtime-synthesized
42739
+ // `AAVE_V4_<SPOKE_HEX>` — so it is matched by the literal prefix, the same
42740
+ // way `isAaveV4Type()` does.
42741
+ lenderPriority: [Lender.AAVE_V3, AAVE_V4_PREFIX, "COMPOUND_V3"],
42344
42742
  lowPriorityLenders: [Lender.ZEROLEND, Lender.AVALON, Lender.MORPHO_BLUE],
42345
42743
  excludedLenders: {
42346
42744
  // paused
@@ -42351,64 +42749,140 @@ var DEFAULT_PRIORITY = {
42351
42749
  "1": [Lender.ZEROLEND_STABLECOINS_RWA, Lender.ZEROLEND_ETH_LRTS],
42352
42750
  "534352": ["LORE"],
42353
42751
  "167000": [Lender.TAKOTAKO, Lender.TAKOTAKO_ETH],
42354
- "56": [Lender.AVALON_USDX],
42752
+ // GAMMA (Planet Finance) is abandoned and its oracle is frozen at ~2022
42753
+ // prices across the board — AVAX $23.78 vs $6.44, BTCB $23k vs $64k, and
42754
+ // it still quotes LUNA/UST. Kept as a hard veto because it prices several
42755
+ // dead assets NOTHING else prices, so no consensus can form to catch it.
42756
+ "56": [Lender.AVALON_USDX, Lender.GAMMA],
42355
42757
  // broken
42356
42758
  "169": [Lender.ZEROLEND]
42357
42759
  },
42358
42760
  lenderPriorityPerChain: {
42359
- [Chain.BNB_SMART_CHAIN_MAINNET]: [Lender.AAVE_V3, Lender.VENUS],
42761
+ // NOTE: a per-chain list REPLACES the global one, so it has to repeat
42762
+ // every prefix that should still rank on that chain.
42763
+ [Chain.BNB_SMART_CHAIN_MAINNET]: [
42764
+ Lender.AAVE_V3,
42765
+ AAVE_V4_PREFIX,
42766
+ Lender.VENUS
42767
+ ],
42360
42768
  [Chain.XDC_NETWORK]: [Lender.PRIME_FI, Lender.FATHOM],
42361
42769
  [Chain.HYPEREVM]: [Lender.HYPERLEND, Lender.HYPURRFI]
42362
42770
  },
42363
42771
  lowPriorityLendersPerChain: {},
42364
- excludedLendersPerChain: {}
42365
- };
42366
- function computePriority(lender, chainId, cfg) {
42367
- const NOT_FOUND = 1e3;
42772
+ excludedLendersPerChain: {},
42773
+ staleRejectSeconds: DEFAULT_STALE_REJECT_SECONDS,
42774
+ outlierGuard: DEFAULT_OUTLIER_GUARD
42775
+ };
42776
+ var LENDER_RANK_UNLISTED = 1e3;
42777
+ var LENDER_RANK_LOW = 2e3;
42778
+ var CHAIN_RANK_UNLISTED = 1e3;
42779
+ function lenderRankOf(lender, chainId, cfg) {
42368
42780
  const excluded = cfg.excludedLenders[chainId];
42369
42781
  if (excluded?.some((p) => lender.startsWith(p))) return -1;
42370
42782
  const excludedPerChain = cfg.excludedLendersPerChain[chainId];
42371
42783
  if (excludedPerChain?.some((p) => lender.startsWith(p))) return -1;
42372
42784
  const lowPerChain = cfg.lowPriorityLendersPerChain[chainId];
42373
42785
  const isLow = lowPerChain ? lowPerChain.some((p) => lender.startsWith(p)) : cfg.lowPriorityLenders.some((p) => lender.startsWith(p));
42374
- if (isLow) return 2e6;
42786
+ if (isLow) return LENDER_RANK_LOW;
42375
42787
  const lenderPriority = cfg.lenderPriorityPerChain[chainId] ?? cfg.lenderPriority;
42376
- const lenderIdx = lenderPriority.findIndex((p) => lender.startsWith(p));
42377
- const lenderScore = lenderIdx >= 0 ? lenderIdx : NOT_FOUND;
42378
- const chainIdx = cfg.chainPriority.indexOf(chainId);
42379
- const chainScore = chainIdx >= 0 ? chainIdx : NOT_FOUND;
42380
- return lenderScore * 1e4 + chainScore;
42381
- }
42382
- function selectAssetGroupPrices(structuredPrices, lists = {}, tvlMap = {}, priorityCfg = DEFAULT_PRIORITY) {
42383
- const candidates = [];
42788
+ const idx = lenderPriority.findIndex((p) => lender.startsWith(p));
42789
+ return idx >= 0 ? idx : LENDER_RANK_UNLISTED;
42790
+ }
42791
+ function depthOf(entryMarketUid, lender, depth) {
42792
+ if (!depth) return 0;
42793
+ if (entryMarketUid) {
42794
+ const perMarket = depth[entryMarketUid];
42795
+ if (typeof perMarket === "number") return perMarket;
42796
+ }
42797
+ const perLender = depth[lender];
42798
+ return typeof perLender === "number" ? perLender : 0;
42799
+ }
42800
+ function compareCandidates(a, b) {
42801
+ return a.freshnessRank - b.freshnessRank || a.reliabilityRank - b.reliabilityRank || a.derivationRank - b.derivationRank || a.lenderRank - b.lenderRank || b.depth - a.depth || a.chainRank - b.chainRank || cmpStr(a.lender, b.lender) || cmpStr(a.chainId, b.chainId) || // One lender can emit many entries for the same asset (Euler prices a
42802
+ // token in every vault that lists it), so lender+chain is NOT a unique
42803
+ // identity. Without these last two the winner among them fell out of
42804
+ // enumeration order and the flat price moved between runs.
42805
+ cmpStr(a.marketUid, b.marketUid) || a.priceUSD - b.priceUSD;
42806
+ }
42807
+ function cmpStr(a, b) {
42808
+ return a < b ? -1 : a > b ? 1 : 0;
42809
+ }
42810
+ function selectAssetGroupPrices(structuredPrices, lists = {}, depthOrTvl = {}, priorityCfg = DEFAULT_PRIORITY, options = {}) {
42811
+ const staleRejectSeconds = priorityCfg.staleRejectSeconds ?? DEFAULT_STALE_REJECT_SECONDS;
42812
+ const guard = priorityCfg.outlierGuard === void 0 ? DEFAULT_OUTLIER_GUARD : priorityCfg.outlierGuard;
42813
+ const depth = options.depth ?? depthOrTvl;
42814
+ const feedStats = options.feedStats;
42815
+ const nowSeconds = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
42816
+ const byKey = /* @__PURE__ */ new Map();
42384
42817
  for (const [chainId, lenders] of Object.entries(structuredPrices)) {
42385
42818
  const tokenList = lists[chainId] ?? {};
42386
42819
  for (const [lender, entries] of Object.entries(lenders)) {
42387
42820
  if (!Array.isArray(entries)) continue;
42388
- const prio = computePriority(lender, chainId, priorityCfg);
42389
- if (prio < 0) continue;
42390
- const tvl = tvlMap[lender] ?? 0;
42821
+ const lenderRank = lenderRankOf(lender, chainId, priorityCfg);
42822
+ if (lenderRank < 0) continue;
42823
+ const family = lenderFamily(lender);
42824
+ const chainIdx = priorityCfg.chainPriority.indexOf(chainId);
42825
+ const chainRank = chainIdx >= 0 ? chainIdx : CHAIN_RANK_UNLISTED;
42391
42826
  for (const entry of entries) {
42392
42827
  if (!entry?.asset || entry.priceUSD <= 0) continue;
42828
+ const feedKey = feedKeyOf(entry, lender);
42829
+ const stat = feedStats?.[feedStatKey(chainId, feedKey)];
42830
+ const freshness = staleRejectSeconds > 0 ? classifyFreshness(
42831
+ entry.updatedAt,
42832
+ stat,
42833
+ staleRejectSeconds,
42834
+ nowSeconds
42835
+ ) : "unknown";
42393
42836
  const oracleKey = tokenList[entry.asset]?.assetGroup ?? `${chainId}-${entry.asset.toLowerCase()}`;
42394
- candidates.push({
42837
+ const candidate = {
42395
42838
  oracleKey,
42396
42839
  priceUSD: entry.priceUSD,
42397
- priority: prio,
42398
- tvl
42399
- });
42840
+ chainId,
42841
+ lender,
42842
+ family,
42843
+ feedKey,
42844
+ marketUid: entry.marketUid ?? "",
42845
+ derivationRank: DERIVATION_RANK[resolveDerivation(entry)],
42846
+ freshnessRank: FRESHNESS_RANK[freshness],
42847
+ reliabilityRank: RELIABILITY_RANK[classifyReliability(stat)],
42848
+ lenderRank,
42849
+ depth: depthOf(entry.marketUid, lender, depth),
42850
+ chainRank
42851
+ };
42852
+ const bucket = byKey.get(oracleKey);
42853
+ if (bucket) bucket.push(candidate);
42854
+ else byKey.set(oracleKey, [candidate]);
42400
42855
  }
42401
42856
  }
42402
42857
  }
42403
- candidates.sort((a, b) => a.priority - b.priority || b.tvl - a.tvl);
42404
42858
  const flatPrices = {};
42405
- for (const c of candidates) {
42406
- if (!(c.oracleKey in flatPrices)) {
42407
- flatPrices[c.oracleKey] = c.priceUSD;
42859
+ for (const [oracleKey, candidates] of byKey) {
42860
+ const viable = guard ? rejectOutliers(candidates, guard) : candidates;
42861
+ let best = viable[0];
42862
+ for (const c of viable) {
42863
+ if (compareCandidates(c, best) < 0) best = c;
42864
+ }
42865
+ if (!best) continue;
42866
+ flatPrices[oracleKey] = best.priceUSD;
42867
+ if (options.onSelection) {
42868
+ options.onSelection({
42869
+ assetGroup: oracleKey,
42870
+ priceUSD: best.priceUSD,
42871
+ chainId: best.chainId,
42872
+ lender: best.lender,
42873
+ feedKey: best.feedKey,
42874
+ derivation: derivationOfRank(best.derivationRank),
42875
+ candidates: candidates.length,
42876
+ rejected: candidates.length - viable.length
42877
+ });
42408
42878
  }
42409
42879
  }
42410
42880
  return flatPrices;
42411
42881
  }
42882
+ var RANK_TO_DERIVATION = ["direct", "quoted", "derived"];
42883
+ function derivationOfRank(rank) {
42884
+ return RANK_TO_DERIVATION[rank] ?? "derived";
42885
+ }
42412
42886
  var { uniq: uniq2 } = lodash;
42413
42887
  function createAssetArrayPerChain() {
42414
42888
  let assetsPerChain = {};
@@ -53111,6 +53585,6 @@ async function fetchTokenBalances(chainId, account, tokens, options = {}) {
53111
53585
  return parseTokenBalanceResult(rawResult, prepared.query);
53112
53586
  }
53113
53587
 
53114
- export { ApiBookSource, DEFAULT_MIDNIGHT_API, EMPTY_BALANCE, GMX_API_HOSTS, GMX_READ_CONTRACTS, GMX_SUPPORTED_CHAINS, HYPERCORE_VAULT_REGISTRY, IDLE_MARKET_ID, INTERFACE_IDS, LAGOON_API_URL, LAGOON_CHAIN_IDS, MORPHO_LENS, MaxParamThresholds, STABLECOIN_SYMBOLS, STCELO_MANAGER_ADDRESS, TELLER_CALLS_PER_BID, TermSubgraphSource, UPSHIFT_CHAIN_IDS, UPSHIFT_VAULTS_URL, VAULT_SHARE_PRICE_PROBE, VOLATILE_VAULT_OVERRIDES, YEARN_CHAIN_IDS, YEARN_YDAEMON_BASE, accountDepositListKey, accountWithdrawalListKey, appendSnapshot, applyPositionDelta, attachPricesToFlashLiquidity, buildFluidFTokensCall, buildLiquityUserCall, buildLoopResult, buildMorphoTypeCall, buildMorphoTypeUserCallWithLens, buildPortfolioTotals, buildSumerAccumulators, buildSummaries, buildTellerUserCall, buildVaultLookup, calculateLeverage, calculateNetApr, calculateOverallNetApr, calculateWeightedAverage, classifyVault, computeBorrowDelta2 as computeBorrowDelta, computeCloseTradeDeltas, computeCollateralSwapDeltas, computeDebtSwapDeltas, computeDepositDelta2 as computeDepositDelta, computeEModeAnalysis, computeOpenTradeDeltas, computePostTradeMetrics, computeRepayDelta2 as computeRepayDelta, computeSumerBorrowDelta, computeSumerDepositDelta, computeSumerRepayDelta, computeSumerWaterfall, computeSumerWithdrawDelta, computeVaultApr, computeWithdrawDelta2 as computeWithdrawDelta, computeZapTradeDeltas, convertExactlyMarketsToResponse, convertLenderUserDataResult, convertLiquityMarketsToResponse, convertRiverMarketsToResponse, convertTellerMarketsToResponse, convertTermMarketsToResponse, createMarketUid, createMidnightBookSource, createMulticallRpcCall, createRawRpcCalls, createTermBookSource, decodeListaMarkets, decodeMarkets, decodePackedListaUserDataset, decodePackedMorphoUserDataset, detectInterfaceKinds, encodeBalanceFetcherCalldata, exactlyLenderKey, exactlyMarketFromLenderKey, exactlyPairLtv, exactlyPenaltyRateToAprPercent, exactlyWadRateToPercent, fetchDolomiteAccountNumbers, fetchEulerEarnVaults, fetchEulerEarnVaultsFromSubgraph, fetchEulerSubAccountIndexes, fetchExactlyMarkets, fetchFlashLiquidityForChain, fetchFluidFTokens, fetchGeneralYields, fetchGeneralYieldsByMarketUid, fetchGmxExecutionFees, fetchGmxTickerPrices, fetchGmxVaults, fetchHypercoreVaults, fetchLagoonApiVaults, fetchLagoonVaults, fetchLiquityMarkets, fetchListaVaultsFromChain, fetchMorphoUserBalances, fetchMorphoUserPositionMarkets, fetchMorphoVaults, fetchMorphoVaultsFromApi, fetchMorphoVaultsFromChain, fetchOraclePrices, fetchPendlePrices, fetchRiverMarkets, fetchSiloVaults, fetchTellerMarkets, fetchTermMarkets, fetchTokenBalances, fetchTokenMetadata, fetchUpshiftApiVaults, fetchUpshiftVaults, fetchYearnApiVaults, fetchYearnVaults, filterActiveLenders, filterLendersByProtocol, fuseLenderData, generateLendingPools, getAavesForChain, getAssetConfig, getBalanceForMarketUid, getBorrowCapacity, getCachedLiquityTroves, getCachedTellerBids, getCoreValidators, getFluidFTokensConverter, getGmxApiHost, getGmxReadContracts, getGmxUserPositions, getHealthFactor, getHypercoreUserPositions, getHypercoreVaultRegistry, getLenderAssets, getLenderPublicData, getLenderPublicDataAll, getLenderPublicDataViaApi, getLenderUserDataMulti, getLenderUserDataResult, getLendersForChain, getLstDelegation, getLstValidators, getLstWithdrawalRegistry, getLstWithdrawalRequests, getMaxAmountClose, getMaxAmountCollateralSwap, getMaxAmountDebtSwap, getMaxAmountOpen, getMergedUserData, getMorphoTypeMarketConverter, getResolvedDolomiteAccountNumbers, getStCeloValidatorGroups, getSubAccountAddress, getSubAccountIndex, getVaultPublicDataAll, getVaultWithdrawalRequests, hasEulerEarnVaultSubgraph, hasLagoonVaults, hasMorphoPositionIndex, hasMorphoUserApi, hasMorphoUserSubgraph, hasUpshiftVaults, hasYearnVaults, isStablecoinSymbol, isYearnV3, keysFromMaps, liquityCandidateTroveIds, liquityKeyParts, liquityLenderKey, multicall3Abi, nanTo, needsLenderApproval, needsTokenApproval, noOpResult, normalizeToBytes, parseBalanceFetcherResult, parseMergedResult, parseMulticallRpcResponses, parseRawRpcBatchResponses, parseRawRpcResponses, parseTokenBalanceResult, positivePart2 as positivePart, prepareLenderUserDataRpcCalls, prepareMergedMulticallParams, prepareMergedRpcCalls, prepareMulticallInputs, prepareTokenBalanceRpcCalls, priceGlvVaults, priceGmMarkets, readVaultSharePrices, resolveStCeloDepositGroup, riverKeyParts, riverLenderKey, selectAssetGroupPrices, stampVaultClassification, tellerBpsToPercent, tellerImpliedLtv, tellerLenderKey, tellerPoolFromLenderKey, termLenderKey, tickToAprNumber, tickToPrice, unflattenLenderData };
53588
+ export { ApiBookSource, DEFAULT_MIDNIGHT_API, DEFAULT_OUTLIER_GUARD, DEFAULT_STALE_REJECT_SECONDS, EMPTY_BALANCE, GMX_API_HOSTS, GMX_READ_CONTRACTS, GMX_SUPPORTED_CHAINS, HYPERCORE_VAULT_REGISTRY, IDLE_MARKET_ID, INTERFACE_IDS, LAGOON_API_URL, LAGOON_CHAIN_IDS, MORPHO_LENS, MaxParamThresholds, STABLECOIN_SYMBOLS, STCELO_MANAGER_ADDRESS, TELLER_CALLS_PER_BID, TermSubgraphSource, UPSHIFT_CHAIN_IDS, UPSHIFT_VAULTS_URL, VAULT_SHARE_PRICE_PROBE, VOLATILE_VAULT_OVERRIDES, YEARN_CHAIN_IDS, YEARN_YDAEMON_BASE, accountDepositListKey, accountWithdrawalListKey, appendSnapshot, applyPositionDelta, attachPricesToFlashLiquidity, buildFluidFTokensCall, buildLiquityUserCall, buildLoopResult, buildMorphoTypeCall, buildMorphoTypeUserCallWithLens, buildPortfolioTotals, buildSumerAccumulators, buildSummaries, buildTellerUserCall, buildVaultLookup, calculateLeverage, calculateNetApr, calculateOverallNetApr, calculateWeightedAverage, classifyFreshness, classifyReliability, classifyVault, collectFeedObservations, computeBorrowDelta2 as computeBorrowDelta, computeCloseTradeDeltas, computeCollateralSwapDeltas, computeDebtSwapDeltas, computeDepositDelta2 as computeDepositDelta, computeEModeAnalysis, computeOpenTradeDeltas, computePostTradeMetrics, computeRepayDelta2 as computeRepayDelta, computeSumerBorrowDelta, computeSumerDepositDelta, computeSumerRepayDelta, computeSumerWaterfall, computeSumerWithdrawDelta, computeVaultApr, computeWithdrawDelta2 as computeWithdrawDelta, computeZapTradeDeltas, consensusReference, convertExactlyMarketsToResponse, convertLenderUserDataResult, convertLiquityMarketsToResponse, convertRiverMarketsToResponse, convertTellerMarketsToResponse, convertTermMarketsToResponse, createMarketUid, createMidnightBookSource, createMulticallRpcCall, createRawRpcCalls, createTermBookSource, decodeListaMarkets, decodeMarkets, decodePackedListaUserDataset, decodePackedMorphoUserDataset, detectInterfaceKinds, encodeBalanceFetcherCalldata, exactlyLenderKey, exactlyMarketFromLenderKey, exactlyPairLtv, exactlyPenaltyRateToAprPercent, exactlyWadRateToPercent, feedKeyOf, feedStatKey, fetchDolomiteAccountNumbers, fetchEulerEarnVaults, fetchEulerEarnVaultsFromSubgraph, fetchEulerSubAccountIndexes, fetchExactlyMarkets, fetchFlashLiquidityForChain, fetchFluidFTokens, fetchGeneralYields, fetchGeneralYieldsByMarketUid, fetchGmxExecutionFees, fetchGmxTickerPrices, fetchGmxVaults, fetchHypercoreVaults, fetchLagoonApiVaults, fetchLagoonVaults, fetchLiquityMarkets, fetchListaVaultsFromChain, fetchMorphoUserBalances, fetchMorphoUserPositionMarkets, fetchMorphoVaults, fetchMorphoVaultsFromApi, fetchMorphoVaultsFromChain, fetchOraclePrices, fetchPendlePrices, fetchRiverMarkets, fetchSiloVaults, fetchTellerMarkets, fetchTermMarkets, fetchTokenBalances, fetchTokenMetadata, fetchUpshiftApiVaults, fetchUpshiftVaults, fetchYearnApiVaults, fetchYearnVaults, filterActiveLenders, filterLendersByProtocol, fuseLenderData, generateLendingPools, getAavesForChain, getAssetConfig, getBalanceForMarketUid, getBorrowCapacity, getCachedLiquityTroves, getCachedTellerBids, getCoreValidators, getFluidFTokensConverter, getGmxApiHost, getGmxReadContracts, getGmxUserPositions, getHealthFactor, getHypercoreUserPositions, getHypercoreVaultRegistry, getLenderAssets, getLenderPublicData, getLenderPublicDataAll, getLenderPublicDataViaApi, getLenderUserDataMulti, getLenderUserDataResult, getLendersForChain, getLstDelegation, getLstValidators, getLstWithdrawalRegistry, getLstWithdrawalRequests, getMaxAmountClose, getMaxAmountCollateralSwap, getMaxAmountDebtSwap, getMaxAmountOpen, getMergedUserData, getMorphoTypeMarketConverter, getResolvedDolomiteAccountNumbers, getStCeloValidatorGroups, getSubAccountAddress, getSubAccountIndex, getVaultPublicDataAll, getVaultWithdrawalRequests, hasEulerEarnVaultSubgraph, hasLagoonVaults, hasMorphoPositionIndex, hasMorphoUserApi, hasMorphoUserSubgraph, hasUpshiftVaults, hasYearnVaults, isStablecoinSymbol, isYearnV3, keysFromMaps, lenderFamily, liquityCandidateTroveIds, liquityKeyParts, liquityLenderKey, multicall3Abi, nanTo, needsLenderApproval, needsTokenApproval, noOpResult, normalizeToBytes, parseBalanceFetcherResult, parseMergedResult, parseMulticallRpcResponses, parseRawRpcBatchResponses, parseRawRpcResponses, parseTokenBalanceResult, positivePart2 as positivePart, prepareLenderUserDataRpcCalls, prepareMergedMulticallParams, prepareMergedRpcCalls, prepareMulticallInputs, prepareTokenBalanceRpcCalls, priceGlvVaults, priceGmMarkets, probeAaveFeedTimestamps, pruneFeedStats, readVaultSharePrices, rejectOutliers as rejectPriceOutliers, resolveDerivation, resolveStCeloDepositGroup, riverKeyParts, riverLenderKey, selectAssetGroupPrices, stampVaultClassification, tellerBpsToPercent, tellerImpliedLtv, tellerLenderKey, tellerPoolFromLenderKey, termLenderKey, tickToAprNumber, tickToPrice, unflattenLenderData, updateFeedStats };
53115
53589
  //# sourceMappingURL=index.js.map
53116
53590
  //# sourceMappingURL=index.js.map