@1delta/margin-fetcher 5.0.9 → 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.d.ts CHANGED
@@ -33,6 +33,50 @@ interface RewardEntry$1 extends BaseYields$1 {
33
33
  asset: string;
34
34
  }
35
35
  type RewardsList$1 = RewardEntry$1[];
36
+ /**
37
+ * The knob the BORROWER turns at open, when this config's numbers depend on one.
38
+ *
39
+ * Absent on every protocol whose factors are constants (Aave, Morpho, Compound…).
40
+ * Present where a per-loan choice moves them — LlamaLend's band count `N` moves
41
+ * the collateral factor; Liquity's chosen interest rate moves the cost.
42
+ *
43
+ * This describes the DOMAIN only. The value a given position actually chose is
44
+ * per-position and lives in the user-data `modes[posId]` slot — it cannot live
45
+ * here, because a lender with sub-accounts (Liquity troves, TermMax GTs) has
46
+ * several live values in one market at once.
47
+ *
48
+ * `kind` is what tells a consumer how to READ that number: without it a UI
49
+ * renders a band count of 15 as "e-mode 15", since `modes` historically only
50
+ * ever carried e-mode categories.
51
+ *
52
+ * See POSITION_PARAMETERS_PLAN.md.
53
+ */
54
+ interface OpenParameter {
55
+ /** Discriminator — how to interpret the matching `modes[posId]` value. */
56
+ kind: 'llamalend-bands' | 'interest-rate';
57
+ /** Which of this config's numbers moves with the parameter. */
58
+ dimension: 'collateralFactor' | 'rate';
59
+ /** Allowed values: a continuous range, or a discrete set. */
60
+ domain: {
61
+ min: number;
62
+ max: number;
63
+ } | {
64
+ values: number[];
65
+ };
66
+ /**
67
+ * The value THIS config's numbers were computed at. A consumer that quotes a
68
+ * different value must recompute — it must not reuse `collateralFactor`.
69
+ */
70
+ default: number;
71
+ /**
72
+ * `true` ⇒ fixed for the life of the loan; changing it means close & reopen
73
+ * (LlamaLend — `_add_collateral_borrow` reuses the tick width).
74
+ * `false` ⇒ adjustable in place (Liquity), subject to the friction below.
75
+ */
76
+ immutableAfterOpen: boolean;
77
+ /** Adjustment cooldown, when mutable (Liquity `interestRateAdjCooldownSeconds`). */
78
+ adjustCooldownSeconds?: number;
79
+ }
36
80
  interface ConfigEntry {
37
81
  category: number;
38
82
  borrowCollateralFactor: number;
@@ -63,6 +107,8 @@ interface ConfigEntry {
63
107
  targetHealthFactor?: number;
64
108
  collateralDisabled?: boolean;
65
109
  debtDisabled?: boolean;
110
+ /** Borrower-chosen open-time parameter this config's numbers depend on. */
111
+ openParameter?: OpenParameter;
66
112
  }
67
113
  interface PoolConfig {
68
114
  [category: string]: ConfigEntry;
@@ -630,6 +676,12 @@ interface LenderConfigData {
630
676
  targetHealthFactor?: number;
631
677
  collateralDisabled?: boolean;
632
678
  debtDisabled?: boolean;
679
+ /**
680
+ * Borrower-chosen open-time parameter this config's numbers depend on.
681
+ * Mirrors the API-side `ConfigEntry.openParameter`; see
682
+ * POSITION_PARAMETERS_PLAN.md.
683
+ */
684
+ openParameter?: OpenParameter;
633
685
  }
634
686
  interface ModeBase {
635
687
  category: number;
@@ -3304,11 +3356,41 @@ interface ResupplyPairRaw {
3304
3356
  collateralPrice: bigint | null;
3305
3357
  /** Cached `1e36 / collateralPrice` from the pair (stale between writes). */
3306
3358
  exchangeRate: bigint | null;
3359
+ /** Convex pool id the collateral is staked into. 0 = not staked, no rewards. */
3360
+ convexPid: bigint | null;
3361
+ /** This pair's WEIGHT in the RSUP emission stream (not a token balance). */
3362
+ rsupWeight: bigint | null;
3363
+ /** Convex reward streams on the staked collateral: reward wei per second per
3364
+ * 1e18 of staked SHARES, aggregated by token (a pool can list the same
3365
+ * token twice). */
3366
+ collateralRewards: {
3367
+ token: string;
3368
+ ratePerSecPerShare: bigint;
3369
+ }[];
3370
+ }
3371
+ /**
3372
+ * Chain-level RSUP emission state — one read for the whole roster.
3373
+ *
3374
+ * `pairEmissions` stakes governance WEIGHT, not tokens: `totalWeight` is the
3375
+ * sum over all pairs and each pair's slice is its `rsupWeight`. A pair's RSUP
3376
+ * per second is `rewardRate x rsupWeight / totalWeight`.
3377
+ */
3378
+ interface ResupplyRsupEmissions {
3379
+ /** The RSUP token. */
3380
+ govToken: string;
3381
+ /** RSUP wei per second across ALL pairs. */
3382
+ rewardRate: bigint;
3383
+ /** Sum of every pair's weight. */
3384
+ totalWeight: bigint;
3385
+ /** Emissions stop here; past it the stream pays nothing. */
3386
+ periodFinish: bigint;
3307
3387
  }
3308
3388
  interface ResupplyMarketsRaw {
3309
3389
  lender: string;
3310
3390
  config?: ResupplyConfigChain;
3311
3391
  pairs: ResupplyPairRaw[];
3392
+ /** Absent when the stream has ended or could not be read. */
3393
+ rsup?: ResupplyRsupEmissions;
3312
3394
  }
3313
3395
 
3314
3396
  /**
@@ -3556,28 +3638,6 @@ declare function llamaLendKeyParts(key: string): {
3556
3638
  lender: string;
3557
3639
  controller: string;
3558
3640
  } | undefined;
3559
- /**
3560
- * Map the LlamaLend batch into the shared `MorphoGeneralPublicResponse` shape,
3561
- * keyed by `LLAMALEND_<CONTROLLER_ADDR>` — one key per market.
3562
- *
3563
- * Per market, two entries in the isolated-pair layout:
3564
- *
3565
- * - COLLATERAL entry — deposit-only. `collateralFactor` is the LTV AT THE
3566
- * MARKET'S DEFAULT BAND COUNT, because LlamaLend has no market-constant
3567
- * LTV: it is a function of `N` and moves 0.886..0.991 on a single market.
3568
- * The whole curve rides along in `params.market.llamalend.bandLtv` so the
3569
- * UI can show the trade-off and the leverage sizer can use the real number
3570
- * for the `N` the user actually picks.
3571
- * - LOAN entry — the borrowed token. Supply side is the ERC-4626 vault, so
3572
- * unlike Inverse this one HAS `totalDeposits`.
3573
- *
3574
- * SOFT LIQUIDATION is the thing this shape cannot express natively, so it is
3575
- * carried explicitly in the descriptor. `liquidationPenalty` here is the HARD
3576
- * liquidation bonus only — it applies below the entire band range. Inside the
3577
- * range a position is converted gradually through the market's own AMM with no
3578
- * penalty at all, and a consumer that renders `liquidationPenalty` as "what
3579
- * you lose when the price hits X" is describing the wrong event.
3580
- */
3581
3641
  declare function convertLlamaLendMarketsToResponse(raw: LlamaLendMarketsRaw, chainId: string, prices?: {
3582
3642
  [asset: string]: number;
3583
3643
  }, additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
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,
@@ -23205,7 +23229,7 @@ var VAULT_PRICE_ABI = [
23205
23229
  }
23206
23230
  ];
23207
23231
  var IDENTITY_READS = 3;
23208
- var STATE_READS = 12;
23232
+ var STATE_READS = 14;
23209
23233
  var ONE = 10n ** 18n;
23210
23234
  var identityCache = /* @__PURE__ */ new Map();
23211
23235
  var identityKey = (chainId, pair) => `${chainId}:${pair.toLowerCase()}`;
@@ -23312,6 +23336,11 @@ async function fetchResupplyMarkets(lender, chainId) {
23312
23336
  }
23313
23337
  const identities = discovered.map((p) => identityCache.get(identityKey(chainId, p))).filter((x) => !!x);
23314
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
+ );
23315
23344
  const utilities = config.utilities;
23316
23345
  const calls = [];
23317
23346
  const abis = [];
@@ -23328,7 +23357,16 @@ async function fetchResupplyMarkets(lender, chainId) {
23328
23357
  // The collateral vault's own share price — the exact input Resupply's
23329
23358
  // `BasicVaultOracle` uses. Read LIVE rather than inverting the pair's
23330
23359
  // cached `exchangeRate`, which only moves when someone writes.
23331
- { 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
+ }
23332
23370
  );
23333
23371
  abis.push(
23334
23372
  ResupplyPairAbi,
@@ -23339,7 +23377,9 @@ async function fetchResupplyMarkets(lender, chainId) {
23339
23377
  ResupplyPairAbi,
23340
23378
  ResupplyPairAbi,
23341
23379
  ResupplyPairAbi,
23342
- VAULT_PRICE_ABI
23380
+ VAULT_PRICE_ABI,
23381
+ ResupplyPairAbi,
23382
+ pairEmissions ? ResupplyPairEmissionsAbi : ResupplyPairAbi
23343
23383
  );
23344
23384
  if (utilities) {
23345
23385
  calls.push(
@@ -23397,11 +23437,118 @@ async function fetchResupplyMarkets(lender, chainId) {
23397
23437
  ratePerSec: tuple(rateInfo, 1),
23398
23438
  exchangeRate: tuple(exchangeInfo, 2),
23399
23439
  collateralPrice: big(results[base + 8]),
23400
- liveRatePerSec: utilities ? big(results[base + 9]) : null,
23401
- 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: []
23402
23449
  };
23403
23450
  });
23404
- 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
+ });
23405
23552
  }
23406
23553
 
23407
23554
  // src/prices/oracle-prices/fetchers/curvanceRoster.ts
@@ -23991,6 +24138,8 @@ function currencyFor9(address, decimals, symbol, tokens) {
23991
24138
  const lower3 = address.toLowerCase();
23992
24139
  return tokens[lower3] ?? { address: lower3, symbol, name: symbol, decimals };
23993
24140
  }
24141
+ var LLAMALEND_MIN_BANDS = 4;
24142
+ var LLAMALEND_MAX_BANDS = 50;
23994
24143
  function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additionalYields = {
23995
24144
  intrinsicYields: {},
23996
24145
  lenderRewards: {},
@@ -24027,6 +24176,13 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24027
24176
  const liqPenalty = Number(market.liquidationDiscount) / 1e18 || 0;
24028
24177
  const closeFactor = 1;
24029
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
+ };
24030
24186
  const entry = { data: {} };
24031
24187
  const collUid = createMarketUid(chainId, lenderKey, collAddr);
24032
24188
  entry.data[collUid] = {
@@ -24064,7 +24220,8 @@ function convertLlamaLendMarketsToResponse(raw, chainId, prices = {}, additional
24064
24220
  liquidationPenalty: liqPenalty,
24065
24221
  closeFactor,
24066
24222
  collateralDisabled: false,
24067
- debtDisabled: true
24223
+ debtDisabled: true,
24224
+ openParameter: bandParameter
24068
24225
  }
24069
24226
  },
24070
24227
  closeFactor,
@@ -24263,6 +24420,52 @@ function ratePerSecToApr(rate) {
24263
24420
  if (rate === null) return 0;
24264
24421
  return Number(rate) / 1e18 * SECONDS_PER_YEAR9 * 100;
24265
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
+ }
24266
24469
  function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additionalYields = {
24267
24470
  intrinsicYields: {},
24268
24471
  lenderRewards: {},
@@ -24298,6 +24501,15 @@ function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additional
24298
24501
  const borrowLiquidity = Math.max(borrowLimit - totalDebt, 0);
24299
24502
  const halted = (p.borrowLimit ?? 0n) === 0n;
24300
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
+ );
24301
24513
  const maxLtv = p.maxLTV !== null ? Number(p.maxLTV) / LTV_PRECISION : 0;
24302
24514
  const liquidationPenalty = p.liquidationFee !== null ? Number(p.liquidationFee) / LTV_PRECISION : 0;
24303
24515
  const mintFeePct = p.mintFee !== null ? Number(p.mintFee) / LTV_PRECISION * 100 : 0;
@@ -24328,7 +24540,9 @@ function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additional
24328
24540
  // The wrapped Curve Lend / Fraxlend supply rate — this collateral is a
24329
24541
  // lending position and earns while it sits here.
24330
24542
  intrinsicYield: collateralIntrinsicApr,
24331
- 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,
24332
24546
  decimals: collDecimals,
24333
24547
  config: {
24334
24548
  0: {
@@ -24385,7 +24599,9 @@ function convertResupplyMarketsToResponse(raw, chainId, prices = {}, _additional
24385
24599
  // but it is governance-settable.
24386
24600
  originationFee: mintFeePct,
24387
24601
  intrinsicYield: 0,
24388
- 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,
24389
24605
  decimals: debtDecimals,
24390
24606
  config: {
24391
24607
  0: {
@@ -31141,7 +31357,7 @@ var getLiquityUserDataConverter = (lender, chainId, account, meta) => {
31141
31357
  troveInfo
31142
31358
  }
31143
31359
  };
31144
- modes[t.troveId] = 0;
31360
+ modes[t.troveId] = Number(troveInfo.annualInterestRate) / 1e18;
31145
31361
  hist[t.troveId] = {
31146
31362
  totalDeposits24h: collNum * collHist,
31147
31363
  totalDebt24h: debtNum * loanHist
@@ -31601,7 +31817,7 @@ var getLlamaLendUserDataConverter = (lender, chainId, account, meta) => {
31601
31817
  }
31602
31818
  }
31603
31819
  };
31604
- const modes = { "0": 0 };
31820
+ const modes = { "0": bandCount };
31605
31821
  const hist = {
31606
31822
  "0": {
31607
31823
  totalDeposits24h: collNum * collHist + bandBorrowedNum * loanHist,