@1delta/margin-fetcher 5.0.8 → 5.0.9

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.d.ts CHANGED
@@ -2299,11 +2299,45 @@ interface LenderRewards {
2299
2299
  };
2300
2300
  };
2301
2301
  }
2302
+ /**
2303
+ * Rewards keyed by the RESERVE TOKEN the campaign pays on — the aToken for a
2304
+ * supply campaign, the variable debt token for a borrow one.
2305
+ *
2306
+ * Why this exists: a protocol's Merkl campaigns cover every deployment under
2307
+ * one `mainProtocolId` ("aave" spans V3 mainnet, Horizon, Prime, Ether.fi and
2308
+ * every V4 hub/spoke), and nothing in the opportunity payload names the
2309
+ * deployment reliably — `depositUrl`'s `marketName` is wrong on at least one
2310
+ * live campaign ("Borrow USDC on Aave" claims `proto_mainnet` while paying on
2311
+ * `variableDebtHorRwaUSDC`, i.e. Horizon). Attributing by (lender, underlying)
2312
+ * therefore collapsed every deployment's campaign onto the base lender key and
2313
+ * SUMMED them: Aave V3 USDC read 5.75% borrow (1.75 + 2 + 2 across three
2314
+ * deployments) and V3 USDG read 12% (two V4 hubs), while Horizon/Prime/V4
2315
+ * markets read 0%.
2316
+ *
2317
+ * The reserve token is unambiguous — it exists in exactly one market of exactly
2318
+ * one deployment. Consumers hold the other half of the join already: Aave-type
2319
+ * market nodes carry `params.metadata.{aToken, vToken}`, so matching on those
2320
+ * lands each campaign on exactly one marketUid, and cross-deployment summing
2321
+ * becomes impossible by construction.
2322
+ */
2323
+ interface RewardsByReserveToken {
2324
+ /** chainId → lowercased reserve-token address → reward */
2325
+ [chainId: string]: {
2326
+ [reserveToken: string]: LenderAssetReward;
2327
+ };
2328
+ }
2302
2329
  interface YieldDataWithTimestamp {
2303
2330
  intrinsicYields: {
2304
2331
  [asset: string]: number | undefined;
2305
2332
  };
2306
2333
  lenderRewards: LenderRewards;
2334
+ /**
2335
+ * Reserve-token-keyed rewards for protocols whose campaigns cannot be
2336
+ * attributed to a deployment from the campaign payload alone (Aave).
2337
+ * Those protocols are absent from `lenderRewards` — better no reward than
2338
+ * one filed against the wrong market.
2339
+ */
2340
+ rewardsByReserveToken: RewardsByReserveToken;
2307
2341
  }
2308
2342
  interface LenderRewardsByMarketUid {
2309
2343
  [marketUid: string]: LenderAssetReward;
@@ -2312,7 +2346,25 @@ interface YieldDataByMarketUid {
2312
2346
  intrinsicYields: {
2313
2347
  [asset: string]: number | undefined;
2314
2348
  };
2349
+ /**
2350
+ * Flattened to marketUid via `createMarketUid(chain, lender, asset)`.
2351
+ *
2352
+ * CAVEAT: that construction assumes a market's uid ends in its underlying
2353
+ * address, which is not universal — Aave V4 uids end in a numeric RESERVE ID
2354
+ * (`AAVE_V4_94E7A5DC…:1:3` is WBTC), so a reward flattened this way can never
2355
+ * match one. Prefer `rewardsByLenderAsset` when the consumer has the market
2356
+ * nodes to hand; this stays for callers that only have uids.
2357
+ */
2315
2358
  lenderRewards: LenderRewardsByMarketUid;
2359
+ /**
2360
+ * The same lender-keyed rewards UNflattened — (chain → lender → underlying).
2361
+ * Consumers holding market nodes should join on this: it needs no assumption
2362
+ * about how a lender composes its marketUid.
2363
+ */
2364
+ rewardsByLenderAsset: LenderRewards;
2365
+ /** See {@link RewardsByReserveToken} — joined by the consumer against each
2366
+ * market node's `params.metadata.{aToken, vToken}`. */
2367
+ rewardsByReserveToken: RewardsByReserveToken;
2316
2368
  }
2317
2369
 
2318
2370
  declare const fetchGeneralYields: () => Promise<YieldDataWithTimestamp>;
package/dist/index.js CHANGED
@@ -23028,7 +23028,11 @@ async function fetchChainExtras(chainId, markets) {
23028
23028
  abi: LLAMALEND_READ_ABI,
23029
23029
  allowFailure: true
23030
23030
  });
23031
- } catch {
23031
+ } catch (e) {
23032
+ console.error(
23033
+ `[llamalend] chain-extras multicall failed on chain ${chainId} for ${markets.length} market(s) \u2014 LTV/price/band state unavailable this cycle:`,
23034
+ e?.message ?? e
23035
+ );
23032
23036
  return {};
23033
23037
  }
23034
23038
  const out = {};
@@ -24019,7 +24023,7 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24019
24023
  const utilization = reserves > 0 ? totalDebt / reserves : 0;
24020
24024
  const borrowRatePct = (m.borrowApr ?? 0) * 100;
24021
24025
  const depositRatePct = (m.lendApr ?? 0) * 100;
24022
- const ltv = m.collateralFactor ?? 0;
24026
+ const ltv = m.collateralFactor ?? null;
24023
24027
  const liqPenalty = Number(market.liquidationDiscount) / 1e18 || 0;
24024
24028
  const closeFactor = 1;
24025
24029
  const depositsEnabled = (m.maxDeposit ?? 0) > 0;
@@ -24128,7 +24132,10 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24128
24132
  collateralDecimals: collDecimals,
24129
24133
  // The Controller doubles as the market id.
24130
24134
  id: market.controller.toLowerCase(),
24131
- lltv: String(ltv),
24135
+ // Mirrors the Morpho shape. Empty string, not "0": a consumer parsing
24136
+ // this as a number gets NaN and can branch, where "0" would silently
24137
+ // become a zero LTV. The real curve is in `llamalend.bandLtv`.
24138
+ lltv: ltv !== null ? String(ltv) : "",
24132
24139
  oracle: market.priceOracle ?? market.amm,
24133
24140
  irm: market.monetaryPolicy ?? zeroAddress,
24134
24141
  collateralAddress: collAddr,
@@ -45942,6 +45949,12 @@ function emptyReward() {
45942
45949
  additionalBorrowData: []
45943
45950
  };
45944
45951
  }
45952
+ function ensureReserveTokenReward(result, chainId, reserveToken) {
45953
+ if (!result[chainId]) result[chainId] = {};
45954
+ const key2 = reserveToken.toLowerCase();
45955
+ if (!result[chainId][key2]) result[chainId][key2] = emptyReward();
45956
+ return result[chainId][key2];
45957
+ }
45945
45958
  function ensureReward(result, chainId, lender, asset) {
45946
45959
  if (!result[chainId]) result[chainId] = {};
45947
45960
  if (!result[chainId][lender]) result[chainId][lender] = {};
@@ -45989,6 +46002,7 @@ function createMerklRewardFetcher(config) {
45989
46002
  label: `MERKL_${config.merklProtocolId.toUpperCase()}`,
45990
46003
  fetch: async () => {
45991
46004
  const result = {};
46005
+ const byReserveToken = {};
45992
46006
  const chainResults = config.chainIds ? await Promise.all(
45993
46007
  config.chainIds.map(async (chainId) => {
45994
46008
  try {
@@ -46012,8 +46026,17 @@ function createMerklRewardFetcher(config) {
46012
46026
  for (const resolved2 of resolutions) {
46013
46027
  const bucket = resolved2.bucket ?? (opp.action === "LEND" ? "deposit" : opp.action === "BORROW" ? "borrow" : void 0);
46014
46028
  if (!bucket) continue;
46015
- const lender = resolved2.lender ?? config.lenderKey;
46016
- const reward = ensureReward(result, chainId, lender, resolved2.asset);
46029
+ const keyBy = resolved2.keyBy ?? (config.keyByReserveToken ? "reserveToken" : "lender");
46030
+ const reward = keyBy === "reserveToken" ? ensureReserveTokenReward(
46031
+ byReserveToken,
46032
+ chainId,
46033
+ resolved2.asset
46034
+ ) : ensureReward(
46035
+ result,
46036
+ chainId,
46037
+ resolved2.lender ?? config.lenderKey,
46038
+ resolved2.asset
46039
+ );
46017
46040
  const breakdowns = extractRewardBreakdowns(opp);
46018
46041
  reward.link = getMerkleUrl(opp);
46019
46042
  if (bucket === "deposit") {
@@ -46026,13 +46049,41 @@ function createMerklRewardFetcher(config) {
46026
46049
  }
46027
46050
  }
46028
46051
  }
46029
- return result;
46052
+ return { byLender: result, byReserveToken };
46030
46053
  }
46031
46054
  };
46032
46055
  }
46033
46056
  var aaveMerklRewardFetcher = createMerklRewardFetcher({
46034
46057
  merklProtocolId: "aave",
46035
- lenderKey: "AAVE_V3"
46058
+ lenderKey: "AAVE_V3",
46059
+ // Aave campaigns are keyed by the reserve token they pay on, NOT by lender:
46060
+ // Merkl's `aave` protocol id covers V3 mainnet, Horizon, Prime, Ether.fi and
46061
+ // every V4 hub/spoke, and nothing in the payload identifies the deployment
46062
+ // reliably — "Borrow USDC on Aave" advertises `marketName=proto_mainnet` in
46063
+ // its depositUrl while paying on `variableDebtHorRwaUSDC`, i.e. Horizon.
46064
+ // Filing them all under AAVE_V3 summed unrelated deployments into one number
46065
+ // (V3 USDC borrow read 5.75% = 1.75 + 2 + 2; V3 USDG read 12% = two V4 hubs)
46066
+ // and left the deployments actually running the campaigns at 0%.
46067
+ keyByReserveToken: true,
46068
+ resolveAsset: (opp) => {
46069
+ const explorer = opp.explorerAddress?.toLowerCase();
46070
+ if (opp.type?.startsWith("AAVE_V4")) {
46071
+ const underlying2 = resolveUnderlyingFromOpportunity(opp);
46072
+ if (!explorer || !underlying2) return void 0;
46073
+ return {
46074
+ asset: underlying2,
46075
+ lender: `AAVE_V4_${explorer.slice(2).toUpperCase()}`,
46076
+ keyBy: "lender"
46077
+ };
46078
+ }
46079
+ if (explorer) return { asset: explorer };
46080
+ const underlying = resolveUnderlyingFromOpportunity(opp);
46081
+ const candidates = (opp.tokens ?? []).filter(
46082
+ (t) => t.address.toLowerCase() !== underlying
46083
+ );
46084
+ if (candidates.length !== 1) return void 0;
46085
+ return { asset: candidates[0].address.toLowerCase() };
46086
+ }
46036
46087
  });
46037
46088
  function parseEulerVaultFromUrl(depositUrl) {
46038
46089
  if (!depositUrl) return void 0;
@@ -46349,6 +46400,8 @@ async function fetchForPool(chainId, lender, pool, nowSeconds) {
46349
46400
  }
46350
46401
  var dtrinityRebateRewardFetcher = {
46351
46402
  label: "DTRINITY_REBATE",
46403
+ // On-chain source: the pool (and therefore the lender key) is known up front,
46404
+ // so this stays lender-keyed — no deployment ambiguity to resolve.
46352
46405
  fetch: async () => {
46353
46406
  const nowSeconds = Math.floor(Date.now() / 1e3);
46354
46407
  const results = await Promise.all(
@@ -46371,7 +46424,7 @@ var dtrinityRebateRewardFetcher = {
46371
46424
  }
46372
46425
  }
46373
46426
  }
46374
- return merged;
46427
+ return { byLender: merged };
46375
46428
  }
46376
46429
  };
46377
46430
 
@@ -46391,30 +46444,51 @@ var rewardFetchers = [
46391
46444
  morphoCollateralMerklRewardFetcher,
46392
46445
  dtrinityRebateRewardFetcher
46393
46446
  ];
46447
+ function mergeReserveTokenRewards(target, source) {
46448
+ for (const [chainId, byToken] of Object.entries(source)) {
46449
+ if (!target[chainId]) target[chainId] = {};
46450
+ Object.assign(target[chainId], byToken);
46451
+ }
46452
+ }
46394
46453
  async function fetchLenderRewards() {
46395
46454
  const results = await Promise.all(
46396
46455
  rewardFetchers.map((f) => safeFetch(f.label, f.fetch))
46397
46456
  );
46398
- const lenderRewards = {};
46457
+ const byLender = {};
46458
+ const byReserveToken = {};
46399
46459
  for (const rr of results) {
46400
- if (rr) mergeRewardResults(lenderRewards, rr);
46460
+ if (!rr) continue;
46461
+ if (rr.byLender) mergeRewardResults(byLender, rr.byLender);
46462
+ if (rr.byReserveToken)
46463
+ mergeReserveTokenRewards(byReserveToken, rr.byReserveToken);
46401
46464
  }
46402
- return lenderRewards;
46465
+ return { byLender, byReserveToken };
46403
46466
  }
46404
46467
 
46405
46468
  // src/yields/fetchGeneralYields.ts
46406
46469
  var fetchGeneralYields = async () => {
46407
- const [intrinsicYields, lenderRewards] = await Promise.all([
46470
+ const [intrinsicYields, rewards] = await Promise.all([
46408
46471
  fetchIntrinsicYields(),
46409
46472
  fetchLenderRewards()
46410
46473
  ]);
46411
- return { intrinsicYields, lenderRewards };
46474
+ return {
46475
+ intrinsicYields,
46476
+ lenderRewards: rewards.byLender,
46477
+ rewardsByReserveToken: rewards.byReserveToken
46478
+ };
46412
46479
  };
46413
46480
  var fetchGeneralYieldsByMarketUid = async () => {
46414
- const { intrinsicYields, lenderRewards } = await fetchGeneralYields();
46481
+ const { intrinsicYields, lenderRewards, rewardsByReserveToken } = await fetchGeneralYields();
46415
46482
  return {
46416
46483
  intrinsicYields,
46417
- lenderRewards: flattenLenderRewards(lenderRewards)
46484
+ lenderRewards: flattenLenderRewards(lenderRewards),
46485
+ // Same rewards, un-flattened: the flatten above assumes a uid ends in the
46486
+ // underlying address, which Aave V4 (reserve-id uids) breaks. Consumers
46487
+ // with market nodes join (lender, underlying) off this instead.
46488
+ rewardsByLenderAsset: lenderRewards,
46489
+ // Left un-flattened for the same reason: turning a reserve token into a
46490
+ // marketUid needs the market's own metadata, which only the consumer has.
46491
+ rewardsByReserveToken
46418
46492
  };
46419
46493
  };
46420
46494
  var flattenLenderRewards = (input) => {