@1delta/margin-fetcher 5.0.8 → 5.0.10

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
@@ -7,7 +7,7 @@ import { fetchTokenLists, fetchTokenList, aavePools, compoundV3Pools, initConfig
7
7
  import lodash from 'lodash';
8
8
  import { Chain } from '@1delta/chain-registry';
9
9
  import { multicallRetryUniversal, getEvmClient, getEvmChain, getEvmClientUniversal } from '@1delta/providers';
10
- import { LiquityTroveManagerAbi, LiquityActivePoolAbi, LiquityStabilityPoolAbi, LiquityPriceFeedAbi, LiquitySortedTrovesAbi, RiverTroveManagerAbi, RiverStabilityPoolAbi, TellerMarketRegistryAbi, TellerV2Abi, InverseMarketAbi, InverseOracleAbi, InverseDbrAbi, Erc20Abi, LlamaLendControllerAbi, LlamaLendControllerV1Abi, LlamaLendControllerV2Abi, LlamaLendVaultAbi, LlamaLendAmmAbi, MetaMorphoAbi, ExactlyPreviewerAbi, ExactlyAuditorAbi, LenderCommitmentGroupAbi, ResupplyRegistryAbi, ResupplyPairAbi, UsddVatAbi, UsddJugAbi, UsddSpotAbi, FrankencoinPositionAbi, FluidLendingResolverAbi, FluidVaultResolverAbi, FluidLiquidityResolverAbi, MoolahVaultAbi, ResupplyUtilitiesAbi, MorphoLensAbi, AaveV4SpokeAbi, AaveV4OracleAbi, AaveV4HubAbi, DolomiteMarginAbi, GearboxMarketCompressorV310Abi, MorphoBlueAbi, MidnightAbi, TermRepoTokenAbi, TermRepoServicerAbi, TermRepoCollateralManagerAbi, LiquityTroveNFTAbi, LiquityCollSurplusPoolAbi, TellerCollateralManagerAbi, TermMaxViewerAbi, InverseEscrowAbi, CurvanceMarketManagerAbi, CurvanceCTokenAbi, GearboxCreditAccountCompressorV310Abi, UsddCdpManagerAbi, UsddProxyRegistryAbi, CurvanceProtocolReaderAbi, CurvanceCentralRegistryAbi, TermPriceConsumerAbi, CurvanceOracleManagerAbi, TermMaxOracleAggregatorV2Abi } from '@1delta/abis';
10
+ import { LiquityTroveManagerAbi, LiquityActivePoolAbi, LiquityStabilityPoolAbi, LiquityPriceFeedAbi, LiquitySortedTrovesAbi, RiverTroveManagerAbi, RiverStabilityPoolAbi, TellerMarketRegistryAbi, TellerV2Abi, InverseMarketAbi, InverseOracleAbi, InverseDbrAbi, Erc20Abi, LlamaLendControllerAbi, LlamaLendControllerV1Abi, LlamaLendControllerV2Abi, LlamaLendVaultAbi, LlamaLendAmmAbi, MetaMorphoAbi, ExactlyPreviewerAbi, ExactlyAuditorAbi, LenderCommitmentGroupAbi, ResupplyRegistryAbi, ResupplyPairAbi, ResupplyUtilitiesAbi, ResupplyRewardHandlerAbi, ResupplyPairEmissionsAbi, ConvexPoolUtilAbi, UsddVatAbi, UsddJugAbi, UsddSpotAbi, FrankencoinPositionAbi, FluidLendingResolverAbi, FluidVaultResolverAbi, FluidLiquidityResolverAbi, MoolahVaultAbi, MorphoLensAbi, AaveV4SpokeAbi, AaveV4OracleAbi, AaveV4HubAbi, DolomiteMarginAbi, GearboxMarketCompressorV310Abi, MorphoBlueAbi, MidnightAbi, TermRepoTokenAbi, TermRepoServicerAbi, TermRepoCollateralManagerAbi, LiquityTroveNFTAbi, LiquityCollSurplusPoolAbi, TellerCollateralManagerAbi, TermMaxViewerAbi, InverseEscrowAbi, CurvanceMarketManagerAbi, CurvanceCTokenAbi, GearboxCreditAccountCompressorV310Abi, UsddCdpManagerAbi, UsddProxyRegistryAbi, CurvanceProtocolReaderAbi, CurvanceCentralRegistryAbi, TermPriceConsumerAbi, CurvanceOracleManagerAbi, TermMaxOracleAggregatorV2Abi } from '@1delta/abis';
11
11
  export { MorphoLensAbi } from '@1delta/abis';
12
12
  import { prepareDebitDataMulticall, prepareLenderDebitMulticall, parseDebitDataResult, parseLenderDebitResult, getPermit2ContractAddress, getCompoundV3CometAddress as getCompoundV3CometAddress$1, getMorphoAddress, getAaveCollateralTokenAddress, getSiloHalfForUnderlying, InitMarginAddresses, getLstAcceptedInputs } from '@1delta/calldata-sdk';
13
13
  import { proxyNativeFetch } from '@1delta/proxy-fetch';
@@ -21401,6 +21401,19 @@ function convertLiquityMarketsToResponse(raw, chainId, prices = {}, _additionalY
21401
21401
  borrowLiquidity = Math.min(borrowLiquidity, Math.max(0, cap - entireDebt));
21402
21402
  }
21403
21403
  if (shutdown) borrowLiquidity = 0;
21404
+ const rateParameter = {
21405
+ kind: "interest-rate",
21406
+ dimension: "rate",
21407
+ domain: {
21408
+ min: Number(cfg.minAnnualInterestRate ?? 0) / 1e18,
21409
+ max: Number(cfg.maxAnnualInterestRate ?? 0) / 1e18
21410
+ },
21411
+ // No protocol default — the borrower must choose. The low end is the
21412
+ // cheapest but the first to be redeemed against.
21413
+ default: Number(cfg.minAnnualInterestRate ?? 0) / 1e18,
21414
+ immutableAfterOpen: false,
21415
+ adjustCooldownSeconds: cfg.interestRateAdjCooldownSeconds ? Number(cfg.interestRateAdjCooldownSeconds) : void 0
21416
+ };
21404
21417
  const entry = { data: {} };
21405
21418
  const collUid = createMarketUid(chainId, lenderKey, collAddr);
21406
21419
  entry.data[collUid] = {
@@ -21435,7 +21448,18 @@ function convertLiquityMarketsToResponse(raw, chainId, prices = {}, _additionalY
21435
21448
  liquidationPenalty: liqPenalty,
21436
21449
  closeFactor: 1,
21437
21450
  collateralDisabled: false,
21438
- debtDisabled: true
21451
+ debtDisabled: true,
21452
+ /**
21453
+ * Liquity's borrower picks their own interest rate at open, and it
21454
+ * sets redemption priority as well as cost — so it is a real
21455
+ * parameter, not a quoted rate.
21456
+ *
21457
+ * The MUTABLE case: adjustable in place, but rate-limited by
21458
+ * `interestRateAdjCooldownSeconds` and fee-charged if adjusted
21459
+ * early. The per-trove value lives in `modes[troveId]`, because one
21460
+ * address can hold several troves at different rates at once.
21461
+ */
21462
+ openParameter: rateParameter
21439
21463
  }
21440
21464
  },
21441
21465
  closeFactor: 1,
@@ -23028,7 +23052,11 @@ async function fetchChainExtras(chainId, markets) {
23028
23052
  abi: LLAMALEND_READ_ABI,
23029
23053
  allowFailure: true
23030
23054
  });
23031
- } catch {
23055
+ } catch (e) {
23056
+ console.error(
23057
+ `[llamalend] chain-extras multicall failed on chain ${chainId} for ${markets.length} market(s) \u2014 LTV/price/band state unavailable this cycle:`,
23058
+ e?.message ?? e
23059
+ );
23032
23060
  return {};
23033
23061
  }
23034
23062
  const out = {};
@@ -23201,7 +23229,7 @@ var VAULT_PRICE_ABI = [
23201
23229
  }
23202
23230
  ];
23203
23231
  var IDENTITY_READS = 3;
23204
- var STATE_READS = 12;
23232
+ var STATE_READS = 14;
23205
23233
  var ONE = 10n ** 18n;
23206
23234
  var identityCache = /* @__PURE__ */ new Map();
23207
23235
  var identityKey = (chainId, pair) => `${chainId}:${pair.toLowerCase()}`;
@@ -23308,6 +23336,11 @@ async function fetchResupplyMarkets(lender, chainId) {
23308
23336
  }
23309
23337
  const identities = discovered.map((p) => identityCache.get(identityKey(chainId, p))).filter((x) => !!x);
23310
23338
  if (identities.length === 0) return { lender, config, pairs: [] };
23339
+ const { rsup, pairEmissions, convexPoolUtil } = await readRewardContext(
23340
+ chainId,
23341
+ config.registry,
23342
+ config.utilities
23343
+ );
23311
23344
  const utilities = config.utilities;
23312
23345
  const calls = [];
23313
23346
  const abis = [];
@@ -23324,7 +23357,16 @@ async function fetchResupplyMarkets(lender, chainId) {
23324
23357
  // The collateral vault's own share price — the exact input Resupply's
23325
23358
  // `BasicVaultOracle` uses. Read LIVE rather than inverting the pair's
23326
23359
  // cached `exchangeRate`, which only moves when someone writes.
23327
- { address: id.collateral, name: "convertToAssets", params: [ONE] }
23360
+ { address: id.collateral, name: "convertToAssets", params: [ONE] },
23361
+ // Rewards. `convexPid == 0` means the collateral is not staked, so there
23362
+ // is nothing to report; the weight is this pair's slice of the RSUP
23363
+ // stream. Both are governance-mutable, so neither is cached.
23364
+ { address: id.pair, name: "convexPid", params: [] },
23365
+ {
23366
+ address: pairEmissions ?? id.pair,
23367
+ name: pairEmissions ? "balanceOf" : "convexPid",
23368
+ params: pairEmissions ? [id.pair] : []
23369
+ }
23328
23370
  );
23329
23371
  abis.push(
23330
23372
  ResupplyPairAbi,
@@ -23335,7 +23377,9 @@ async function fetchResupplyMarkets(lender, chainId) {
23335
23377
  ResupplyPairAbi,
23336
23378
  ResupplyPairAbi,
23337
23379
  ResupplyPairAbi,
23338
- VAULT_PRICE_ABI
23380
+ VAULT_PRICE_ABI,
23381
+ ResupplyPairAbi,
23382
+ pairEmissions ? ResupplyPairEmissionsAbi : ResupplyPairAbi
23339
23383
  );
23340
23384
  if (utilities) {
23341
23385
  calls.push(
@@ -23393,11 +23437,118 @@ async function fetchResupplyMarkets(lender, chainId) {
23393
23437
  ratePerSec: tuple(rateInfo, 1),
23394
23438
  exchangeRate: tuple(exchangeInfo, 2),
23395
23439
  collateralPrice: big(results[base + 8]),
23396
- liveRatePerSec: utilities ? big(results[base + 9]) : null,
23397
- underlyingSupplyRatePerSec: utilities ? big(results[base + 10]) : null
23440
+ // Indices follow the PUSH ORDER above exactly: the two reward reads sit
23441
+ // at +9/+10, BEFORE the lens block, so the lens reads are +11/+12.
23442
+ // (Getting this wrong is silent — it produced a 3-billion-percent APR
23443
+ // before the numbers were checked against a hand computation.)
23444
+ convexPid: big(results[base + 9]),
23445
+ rsupWeight: pairEmissions ? big(results[base + 10]) : null,
23446
+ liveRatePerSec: utilities ? big(results[base + 11]) : null,
23447
+ underlyingSupplyRatePerSec: utilities ? big(results[base + 12]) : null,
23448
+ collateralRewards: []
23398
23449
  };
23399
23450
  });
23400
- return { lender, config, pairs };
23451
+ await attachCollateralRewards(chainId, convexPoolUtil, pairs);
23452
+ return { lender, config, pairs, rsup };
23453
+ }
23454
+ async function readRewardContext(chainId, registry, utilities) {
23455
+ const addr2 = (v) => typeof v === "string" && v.startsWith("0x") && !/^0x0+$/.test(v) ? v : void 0;
23456
+ try {
23457
+ const first = await multicallRetryUniversal({
23458
+ chain: chainId,
23459
+ calls: [
23460
+ { address: registry, name: "rewardHandler", params: [] },
23461
+ { address: registry, name: "govToken", params: [] },
23462
+ ...utilities ? [{ address: utilities, name: "convexPoolUtil", params: [] }] : []
23463
+ ],
23464
+ abi: [
23465
+ ResupplyRegistryAbi,
23466
+ ResupplyRegistryAbi,
23467
+ ...utilities ? [ResupplyUtilitiesAbi] : []
23468
+ ],
23469
+ allowFailure: true
23470
+ });
23471
+ const rewardHandler = addr2(first[0]);
23472
+ const govToken = addr2(first[1]);
23473
+ const convexPoolUtil = utilities ? addr2(first[2]) : void 0;
23474
+ if (!rewardHandler || !govToken) return { convexPoolUtil };
23475
+ const [emissions] = await multicallRetryUniversal({
23476
+ chain: chainId,
23477
+ calls: [{ address: rewardHandler, name: "pairEmissions", params: [] }],
23478
+ abi: ResupplyRewardHandlerAbi,
23479
+ allowFailure: true
23480
+ });
23481
+ const pairEmissions = addr2(emissions);
23482
+ if (!pairEmissions) return { convexPoolUtil };
23483
+ const stream = await multicallRetryUniversal({
23484
+ chain: chainId,
23485
+ calls: [
23486
+ { address: pairEmissions, name: "periodFinish", params: [] },
23487
+ { address: pairEmissions, name: "rewardRate", params: [] },
23488
+ { address: pairEmissions, name: "totalSupply", params: [] }
23489
+ ],
23490
+ abi: [
23491
+ ResupplyPairEmissionsAbi,
23492
+ ResupplyPairEmissionsAbi,
23493
+ ResupplyPairEmissionsAbi
23494
+ ],
23495
+ allowFailure: true
23496
+ });
23497
+ const periodFinish = big(stream[0]);
23498
+ const rewardRate = big(stream[1]);
23499
+ const totalWeight = big(stream[2]);
23500
+ if (periodFinish === null || rewardRate === null || totalWeight === null || totalWeight === 0n) {
23501
+ return { pairEmissions, convexPoolUtil };
23502
+ }
23503
+ if (periodFinish <= BigInt(Math.floor(Date.now() / 1e3))) {
23504
+ return { pairEmissions, convexPoolUtil };
23505
+ }
23506
+ return {
23507
+ rsup: { govToken, rewardRate, totalWeight, periodFinish },
23508
+ pairEmissions,
23509
+ convexPoolUtil
23510
+ };
23511
+ } catch {
23512
+ return {};
23513
+ }
23514
+ }
23515
+ async function attachCollateralRewards(chainId, convexPoolUtil, pairs) {
23516
+ if (!convexPoolUtil) return;
23517
+ const staked = pairs.filter((p) => (p.convexPid ?? 0n) > 0n);
23518
+ if (staked.length === 0) return;
23519
+ let res = [];
23520
+ try {
23521
+ res = await multicallRetryUniversal({
23522
+ chain: chainId,
23523
+ calls: staked.map((p) => ({
23524
+ address: convexPoolUtil,
23525
+ name: "rewardRates",
23526
+ params: [p.convexPid]
23527
+ })),
23528
+ abi: staked.map(() => ConvexPoolUtilAbi),
23529
+ allowFailure: true
23530
+ });
23531
+ } catch {
23532
+ return;
23533
+ }
23534
+ staked.forEach((p, i) => {
23535
+ const entry = res[i];
23536
+ if (!Array.isArray(entry)) return;
23537
+ const [tokens, rates] = entry;
23538
+ if (!Array.isArray(tokens) || !Array.isArray(rates)) return;
23539
+ const byToken = /* @__PURE__ */ new Map();
23540
+ tokens.forEach((t, j) => {
23541
+ const rate = rates[j];
23542
+ if (typeof t !== "string" || typeof rate !== "bigint" || rate === 0n) {
23543
+ return;
23544
+ }
23545
+ const key2 = t.toLowerCase();
23546
+ byToken.set(key2, (byToken.get(key2) ?? 0n) + rate);
23547
+ });
23548
+ p.collateralRewards = [...byToken.entries()].map(
23549
+ ([token, ratePerSecPerShare]) => ({ token, ratePerSecPerShare })
23550
+ );
23551
+ });
23401
23552
  }
23402
23553
 
23403
23554
  // src/prices/oracle-prices/fetchers/curvanceRoster.ts
@@ -23987,6 +24138,8 @@ function currencyFor9(address, decimals, symbol, tokens) {
23987
24138
  const lower3 = address.toLowerCase();
23988
24139
  return tokens[lower3] ?? { address: lower3, symbol, name: symbol, decimals };
23989
24140
  }
24141
+ var LLAMALEND_MIN_BANDS = 4;
24142
+ var LLAMALEND_MAX_BANDS = 50;
23990
24143
  function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additionalYields = {
23991
24144
  intrinsicYields: {},
23992
24145
  lenderRewards: {},
@@ -24019,10 +24172,17 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24019
24172
  const utilization = reserves > 0 ? totalDebt / reserves : 0;
24020
24173
  const borrowRatePct = (m.borrowApr ?? 0) * 100;
24021
24174
  const depositRatePct = (m.lendApr ?? 0) * 100;
24022
- const ltv = m.collateralFactor ?? 0;
24175
+ const ltv = m.collateralFactor ?? null;
24023
24176
  const liqPenalty = Number(market.liquidationDiscount) / 1e18 || 0;
24024
24177
  const closeFactor = 1;
24025
24178
  const depositsEnabled = (m.maxDeposit ?? 0) > 0;
24179
+ const bandParameter = {
24180
+ kind: "llamalend-bands",
24181
+ dimension: "collateralFactor",
24182
+ domain: { min: LLAMALEND_MIN_BANDS, max: LLAMALEND_MAX_BANDS },
24183
+ default: market.defaultBands ?? 10,
24184
+ immutableAfterOpen: true
24185
+ };
24026
24186
  const entry = { data: {} };
24027
24187
  const collUid = createMarketUid(chainId, lenderKey, collAddr);
24028
24188
  entry.data[collUid] = {
@@ -24060,7 +24220,8 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24060
24220
  liquidationPenalty: liqPenalty,
24061
24221
  closeFactor,
24062
24222
  collateralDisabled: false,
24063
- debtDisabled: true
24223
+ debtDisabled: true,
24224
+ openParameter: bandParameter
24064
24225
  }
24065
24226
  },
24066
24227
  closeFactor,
@@ -24128,7 +24289,10 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24128
24289
  collateralDecimals: collDecimals,
24129
24290
  // The Controller doubles as the market id.
24130
24291
  id: market.controller.toLowerCase(),
24131
- lltv: String(ltv),
24292
+ // Mirrors the Morpho shape. Empty string, not "0": a consumer parsing
24293
+ // this as a number gets NaN and can branch, where "0" would silently
24294
+ // become a zero LTV. The real curve is in `llamalend.bandLtv`.
24295
+ lltv: ltv !== null ? String(ltv) : "",
24132
24296
  oracle: market.priceOracle ?? market.amm,
24133
24297
  irm: market.monetaryPolicy ?? zeroAddress,
24134
24298
  collateralAddress: collAddr,
@@ -24256,6 +24420,52 @@ function ratePerSecToApr(rate) {
24256
24420
  if (rate === null) return 0;
24257
24421
  return Number(rate) / 1e18 * SECONDS_PER_YEAR9 * 100;
24258
24422
  }
24423
+ function buildRewardEntries(p, rsup, chainId, prices, tokens, collateralUsd, debtUsd) {
24424
+ const priceOf = (address) => {
24425
+ const lower3 = address.toLowerCase();
24426
+ const token = tokens[lower3];
24427
+ const key2 = toOracleKey(token?.assetGroup) || toGenericPriceKey(lower3, chainId);
24428
+ return prices[key2] ?? 0;
24429
+ };
24430
+ const collateral = [];
24431
+ if (collateralUsd > 0) {
24432
+ for (const r of p.collateralRewards ?? []) {
24433
+ const price2 = priceOf(r.token);
24434
+ if (price2 <= 0) continue;
24435
+ const decimals = tokens[r.token.toLowerCase()]?.decimals ?? 18;
24436
+ const perSecond = Number(r.ratePerSecPerShare) * Number(p.totalCollateral ?? 0n) / 1e18;
24437
+ const usdPerYear = perSecond / 10 ** decimals * SECONDS_PER_YEAR9 * price2;
24438
+ const apr = usdPerYear / collateralUsd * 100;
24439
+ if (!Number.isFinite(apr) || apr <= 0) continue;
24440
+ collateral.push({
24441
+ asset: r.token,
24442
+ depositRate: apr,
24443
+ variableBorrowRate: 0,
24444
+ stableBorrowRate: 0
24445
+ });
24446
+ }
24447
+ }
24448
+ const borrow = [];
24449
+ if (rsup && debtUsd > 0 && (p.rsupWeight ?? 0n) > 0n) {
24450
+ const price2 = priceOf(rsup.govToken);
24451
+ if (price2 > 0) {
24452
+ const decimals = tokens[rsup.govToken.toLowerCase()]?.decimals ?? 18;
24453
+ const perSecond = Number(rsup.rewardRate) * Number(p.rsupWeight) / Number(rsup.totalWeight);
24454
+ const usdPerYear = perSecond / 10 ** decimals * SECONDS_PER_YEAR9 * price2;
24455
+ const apr = usdPerYear / debtUsd * 100;
24456
+ if (Number.isFinite(apr) && apr > 0) {
24457
+ borrow.push({
24458
+ asset: rsup.govToken,
24459
+ depositRate: 0,
24460
+ // A REBATE on the borrow cost, not a charge.
24461
+ variableBorrowRate: apr,
24462
+ stableBorrowRate: 0
24463
+ });
24464
+ }
24465
+ }
24466
+ }
24467
+ return { collateral, borrow };
24468
+ }
24259
24469
  function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additionalYields = {
24260
24470
  intrinsicYields: {},
24261
24471
  lenderRewards: {},
@@ -24291,6 +24501,15 @@ function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additional
24291
24501
  const borrowLiquidity = Math.max(borrowLimit - totalDebt, 0);
24292
24502
  const halted = (p.borrowLimit ?? 0n) === 0n;
24293
24503
  const wrappedMarket = resolveWrappedMarket(chainId, id.collateral, id.name);
24504
+ const rewardEntries = buildRewardEntries(
24505
+ p,
24506
+ raw.rsup,
24507
+ chainId,
24508
+ prices,
24509
+ tokens,
24510
+ totalColl * collPrice,
24511
+ totalDebt * debtPrice
24512
+ );
24294
24513
  const maxLtv = p.maxLTV !== null ? Number(p.maxLTV) / LTV_PRECISION : 0;
24295
24514
  const liquidationPenalty = p.liquidationFee !== null ? Number(p.liquidationFee) / LTV_PRECISION : 0;
24296
24515
  const mintFeePct = p.mintFee !== null ? Number(p.mintFee) / LTV_PRECISION * 100 : 0;
@@ -24321,7 +24540,9 @@ function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additional
24321
24540
  // The wrapped Curve Lend / Fraxlend supply rate — this collateral is a
24322
24541
  // lending position and earns while it sits here.
24323
24542
  intrinsicYield: collateralIntrinsicApr,
24324
- rewards: void 0,
24543
+ // CRV/CVX from the Convex position the pair stakes the collateral into.
24544
+ // Empty (not undefined) when the pair does not stake — `convexPid == 0`.
24545
+ rewards: rewardEntries.collateral,
24325
24546
  decimals: collDecimals,
24326
24547
  config: {
24327
24548
  0: {
@@ -24378,7 +24599,9 @@ function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additional
24378
24599
  // but it is governance-settable.
24379
24600
  originationFee: mintFeePct,
24380
24601
  intrinsicYield: 0,
24381
- rewards: void 0,
24602
+ // RSUP emissions to BORROWERS — a rebate on `variableBorrowRate`, not a
24603
+ // deposit yield. Empty when the emission stream has expired.
24604
+ rewards: rewardEntries.borrow,
24382
24605
  decimals: debtDecimals,
24383
24606
  config: {
24384
24607
  0: {
@@ -31134,7 +31357,7 @@ var getLiquityUserDataConverter = (lender, chainId, account, meta) => {
31134
31357
  troveInfo
31135
31358
  }
31136
31359
  };
31137
- modes[t.troveId] = 0;
31360
+ modes[t.troveId] = Number(troveInfo.annualInterestRate) / 1e18;
31138
31361
  hist[t.troveId] = {
31139
31362
  totalDeposits24h: collNum * collHist,
31140
31363
  totalDebt24h: debtNum * loanHist
@@ -31594,7 +31817,7 @@ var getLlamaLendUserDataConverter = (lender, chainId, account, meta) => {
31594
31817
  }
31595
31818
  }
31596
31819
  };
31597
- const modes = { "0": 0 };
31820
+ const modes = { "0": bandCount };
31598
31821
  const hist = {
31599
31822
  "0": {
31600
31823
  totalDeposits24h: collNum * collHist + bandBorrowedNum * loanHist,
@@ -45942,6 +46165,12 @@ function emptyReward() {
45942
46165
  additionalBorrowData: []
45943
46166
  };
45944
46167
  }
46168
+ function ensureReserveTokenReward(result, chainId, reserveToken) {
46169
+ if (!result[chainId]) result[chainId] = {};
46170
+ const key2 = reserveToken.toLowerCase();
46171
+ if (!result[chainId][key2]) result[chainId][key2] = emptyReward();
46172
+ return result[chainId][key2];
46173
+ }
45945
46174
  function ensureReward(result, chainId, lender, asset) {
45946
46175
  if (!result[chainId]) result[chainId] = {};
45947
46176
  if (!result[chainId][lender]) result[chainId][lender] = {};
@@ -45989,6 +46218,7 @@ function createMerklRewardFetcher(config) {
45989
46218
  label: `MERKL_${config.merklProtocolId.toUpperCase()}`,
45990
46219
  fetch: async () => {
45991
46220
  const result = {};
46221
+ const byReserveToken = {};
45992
46222
  const chainResults = config.chainIds ? await Promise.all(
45993
46223
  config.chainIds.map(async (chainId) => {
45994
46224
  try {
@@ -46012,8 +46242,17 @@ function createMerklRewardFetcher(config) {
46012
46242
  for (const resolved2 of resolutions) {
46013
46243
  const bucket = resolved2.bucket ?? (opp.action === "LEND" ? "deposit" : opp.action === "BORROW" ? "borrow" : void 0);
46014
46244
  if (!bucket) continue;
46015
- const lender = resolved2.lender ?? config.lenderKey;
46016
- const reward = ensureReward(result, chainId, lender, resolved2.asset);
46245
+ const keyBy = resolved2.keyBy ?? (config.keyByReserveToken ? "reserveToken" : "lender");
46246
+ const reward = keyBy === "reserveToken" ? ensureReserveTokenReward(
46247
+ byReserveToken,
46248
+ chainId,
46249
+ resolved2.asset
46250
+ ) : ensureReward(
46251
+ result,
46252
+ chainId,
46253
+ resolved2.lender ?? config.lenderKey,
46254
+ resolved2.asset
46255
+ );
46017
46256
  const breakdowns = extractRewardBreakdowns(opp);
46018
46257
  reward.link = getMerkleUrl(opp);
46019
46258
  if (bucket === "deposit") {
@@ -46026,13 +46265,41 @@ function createMerklRewardFetcher(config) {
46026
46265
  }
46027
46266
  }
46028
46267
  }
46029
- return result;
46268
+ return { byLender: result, byReserveToken };
46030
46269
  }
46031
46270
  };
46032
46271
  }
46033
46272
  var aaveMerklRewardFetcher = createMerklRewardFetcher({
46034
46273
  merklProtocolId: "aave",
46035
- lenderKey: "AAVE_V3"
46274
+ lenderKey: "AAVE_V3",
46275
+ // Aave campaigns are keyed by the reserve token they pay on, NOT by lender:
46276
+ // Merkl's `aave` protocol id covers V3 mainnet, Horizon, Prime, Ether.fi and
46277
+ // every V4 hub/spoke, and nothing in the payload identifies the deployment
46278
+ // reliably — "Borrow USDC on Aave" advertises `marketName=proto_mainnet` in
46279
+ // its depositUrl while paying on `variableDebtHorRwaUSDC`, i.e. Horizon.
46280
+ // Filing them all under AAVE_V3 summed unrelated deployments into one number
46281
+ // (V3 USDC borrow read 5.75% = 1.75 + 2 + 2; V3 USDG read 12% = two V4 hubs)
46282
+ // and left the deployments actually running the campaigns at 0%.
46283
+ keyByReserveToken: true,
46284
+ resolveAsset: (opp) => {
46285
+ const explorer = opp.explorerAddress?.toLowerCase();
46286
+ if (opp.type?.startsWith("AAVE_V4")) {
46287
+ const underlying2 = resolveUnderlyingFromOpportunity(opp);
46288
+ if (!explorer || !underlying2) return void 0;
46289
+ return {
46290
+ asset: underlying2,
46291
+ lender: `AAVE_V4_${explorer.slice(2).toUpperCase()}`,
46292
+ keyBy: "lender"
46293
+ };
46294
+ }
46295
+ if (explorer) return { asset: explorer };
46296
+ const underlying = resolveUnderlyingFromOpportunity(opp);
46297
+ const candidates = (opp.tokens ?? []).filter(
46298
+ (t) => t.address.toLowerCase() !== underlying
46299
+ );
46300
+ if (candidates.length !== 1) return void 0;
46301
+ return { asset: candidates[0].address.toLowerCase() };
46302
+ }
46036
46303
  });
46037
46304
  function parseEulerVaultFromUrl(depositUrl) {
46038
46305
  if (!depositUrl) return void 0;
@@ -46349,6 +46616,8 @@ async function fetchForPool(chainId, lender, pool, nowSeconds) {
46349
46616
  }
46350
46617
  var dtrinityRebateRewardFetcher = {
46351
46618
  label: "DTRINITY_REBATE",
46619
+ // On-chain source: the pool (and therefore the lender key) is known up front,
46620
+ // so this stays lender-keyed — no deployment ambiguity to resolve.
46352
46621
  fetch: async () => {
46353
46622
  const nowSeconds = Math.floor(Date.now() / 1e3);
46354
46623
  const results = await Promise.all(
@@ -46371,7 +46640,7 @@ var dtrinityRebateRewardFetcher = {
46371
46640
  }
46372
46641
  }
46373
46642
  }
46374
- return merged;
46643
+ return { byLender: merged };
46375
46644
  }
46376
46645
  };
46377
46646
 
@@ -46391,30 +46660,51 @@ var rewardFetchers = [
46391
46660
  morphoCollateralMerklRewardFetcher,
46392
46661
  dtrinityRebateRewardFetcher
46393
46662
  ];
46663
+ function mergeReserveTokenRewards(target, source) {
46664
+ for (const [chainId, byToken] of Object.entries(source)) {
46665
+ if (!target[chainId]) target[chainId] = {};
46666
+ Object.assign(target[chainId], byToken);
46667
+ }
46668
+ }
46394
46669
  async function fetchLenderRewards() {
46395
46670
  const results = await Promise.all(
46396
46671
  rewardFetchers.map((f) => safeFetch(f.label, f.fetch))
46397
46672
  );
46398
- const lenderRewards = {};
46673
+ const byLender = {};
46674
+ const byReserveToken = {};
46399
46675
  for (const rr of results) {
46400
- if (rr) mergeRewardResults(lenderRewards, rr);
46676
+ if (!rr) continue;
46677
+ if (rr.byLender) mergeRewardResults(byLender, rr.byLender);
46678
+ if (rr.byReserveToken)
46679
+ mergeReserveTokenRewards(byReserveToken, rr.byReserveToken);
46401
46680
  }
46402
- return lenderRewards;
46681
+ return { byLender, byReserveToken };
46403
46682
  }
46404
46683
 
46405
46684
  // src/yields/fetchGeneralYields.ts
46406
46685
  var fetchGeneralYields = async () => {
46407
- const [intrinsicYields, lenderRewards] = await Promise.all([
46686
+ const [intrinsicYields, rewards] = await Promise.all([
46408
46687
  fetchIntrinsicYields(),
46409
46688
  fetchLenderRewards()
46410
46689
  ]);
46411
- return { intrinsicYields, lenderRewards };
46690
+ return {
46691
+ intrinsicYields,
46692
+ lenderRewards: rewards.byLender,
46693
+ rewardsByReserveToken: rewards.byReserveToken
46694
+ };
46412
46695
  };
46413
46696
  var fetchGeneralYieldsByMarketUid = async () => {
46414
- const { intrinsicYields, lenderRewards } = await fetchGeneralYields();
46697
+ const { intrinsicYields, lenderRewards, rewardsByReserveToken } = await fetchGeneralYields();
46415
46698
  return {
46416
46699
  intrinsicYields,
46417
- lenderRewards: flattenLenderRewards(lenderRewards)
46700
+ lenderRewards: flattenLenderRewards(lenderRewards),
46701
+ // Same rewards, un-flattened: the flatten above assumes a uid ends in the
46702
+ // underlying address, which Aave V4 (reserve-id uids) breaks. Consumers
46703
+ // with market nodes join (lender, underlying) off this instead.
46704
+ rewardsByLenderAsset: lenderRewards,
46705
+ // Left un-flattened for the same reason: turning a reserve token into a
46706
+ // marketUid needs the market's own metadata, which only the consumer has.
46707
+ rewardsByReserveToken
46418
46708
  };
46419
46709
  };
46420
46710
  var flattenLenderRewards = (input) => {