curvance 4.1.2 → 5.1.0

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 (115) hide show
  1. package/README.md +65 -10
  2. package/dist/abis/OptimizerReader.json +43 -45
  3. package/dist/abis/ProtocolReader.json +279 -55
  4. package/dist/chains/arbitrum.js +3 -3
  5. package/dist/chains/arbitrum.js.map +1 -1
  6. package/dist/classes/Api.d.ts +2 -2
  7. package/dist/classes/Api.d.ts.map +1 -1
  8. package/dist/classes/Api.js +87 -14
  9. package/dist/classes/Api.js.map +1 -1
  10. package/dist/classes/BorrowableCToken.d.ts +9 -1
  11. package/dist/classes/BorrowableCToken.d.ts.map +1 -1
  12. package/dist/classes/BorrowableCToken.js +67 -12
  13. package/dist/classes/BorrowableCToken.js.map +1 -1
  14. package/dist/classes/CToken.d.ts +210 -21
  15. package/dist/classes/CToken.d.ts.map +1 -1
  16. package/dist/classes/CToken.js +692 -284
  17. package/dist/classes/CToken.js.map +1 -1
  18. package/dist/classes/Calldata.d.ts +2 -2
  19. package/dist/classes/Calldata.d.ts.map +1 -1
  20. package/dist/classes/Calldata.js +6 -2
  21. package/dist/classes/Calldata.js.map +1 -1
  22. package/dist/classes/DexAggregators/IDexAgg.d.ts +1 -1
  23. package/dist/classes/DexAggregators/Kuru.js +1 -1
  24. package/dist/classes/DexAggregators/Kuru.js.map +1 -1
  25. package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
  26. package/dist/classes/DexAggregators/KyberSwap.js +112 -61
  27. package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
  28. package/dist/classes/DexAggregators/MultiDexAgg.d.ts +2 -2
  29. package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
  30. package/dist/classes/DexAggregators/MultiDexAgg.js +30 -18
  31. package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
  32. package/dist/classes/DexAggregators/UnsupportedDexAgg.d.ts +19 -0
  33. package/dist/classes/DexAggregators/UnsupportedDexAgg.d.ts.map +1 -0
  34. package/dist/classes/DexAggregators/UnsupportedDexAgg.js +29 -0
  35. package/dist/classes/DexAggregators/UnsupportedDexAgg.js.map +1 -0
  36. package/dist/classes/DexAggregators/helpers.d.ts +30 -0
  37. package/dist/classes/DexAggregators/helpers.d.ts.map +1 -0
  38. package/dist/classes/DexAggregators/helpers.js +57 -0
  39. package/dist/classes/DexAggregators/helpers.js.map +1 -0
  40. package/dist/classes/DexAggregators/index.d.ts +2 -0
  41. package/dist/classes/DexAggregators/index.d.ts.map +1 -1
  42. package/dist/classes/DexAggregators/index.js +1 -0
  43. package/dist/classes/DexAggregators/index.js.map +1 -1
  44. package/dist/classes/ERC20.d.ts +3 -3
  45. package/dist/classes/ERC20.d.ts.map +1 -1
  46. package/dist/classes/ERC20.js +26 -10
  47. package/dist/classes/ERC20.js.map +1 -1
  48. package/dist/classes/FormatConverter.d.ts.map +1 -1
  49. package/dist/classes/FormatConverter.js +4 -1
  50. package/dist/classes/FormatConverter.js.map +1 -1
  51. package/dist/classes/Market.d.ts +46 -8
  52. package/dist/classes/Market.d.ts.map +1 -1
  53. package/dist/classes/Market.js +342 -111
  54. package/dist/classes/Market.js.map +1 -1
  55. package/dist/classes/NativeToken.d.ts +2 -2
  56. package/dist/classes/NativeToken.d.ts.map +1 -1
  57. package/dist/classes/NativeToken.js +21 -7
  58. package/dist/classes/NativeToken.js.map +1 -1
  59. package/dist/classes/OptimizerReader.d.ts +15 -7
  60. package/dist/classes/OptimizerReader.d.ts.map +1 -1
  61. package/dist/classes/OptimizerReader.js +108 -30
  62. package/dist/classes/OptimizerReader.js.map +1 -1
  63. package/dist/classes/OracleManager.d.ts.map +1 -1
  64. package/dist/classes/OracleManager.js +11 -4
  65. package/dist/classes/OracleManager.js.map +1 -1
  66. package/dist/classes/PositionManager.d.ts.map +1 -1
  67. package/dist/classes/PositionManager.js +1 -3
  68. package/dist/classes/PositionManager.js.map +1 -1
  69. package/dist/classes/ProtocolReader.d.ts +30 -9
  70. package/dist/classes/ProtocolReader.d.ts.map +1 -1
  71. package/dist/classes/ProtocolReader.js +158 -138
  72. package/dist/classes/ProtocolReader.js.map +1 -1
  73. package/dist/classes/Zapper.d.ts +5 -5
  74. package/dist/classes/Zapper.d.ts.map +1 -1
  75. package/dist/classes/Zapper.js +21 -18
  76. package/dist/classes/Zapper.js.map +1 -1
  77. package/dist/contracts/monad-mainnet.json +1 -1
  78. package/dist/feePolicy.d.ts +2 -0
  79. package/dist/feePolicy.d.ts.map +1 -1
  80. package/dist/feePolicy.js +19 -0
  81. package/dist/feePolicy.js.map +1 -1
  82. package/dist/format/borrow.d.ts.map +1 -1
  83. package/dist/format/borrow.js +7 -1
  84. package/dist/format/borrow.js.map +1 -1
  85. package/dist/format/health.d.ts +3 -2
  86. package/dist/format/health.d.ts.map +1 -1
  87. package/dist/format/health.js +10 -9
  88. package/dist/format/health.js.map +1 -1
  89. package/dist/format/leverage.js +5 -4
  90. package/dist/format/leverage.js.map +1 -1
  91. package/dist/helpers.d.ts +14 -11
  92. package/dist/helpers.d.ts.map +1 -1
  93. package/dist/helpers.js +171 -52
  94. package/dist/helpers.js.map +1 -1
  95. package/dist/integrations/merkl.d.ts +2 -1
  96. package/dist/integrations/merkl.d.ts.map +1 -1
  97. package/dist/integrations/merkl.js +199 -4
  98. package/dist/integrations/merkl.js.map +1 -1
  99. package/dist/integrations/snapshot.d.ts +28 -24
  100. package/dist/integrations/snapshot.d.ts.map +1 -1
  101. package/dist/integrations/snapshot.js +106 -42
  102. package/dist/integrations/snapshot.js.map +1 -1
  103. package/dist/retry-provider.d.ts +6 -0
  104. package/dist/retry-provider.d.ts.map +1 -1
  105. package/dist/retry-provider.js +103 -11
  106. package/dist/retry-provider.js.map +1 -1
  107. package/dist/setup.d.ts +8 -6
  108. package/dist/setup.d.ts.map +1 -1
  109. package/dist/setup.js +125 -45
  110. package/dist/setup.js.map +1 -1
  111. package/dist/validation.d.ts +1 -1
  112. package/dist/validation.d.ts.map +1 -1
  113. package/dist/validation.js +13 -7
  114. package/dist/validation.js.map +1 -1
  115. package/package.json +6 -3
@@ -8,11 +8,19 @@ const helpers_1 = require("../helpers");
8
8
  const CToken_1 = require("./CToken");
9
9
  const MarketManagerIsolated_json_1 = __importDefault(require("../abis/MarketManagerIsolated.json"));
10
10
  const decimal_js_1 = require("decimal.js");
11
- const setup_1 = require("../setup");
12
11
  const merkl_1 = require("../integrations/merkl");
13
12
  const BorrowableCToken_1 = require("./BorrowableCToken");
14
13
  const FormatConverter_1 = __importDefault(require("./FormatConverter"));
15
14
  const Api_1 = require("./Api");
15
+ const chains_1 = require("../chains");
16
+ function resolveDefaultSetupConfig(context) {
17
+ const config = require("../setup").setup_config;
18
+ if (config == undefined) {
19
+ throw new Error(`Setup config is not configured for ${context}. ` +
20
+ `Pass setup/provider context explicitly or initialize setupChain() first.`);
21
+ }
22
+ return config;
23
+ }
16
24
  class Market {
17
25
  provider;
18
26
  signer;
@@ -26,6 +34,7 @@ class Market {
26
34
  cache;
27
35
  milestone = null;
28
36
  incentives = [];
37
+ _userDataScope;
29
38
  constructor(provider, signer, account, static_data, dynamic_data, user_data, deploy_data, oracle_manager, reader, setup) {
30
39
  this.provider = provider;
31
40
  this.signer = signer;
@@ -36,14 +45,10 @@ class Market {
36
45
  this.setup = setup;
37
46
  this.contract = (0, helpers_1.contractSetup)(provider, this.address, MarketManagerIsolated_json_1.default);
38
47
  this.cache = { static: static_data, dynamic: dynamic_data, user: user_data, deploy: deploy_data };
39
- for (let i = 0; i < static_data.tokens.length; i++) {
48
+ this._userDataScope = "full";
49
+ for (const tokenData of Market.mergeTokenPayloads(static_data, dynamic_data, user_data)) {
40
50
  // @NOTE: Merged fields from the 3 types, so you wanna make sure there is no collisions
41
51
  // Otherwise we will have some dataloss
42
- const tokenData = {
43
- ...static_data.tokens[i],
44
- ...dynamic_data.tokens[i],
45
- ...user_data.tokens[i]
46
- };
47
52
  if (tokenData.isBorrowable) {
48
53
  const ctoken = new BorrowableCToken_1.BorrowableCToken(provider, tokenData.address, tokenData, this);
49
54
  this.tokens.push(ctoken);
@@ -57,6 +62,44 @@ class Market {
57
62
  getAccountOrThrow() {
58
63
  return (0, helpers_1.requireAccount)(this.account, this.signer);
59
64
  }
65
+ assertRefreshAccountCompatible(account, allowSignerMismatch = false) {
66
+ if (allowSignerMismatch) {
67
+ return;
68
+ }
69
+ const signerAddress = this.signer?.address;
70
+ if (signerAddress == null || signerAddress.toLowerCase() === account.toLowerCase()) {
71
+ return;
72
+ }
73
+ throw new Error(`Cannot refresh signer-backed market ${this.address} for ${account}; ` +
74
+ `the active signer is ${signerAddress}.`);
75
+ }
76
+ bindRefreshedAccount(account, allowSignerMismatch = false) {
77
+ this.assertRefreshAccountCompatible(account, allowSignerMismatch);
78
+ this.account = account;
79
+ }
80
+ requireFullUserTokenData(accessLabel) {
81
+ if (this.userDataScope !== "summary") {
82
+ return;
83
+ }
84
+ throw new Error(`Market-level token-derived user data is stale for ${this.address} after a summary-only refresh. ` +
85
+ `Call market.reloadUserData(account) or Market.reloadUserMarkets(...) before ${accessLabel}.`);
86
+ }
87
+ requireValidCachedUserHealth(accessLabel) {
88
+ if (this.cache.user.errorCodeHit || this.cache.user.priceStale) {
89
+ throw new Error(`Cached market user data for ${this.address} is not reliable for ${accessLabel} because ` +
90
+ `the reader reported an oracle error or stale price.`);
91
+ }
92
+ }
93
+ cooldownDateFromUnlockTime(unlockTime, cooldownLength = this.cooldownLength) {
94
+ if (unlockTime === cooldownLength) {
95
+ return null;
96
+ }
97
+ const now = BigInt(Math.floor(Date.now() / 1000));
98
+ if (unlockTime <= now) {
99
+ return null;
100
+ }
101
+ return new Date(Number(unlockTime * 1000n));
102
+ }
60
103
  /** @returns {string} - The name of the market at deployment. */
61
104
  get name() { return this.cache.deploy.name; }
62
105
  /** @returns {Plugins} - The address of the market's plugins by deploy name. */
@@ -66,15 +109,27 @@ class Market {
66
109
  /** @returns {bigint[]} - A list of oracle identifiers which can be mapped to AdaptorTypes enum */
67
110
  get adapters() { return this.cache.static.adapters; }
68
111
  /** @returns {Date | null} - Market cooldown, activated by Collateralization or Borrowing. Lasts as long as {this.cooldownLength} which is currently 20mins */
69
- get cooldown() { return this.cache.user.cooldown == this.cooldownLength ? null : new Date(Number(this.cache.user.cooldown * 1000n)); }
112
+ get cooldown() { return this.cooldownDateFromUnlockTime(this.cache.user.cooldown); }
113
+ /** @returns The scope of the latest whole-market user refresh. */
114
+ get userDataScope() { return this._userDataScope ?? "full"; }
70
115
  /** @returns {Decimal} - The user's collateral in Shares. */
71
- get userCollateral() { return (0, helpers_1.toDecimal)(this.cache.user.collateral, 18n); }
116
+ get userCollateral() {
117
+ this.requireValidCachedUserHealth("reading userCollateral");
118
+ return (0, helpers_1.toDecimal)(this.cache.user.collateral, 18n);
119
+ }
72
120
  /** @returns {USD} - The user's debt in USD. */
73
- get userDebt() { return (0, helpers_1.toDecimal)(this.cache.user.debt, 18n); }
121
+ get userDebt() {
122
+ this.requireValidCachedUserHealth("reading userDebt");
123
+ return (0, helpers_1.toDecimal)(this.cache.user.debt, 18n);
124
+ }
74
125
  /** @returns {USD} - The user's maximum debt in USD. */
75
- get userMaxDebt() { return (0, helpers_1.toDecimal)(this.cache.user.maxDebt, 18n); }
126
+ get userMaxDebt() {
127
+ this.requireValidCachedUserHealth("reading userMaxDebt");
128
+ return (0, helpers_1.toDecimal)(this.cache.user.maxDebt, 18n);
129
+ }
76
130
  /** @returns {USD} - The user's remaining credit with a .1% buffer in USD */
77
131
  get userRemainingCredit() {
132
+ this.requireValidCachedUserHealth("reading userRemainingCredit");
78
133
  const remaining = this.cache.user.maxDebt - this.cache.user.debt;
79
134
  return (0, helpers_1.toDecimal)(remaining, 18n).mul(.999);
80
135
  }
@@ -83,6 +138,7 @@ class Market {
83
138
  * @returns {Percentage | null} - The user's position health Percentage or null if infinity
84
139
  */
85
140
  get positionHealth() {
141
+ this.requireValidCachedUserHealth("reading positionHealth");
86
142
  if (this.cache.user.positionHealth == helpers_1.UINT256_MAX) {
87
143
  return null;
88
144
  }
@@ -93,6 +149,7 @@ class Market {
93
149
  * @returns {USD} - The total user deposits in USD.
94
150
  */
95
151
  get userDeposits() {
152
+ this.requireFullUserTokenData("reading userDeposits");
96
153
  let total_deposits = (0, decimal_js_1.Decimal)(0);
97
154
  for (const token of this.tokens) {
98
155
  total_deposits = total_deposits.add(token.getUserAssetBalance(true));
@@ -104,6 +161,7 @@ class Market {
104
161
  * @returns {USD} - The user's net position in USD.
105
162
  */
106
163
  get userNet() {
164
+ this.requireFullUserTokenData("reading userNet");
107
165
  return this.userDeposits.sub(this.userDebt);
108
166
  }
109
167
  /** @returns Market LTV */
@@ -127,13 +185,17 @@ class Market {
127
185
  }
128
186
  return `${min.mul(100)}% - ${max.mul(100)}%`;
129
187
  }
130
- /** @returns Total market deposits */
131
- get tvl() {
132
- let marketTvl = new decimal_js_1.Decimal(0);
188
+ /** @returns Total market deposits in USD, summed across the market's tokens.
189
+ * Renamed from `tvl` to match the sibling getter naming
190
+ * (`totalDebt`, `totalCollateral`). Backed by the per-token
191
+ * `getDeposits` (which now values via `totalAssets`, not `totalSupply`,
192
+ * so the liquidity ≤ deposits invariant holds). */
193
+ get totalDeposits() {
194
+ let total = new decimal_js_1.Decimal(0);
133
195
  for (const token of this.tokens) {
134
- marketTvl = marketTvl.add(token.getTvl(true));
196
+ total = total.add(token.getDeposits(true));
135
197
  }
136
- return marketTvl;
198
+ return total;
137
199
  }
138
200
  /** @returns Total market debt */
139
201
  get totalDebt() {
@@ -158,6 +220,7 @@ class Market {
158
220
  * @returns What tokens can and cannot be borrowed from
159
221
  */
160
222
  getBorrowableCTokens() {
223
+ this.requireFullUserTokenData("reading borrowable token eligibility");
161
224
  const result = {
162
225
  eligible: [],
163
226
  ineligible: []
@@ -181,6 +244,7 @@ class Market {
181
244
  * @returns The total user deposits change (ex: 50, which would be $50/day)
182
245
  */
183
246
  getUserDepositsChange(rate) {
247
+ this.requireFullUserTokenData(`reading user deposit change for rate ${rate}`);
184
248
  let total_change = (0, decimal_js_1.Decimal)(0);
185
249
  for (const token of this.tokens) {
186
250
  const amount = token.getUserAssetBalance(true);
@@ -194,6 +258,7 @@ class Market {
194
258
  * @returns The total user debt change (ex: 50, which would be $50/day)
195
259
  */
196
260
  getUserDebtChange(rate) {
261
+ this.requireFullUserTokenData(`reading user debt change for rate ${rate}`);
197
262
  let total_change = (0, decimal_js_1.Decimal)(0);
198
263
  for (const token of this.tokens) {
199
264
  if (!token.isBorrowable) {
@@ -210,6 +275,7 @@ class Market {
210
275
  * @returns The total user net change (ex: 50, which would be $50/day)
211
276
  */
212
277
  getUserNetChange(rate) {
278
+ this.requireFullUserTokenData(`reading user net change for rate ${rate}`);
213
279
  const earn = this.getUserDepositsChange(rate);
214
280
  const debt = this.getUserDebtChange(rate);
215
281
  return earn.sub(debt);
@@ -253,24 +319,82 @@ class Market {
253
319
  return Promise.all(this.tokens.map((token) => token.getSnapshot(account)));
254
320
  }
255
321
  hasUserActivity() {
322
+ this.requireFullUserTokenData("determining user activity");
256
323
  return this.tokens.some((token) => token.cache.userAssetBalance > 0n ||
257
324
  token.cache.userShareBalance > 0n ||
258
325
  token.cache.userCollateral > 0n ||
259
326
  token.cache.userDebt > 0n);
260
327
  }
328
+ requireRefreshTokenRows(rows, label) {
329
+ if (rows.length !== this.tokens.length) {
330
+ throw new Error(`Token row count mismatch for market ${this.address} during refresh: ` +
331
+ `expected=${this.tokens.length} ${label}=${rows.length}.`);
332
+ }
333
+ const rowsByAddress = new Map(rows.map((row) => [row.address.toLowerCase(), row]));
334
+ for (const token of this.tokens) {
335
+ if (!rowsByAddress.has(token.address.toLowerCase())) {
336
+ throw new Error(`Missing ${label} token data for ${token.address} in market ${this.address} during refresh.`);
337
+ }
338
+ }
339
+ return rowsByAddress;
340
+ }
341
+ requireRefreshMarketAddress(row, label) {
342
+ if (row.address.toLowerCase() !== this.address.toLowerCase()) {
343
+ throw new Error(`Mismatched ${label} market data during refresh: expected=${this.address} received=${row.address}.`);
344
+ }
345
+ }
346
+ validateStateRows(dynamicData, userData) {
347
+ this.requireRefreshMarketAddress(dynamicData, "dynamic");
348
+ if (userData != undefined) {
349
+ this.requireRefreshMarketAddress(userData, "user");
350
+ }
351
+ this.requireRefreshTokenRows(dynamicData.tokens, "dynamic");
352
+ if (userData != undefined) {
353
+ this.requireRefreshTokenRows(userData.tokens, "user");
354
+ }
355
+ }
356
+ validateRefreshState(dynamicData, userData) {
357
+ this.validateStateRows(dynamicData, userData);
358
+ }
359
+ static requireMarketRow(rows, market, label) {
360
+ const row = new Map(rows.map((entry) => [entry.address.toLowerCase(), entry])).get(market.address.toLowerCase());
361
+ if (row == undefined) {
362
+ throw new Error(`Could not reload ${label} data for market ${market.address}.`);
363
+ }
364
+ return row;
365
+ }
261
366
  applyState(dynamicData, userData) {
367
+ this.validateStateRows(dynamicData, userData);
368
+ const dynamicRowsByAddress = this.requireRefreshTokenRows(dynamicData.tokens, "dynamic");
369
+ const userRowsByAddress = userData != undefined
370
+ ? this.requireRefreshTokenRows(userData.tokens, "user")
371
+ : undefined;
262
372
  this.cache.dynamic = dynamicData;
263
373
  if (userData != undefined) {
264
374
  this.cache.user = userData;
375
+ this._userDataScope = "full";
265
376
  }
266
377
  for (const token of this.tokens) {
267
- const nextDynamic = dynamicData.tokens.find((t) => t.address == token.address);
268
- const nextUser = userData?.tokens.find((t) => t.address == token.address);
378
+ const nextDynamic = dynamicRowsByAddress.get(token.address.toLowerCase());
379
+ const nextUser = userRowsByAddress?.get(token.address.toLowerCase());
269
380
  token.cache = {
270
381
  ...token.cache,
271
- ...(nextDynamic ?? {}),
382
+ ...nextDynamic,
272
383
  ...(nextUser ?? {}),
273
384
  };
385
+ if (nextUser != undefined) {
386
+ token.markUserCacheFresh?.();
387
+ }
388
+ }
389
+ }
390
+ applyUserSummary(userData) {
391
+ this.cache.user = {
392
+ ...this.cache.user,
393
+ ...userData,
394
+ };
395
+ this._userDataScope = "summary";
396
+ for (const token of this.tokens) {
397
+ token.invalidateUserCache?.();
274
398
  }
275
399
  }
276
400
  async reloadMarketData() {
@@ -281,48 +405,149 @@ class Market {
281
405
  }
282
406
  this.applyState(dynamic);
283
407
  }
284
- async reloadUserData(account) {
408
+ async reloadUserData(account, options = {}) {
409
+ const allowSignerMismatch = options.allowSignerMismatch ?? false;
410
+ this.assertRefreshAccountCompatible(account, allowSignerMismatch);
285
411
  const { dynamicMarkets, userMarkets } = await this.reader.getMarketStates([this.address], account);
286
- const dynamic = dynamicMarkets[0];
287
- const user = userMarkets[0];
288
- if (dynamic == undefined || user == undefined) {
289
- throw new Error(`Could not reload market state for ${this.address}.`);
290
- }
412
+ const dynamic = Market.requireMarketRow(dynamicMarkets, this, "dynamic");
413
+ const user = Market.requireMarketRow(userMarkets, this, "user");
291
414
  this.applyState(dynamic, user);
415
+ this.bindRefreshedAccount(account, allowSignerMismatch);
416
+ }
417
+ async reloadUserSummary(account) {
418
+ this.assertRefreshAccountCompatible(account);
419
+ const userMarkets = await this.reader.getMarketSummaries([this.address], account);
420
+ const user = Market.requireMarketRow(userMarkets, this, "user summary");
421
+ this.applyUserSummary(user);
422
+ this.bindRefreshedAccount(account);
292
423
  }
293
424
  static getActiveUserMarkets(markets) {
294
425
  return markets.filter((market) => market.hasUserActivity());
295
426
  }
296
- static async reloadUserMarkets(markets, account) {
297
- if (markets.length === 0) {
298
- return [];
299
- }
427
+ static groupByReaderDeployment(markets) {
300
428
  const groups = new Map();
301
429
  for (const market of markets) {
302
- const existing = groups.get(market.reader);
430
+ const groupKey = market.reader.batchKey ?? market.reader;
431
+ const existing = groups.get(groupKey);
303
432
  if (existing) {
304
- existing.push(market);
433
+ existing.markets.push(market);
305
434
  }
306
435
  else {
307
- groups.set(market.reader, [market]);
436
+ groups.set(groupKey, {
437
+ reader: market.reader,
438
+ markets: [market],
439
+ });
308
440
  }
309
441
  }
310
- for (const [reader, groupedMarkets] of groups) {
442
+ return groups.values();
443
+ }
444
+ static async reloadUserMarkets(markets, account) {
445
+ if (markets.length === 0) {
446
+ return [];
447
+ }
448
+ for (const market of markets) {
449
+ market.assertRefreshAccountCompatible(account);
450
+ }
451
+ const plans = [];
452
+ for (const { reader, markets: groupedMarkets } of this.groupByReaderDeployment(markets)) {
311
453
  const addresses = groupedMarkets.map((market) => market.address);
312
454
  const { dynamicMarkets, userMarkets } = await reader.getMarketStates(addresses, account);
313
- const dynamicByAddress = new Map(dynamicMarkets.map((market) => [market.address, market]));
314
- const userByAddress = new Map(userMarkets.map((market) => [market.address, market]));
455
+ const dynamicByAddress = new Map(dynamicMarkets.map((market) => [market.address.toLowerCase(), market]));
456
+ const userByAddress = new Map(userMarkets.map((market) => [market.address.toLowerCase(), market]));
315
457
  for (const market of groupedMarkets) {
316
- const dynamic = dynamicByAddress.get(market.address);
317
- const user = userByAddress.get(market.address);
458
+ const dynamic = dynamicByAddress.get(market.address.toLowerCase());
459
+ const user = userByAddress.get(market.address.toLowerCase());
318
460
  if (dynamic == undefined || user == undefined) {
319
461
  throw new Error(`Could not reload market state for ${market.address}.`);
320
462
  }
321
- market.applyState(dynamic, user);
463
+ market.validateStateRows(dynamic, user);
464
+ plans.push({ market, dynamic, user });
465
+ }
466
+ }
467
+ for (const { market, dynamic, user } of plans) {
468
+ market.applyState(dynamic, user);
469
+ market.bindRefreshedAccount(account);
470
+ }
471
+ return markets;
472
+ }
473
+ static async reloadUserMarketSummaries(markets, account) {
474
+ if (markets.length === 0) {
475
+ return [];
476
+ }
477
+ for (const market of markets) {
478
+ market.assertRefreshAccountCompatible(account);
479
+ }
480
+ const plans = [];
481
+ for (const { reader, markets: groupedMarkets } of this.groupByReaderDeployment(markets)) {
482
+ const addresses = groupedMarkets.map((market) => market.address);
483
+ const userMarkets = await reader.getMarketSummaries(addresses, account);
484
+ const userByAddress = new Map(userMarkets.map((market) => [market.address.toLowerCase(), market]));
485
+ for (const market of groupedMarkets) {
486
+ const user = userByAddress.get(market.address.toLowerCase());
487
+ if (user == undefined) {
488
+ throw new Error(`Could not reload market user summary for ${market.address}.`);
489
+ }
490
+ market.requireRefreshMarketAddress(user, "user summary");
491
+ plans.push({ market, user });
322
492
  }
323
493
  }
494
+ for (const { market, user } of plans) {
495
+ market.applyUserSummary(user);
496
+ market.bindRefreshedAccount(account);
497
+ }
324
498
  return markets;
325
499
  }
500
+ static buildDeployDataIndex(setup) {
501
+ const index = new Map();
502
+ const deployments = setup.contracts.markets;
503
+ for (const [name, data] of Object.entries(deployments)) {
504
+ if (typeof data !== 'object' || data == null || typeof data.address !== 'string') {
505
+ continue;
506
+ }
507
+ const key = data.address.toLowerCase();
508
+ if (!index.has(key)) {
509
+ index.set(key, {
510
+ name,
511
+ plugins: 'plugins' in data ? data.plugins : {},
512
+ });
513
+ }
514
+ }
515
+ return index;
516
+ }
517
+ static buildYieldIndex(yields) {
518
+ const index = new Map();
519
+ for (const yieldEntry of yields) {
520
+ const key = yieldEntry.symbol.toUpperCase();
521
+ if (!index.has(key)) {
522
+ index.set(key, yieldEntry);
523
+ }
524
+ }
525
+ return index;
526
+ }
527
+ static buildMarketPayloadIndex(entries) {
528
+ return new Map(entries.map((entry) => [entry.address.toLowerCase(), entry]));
529
+ }
530
+ static requireTokenRow(rowsByAddress, tokenAddress, marketAddress, label) {
531
+ const row = rowsByAddress.get(tokenAddress.toLowerCase());
532
+ if (row == undefined) {
533
+ throw new Error(`Missing ${label} token data for ${tokenAddress} in market ${marketAddress} during Market boot.`);
534
+ }
535
+ return row;
536
+ }
537
+ static mergeTokenPayloads(staticData, dynamicData, userData) {
538
+ if (staticData.tokens.length !== dynamicData.tokens.length ||
539
+ staticData.tokens.length !== userData.tokens.length) {
540
+ throw new Error(`Token row count mismatch for market ${staticData.address} during Market boot: ` +
541
+ `static=${staticData.tokens.length} dynamic=${dynamicData.tokens.length} user=${userData.tokens.length}.`);
542
+ }
543
+ const dynamicByAddress = this.buildMarketPayloadIndex(dynamicData.tokens);
544
+ const userByAddress = this.buildMarketPayloadIndex(userData.tokens);
545
+ return staticData.tokens.map((staticToken) => ({
546
+ ...staticToken,
547
+ ...this.requireTokenRow(dynamicByAddress, staticToken.address, staticData.address, "dynamic"),
548
+ ...this.requireTokenRow(userByAddress, staticToken.address, staticData.address, "user"),
549
+ }));
550
+ }
326
551
  /**
327
552
  * Preview the impact of the user descision for their deposit/borrow/leverage
328
553
  * @param user - Wallet address
@@ -335,7 +560,14 @@ class Market {
335
560
  async previewAssetImpact(user, collateral_ctoken, debt_ctoken, deposit_amount, borrow_amount, rate_change) {
336
561
  const amount_in = (0, helpers_1.toBigInt)(deposit_amount, collateral_ctoken.asset.decimals);
337
562
  const amount_out = (0, helpers_1.toBigInt)(borrow_amount, debt_ctoken.asset.decimals);
338
- const { supply, borrow } = await this.reader.previewAssetImpact(user, collateral_ctoken.address, debt_ctoken.address, amount_in, amount_out);
563
+ let { supply, borrow } = await this.reader.previewAssetImpact(user, collateral_ctoken.address, debt_ctoken.address, amount_in, amount_out);
564
+ if (amount_out > 0n) {
565
+ const irm = await debt_ctoken.dynamicIRM();
566
+ const currentAssetsHeld = await debt_ctoken.contract.assetsHeld();
567
+ const currentDebt = await debt_ctoken.contract.marketOutstandingDebt();
568
+ const projectedAssetsHeld = currentAssetsHeld > amount_out ? currentAssetsHeld - amount_out : 0n;
569
+ borrow = await irm.borrowRate(projectedAssetsHeld, currentDebt + amount_out);
570
+ }
339
571
  const supply_apy = (0, decimal_js_1.Decimal)(supply * (0, helpers_1.getRateSeconds)('year')).div(helpers_1.WAD);
340
572
  const borrow_apy = (0, decimal_js_1.Decimal)(borrow * (0, helpers_1.getRateSeconds)('year')).div(helpers_1.WAD);
341
573
  const supply_percent = (0, decimal_js_1.Decimal)(supply * (0, helpers_1.getRateSeconds)(rate_change)).div(helpers_1.WAD);
@@ -371,27 +603,26 @@ class Market {
371
603
  if (newLeverage.equals(1)) {
372
604
  return null;
373
605
  }
374
- const { collateralAssetReduction } = deposit_ctoken.previewLeverageDown(newLeverage, currentLeverage);
375
- const repayUsd = deposit_ctoken.convertTokensToUsd(collateralAssetReduction, true);
606
+ const preview = deposit_ctoken.previewLeverageDown(newLeverage, currentLeverage, borrow_ctoken);
607
+ const repayCollateralAssets = preview.collateralAssetReduction;
608
+ let { collateralAssetReduction } = preview;
609
+ if (preview.feeBps > 0n) {
610
+ collateralAssetReduction = collateralAssetReduction * 10000n / (10000n - preview.feeBps);
611
+ }
612
+ const repayUsd = deposit_ctoken.convertTokensToUsd(repayCollateralAssets, true);
376
613
  const repayTokens = borrow_ctoken.convertUsdToTokens(repayUsd, true);
377
614
  return this.previewPositionHealth(deposit_ctoken, borrow_ctoken, false, FormatConverter_1.default.bigIntToDecimal(collateralAssetReduction, deposit_ctoken.asset.decimals), true, repayTokens);
378
615
  }
379
- async previewPositionHealthLeverageUp(deposit_ctoken, borrow_ctoken, newLeverage, depositAssets) {
380
- const { borrowAmount } = deposit_ctoken.previewLeverageUp(newLeverage, borrow_ctoken, depositAssets);
381
- // borrowAmount is the reduced amount sent to the contract — this is both
382
- // what enters the vault/swap (becomes collateral) and what the user owes (debt).
383
- // Use price-based conversion for collateral increase — this matches how the
384
- // on-chain health reader values positions (via oracle prices, not vault rates).
385
- const borrowUsd = borrowAmount.mul(borrow_ctoken.getPrice(true));
386
- const collateralFromBorrow = borrowUsd.div(deposit_ctoken.getPrice(true));
387
- // Total collateral increase = initial deposit + borrowed amount swapped to collateral.
388
- // The on-chain reader starts from the user's current position, so the deposit
389
- // must be included or the preview will undercount collateral (showing ~0% health).
390
- const depositInTokens = depositAssets
391
- ? FormatConverter_1.default.bigIntToDecimal(depositAssets, deposit_ctoken.asset.decimals)
392
- : (0, decimal_js_1.Decimal)(0);
393
- const collateralIncrease = collateralFromBorrow.add(depositInTokens);
394
- return this.previewPositionHealth(deposit_ctoken, borrow_ctoken, true, collateralIncrease, false, borrowAmount);
616
+ async previewPositionHealthLeverageUp(deposit_ctoken, borrow_ctoken, newLeverage, depositAssets, positionManagerType) {
617
+ if ((depositAssets ?? 0n) > 0n) {
618
+ return this.previewPositionHealthDepositAndLeverage(deposit_ctoken, borrow_ctoken, newLeverage, depositAssets, positionManagerType);
619
+ }
620
+ const preview = deposit_ctoken.previewLeverageUp(newLeverage, borrow_ctoken, undefined, positionManagerType);
621
+ return this.previewPositionHealth(deposit_ctoken, borrow_ctoken, true, preview.collateralIncreaseInAssets, false, preview.debtIncreaseInAssets);
622
+ }
623
+ async previewPositionHealthDepositAndLeverage(deposit_ctoken, borrow_ctoken, newLeverage, depositAssets, positionManagerType) {
624
+ const preview = deposit_ctoken.previewDepositAndLeverage(newLeverage, borrow_ctoken, depositAssets, positionManagerType);
625
+ return this.previewPositionHealth(deposit_ctoken, borrow_ctoken, true, preview.collateralIncreaseInAssets, false, preview.debtIncreaseInAssets);
395
626
  }
396
627
  /**
397
628
  * A dynamic position health previewer for any action
@@ -434,12 +665,13 @@ class Market {
434
665
  */
435
666
  async previewPositionHealthRedeem(ctoken, amount) {
436
667
  const user = this.getAccountOrThrow();
437
- const redeem_amount = ctoken.convertTokenInputToShares(amount);
438
- const existing_collateral = ctoken.cache.userCollateral;
439
- if (redeem_amount > existing_collateral) {
440
- throw new Error(`Insufficient collateral: Existing (${existing_collateral}) < Redeem amount (${redeem_amount})`);
668
+ const redeemShares = ctoken.convertTokenInputToShares(amount);
669
+ const redeemAssets = FormatConverter_1.default.decimalToBigInt(amount, ctoken.asset.decimals);
670
+ const existing_collateral = ctoken.getUserCollateralShares();
671
+ if (redeemShares > existing_collateral) {
672
+ throw new Error(`Insufficient collateral: Existing (${existing_collateral}) < Redeem amount (${redeemShares})`);
441
673
  }
442
- const data = await this.reader.getPositionHealth(this.address, user, ctoken.address, helpers_1.EMPTY_ADDRESS, false, redeem_amount, false, 0n, 0n);
674
+ const data = await this.reader.getPositionHealth(this.address, user, ctoken.address, helpers_1.EMPTY_ADDRESS, false, redeemAssets, false, 0n, 0n);
443
675
  if (data.errorCodeHit) {
444
676
  throw new Error(`Error code hit when calculating position health preview. This usually means price is stale so we couldn't get a valid health value.`);
445
677
  }
@@ -476,7 +708,10 @@ class Market {
476
708
  */
477
709
  async previewPositionHealthRepay(token, amount) {
478
710
  const user = this.getAccountOrThrow();
479
- const data = await this.reader.getPositionHealth(this.address, user, helpers_1.EMPTY_ADDRESS, token.address, false, 0n, true, FormatConverter_1.default.decimalToBigInt(amount, token.decimals), 0n);
711
+ const repayAssets = amount.eq(0)
712
+ ? helpers_1.UINT256_MAX
713
+ : FormatConverter_1.default.decimalToBigInt(amount, token.decimals);
714
+ const data = await this.reader.getPositionHealth(this.address, user, helpers_1.EMPTY_ADDRESS, token.address, false, 0n, true, repayAssets, 0n);
480
715
  if (data.errorCodeHit) {
481
716
  throw new Error(`Error code hit when calculating position health preview. This usually means price is stale so we couldn't get a valid health value.`);
482
717
  }
@@ -490,8 +725,8 @@ class Market {
490
725
  * @param borrowAssets - Amount of assets being borrowed
491
726
  * @returns An object containing the hypothetical liquidity values
492
727
  */
493
- async hypotheticalLiquidityOf(account, cTokenModified = helpers_1.EMPTY_ADDRESS, redemptionShares = 0n, borrowAssets = 0n) {
494
- return this.contract.hypotheticalLiquidityOf(account, cTokenModified, redemptionShares, borrowAssets);
728
+ async hypotheticalLiquidityOf(account, cTokenModified = helpers_1.EMPTY_ADDRESS, redemptionShares = 0n, borrowAssets = 0n, bufferTime = 0n) {
729
+ return this.reader.hypotheticalLiquidityOf(this.address, account, cTokenModified, redemptionShares, borrowAssets, bufferTime);
495
730
  }
496
731
  /**
497
732
  * Fetch the expiration date of a user's cooldown period
@@ -503,7 +738,7 @@ class Market {
503
738
  const cooldownTimestamp = await this.contract.accountAssets(account);
504
739
  const cooldownLength = fetch || this.cooldownLength == 0n ? await this.contract.MIN_HOLD_PERIOD() : this.cooldownLength;
505
740
  const unlockTime = cooldownTimestamp + cooldownLength;
506
- return unlockTime == cooldownLength ? null : new Date(Number(unlockTime * 1000n));
741
+ return this.cooldownDateFromUnlockTime(unlockTime, cooldownLength);
507
742
  }
508
743
  /**
509
744
  * Fetch multiple market cooldown expirations
@@ -522,7 +757,7 @@ class Market {
522
757
  const market = markets[i];
523
758
  const cooldownTimestamp = cooldownTimestamps[i];
524
759
  const cooldownLength = market.cooldownLength;
525
- cooldowns[market.address] = cooldownTimestamp == cooldownLength ? null : new Date(Number(cooldownTimestamp * 1000n));
760
+ cooldowns[market.address] = market.cooldownDateFromUnlockTime(cooldownTimestamp, cooldownLength);
526
761
  }
527
762
  return cooldowns;
528
763
  }
@@ -533,68 +768,64 @@ class Market {
533
768
  * @param provider - The RPC provider
534
769
  * @returns An array of Market instances setup with protocol reader data
535
770
  */
536
- static async getAll(reader, oracle_manager, provider = setup_1.setup_config.readProvider, signer = setup_1.setup_config.signer, account = setup_1.setup_config.account, milestones = {}, incentives = {}, setup = setup_1.setup_config) {
537
- const all_data = await reader.getAllMarketData(account);
538
- const deploy_keys = Object.keys(setup.contracts.markets);
771
+ static async getAll(reader, oracle_manager, provider, signer, account, milestones = {}, incentives = {}, setup) {
772
+ const resolvedSetup = setup ?? resolveDefaultSetupConfig("Market.getAll");
773
+ const resolvedProvider = provider ?? resolvedSetup.readProvider;
774
+ const resolvedSigner = signer === undefined ? resolvedSetup.signer : signer;
775
+ const resolvedAccount = account === undefined ? resolvedSetup.account : account;
776
+ const chainId = chains_1.chain_config[resolvedSetup.chain]?.chainId;
777
+ const all_data = await reader.getAllMarketData(resolvedAccount);
539
778
  // Filter out USDC — DeFiLlama incorrectly returns YZM vault yield labeled as USDC
540
779
  const [yields, merklLendOpps, merklBorrowOpps] = await Promise.all([
541
- Api_1.Api.fetchNativeYields(setup).then(y => y.filter(y => y.symbol.toUpperCase() !== 'USDC')),
542
- (0, merkl_1.fetchMerklOpportunities)({ action: 'LEND' }).catch(() => []),
543
- (0, merkl_1.fetchMerklOpportunities)({ action: 'BORROW' }).catch(() => []),
780
+ Api_1.Api.fetchNativeYields(resolvedSetup).then(y => y.filter(y => y.symbol.toUpperCase() !== 'USDC')),
781
+ (0, merkl_1.fetchMerklOpportunities)({ action: 'LEND', chainId }).catch(() => []),
782
+ (0, merkl_1.fetchMerklOpportunities)({ action: 'BORROW', chainId }).catch(() => []),
544
783
  ]);
784
+ const deployIndex = this.buildDeployDataIndex(resolvedSetup);
785
+ const milestonesByAddress = new Map(Object.entries(milestones).map(([market, milestone]) => [market.toLowerCase(), milestone]));
786
+ const incentivesByAddress = new Map(Object.entries(incentives).map(([market, marketIncentives]) => [market.toLowerCase(), marketIncentives]));
787
+ const lendOppApyByToken = (0, helpers_1.aggregateMerklAprByToken)(merklLendOpps, "deposit");
788
+ const borrowOppApyByToken = (0, helpers_1.aggregateMerklAprByToken)(merklBorrowOpps, "borrow");
789
+ const yieldIndex = this.buildYieldIndex(yields);
790
+ const dynamicByAddress = this.buildMarketPayloadIndex(all_data.dynamicMarket);
791
+ const userByAddress = this.buildMarketPayloadIndex(all_data.userData.markets);
545
792
  let markets = [];
546
- for (let i = 0; i < all_data.staticMarket.length; i++) {
547
- const staticData = all_data.staticMarket[i];
548
- const dynamicData = all_data.dynamicMarket[i];
549
- const userData = all_data.userData.markets[i];
793
+ for (const staticData of all_data.staticMarket) {
550
794
  const market_address = staticData.address;
551
- let deploy_data;
552
- for (const obj_key of deploy_keys) {
553
- const data = setup.contracts.markets[obj_key];
554
- if (typeof data != 'object') {
555
- continue;
556
- }
557
- if (market_address == data.address) {
558
- deploy_data = {
559
- name: obj_key,
560
- plugins: 'plugins' in data ? data.plugins : {}
561
- };
562
- break;
563
- }
564
- }
795
+ const deploy_data = deployIndex.get(market_address.toLowerCase());
796
+ const dynamicData = dynamicByAddress.get(market_address.toLowerCase());
797
+ const userData = userByAddress.get(market_address.toLowerCase());
565
798
  if (deploy_data == undefined) {
566
799
  console.warn(`Could not find deploy data for market: ${market_address}, skipping...`);
567
800
  continue;
568
801
  }
569
- if (staticData == undefined) {
570
- console.warn(`Could not find static market data for index: ${i}`);
571
- continue;
572
- }
573
802
  if (dynamicData == undefined) {
574
- console.warn(`Could not find dynamic market data for index: ${i}`);
575
- continue;
803
+ throw new Error(`Missing dynamic market data for ${market_address} during Market.getAll boot.`);
576
804
  }
577
805
  if (userData == undefined) {
578
- console.warn(`Could not find user market data for index: ${i}`);
579
- continue;
806
+ throw new Error(`Missing user market data for ${market_address} during Market.getAll boot.`);
580
807
  }
581
- const market = new Market(provider, signer, account, staticData, dynamicData, userData, deploy_data, oracle_manager, reader, setup);
582
- if (milestones[market.address] != undefined) {
583
- market.milestone = milestones[market.address];
808
+ const market = new Market(resolvedProvider, resolvedSigner, resolvedAccount, staticData, dynamicData, userData, deploy_data, oracle_manager, reader, resolvedSetup);
809
+ const rewardKey = market.address.toLowerCase();
810
+ const milestone = milestonesByAddress.get(rewardKey);
811
+ if (milestone != undefined) {
812
+ market.milestone = milestone;
584
813
  }
585
- if (incentives[market.address] != undefined) {
586
- market.incentives = incentives[market.address];
814
+ const marketIncentives = incentivesByAddress.get(rewardKey);
815
+ if (marketIncentives != undefined) {
816
+ market.incentives = marketIncentives;
587
817
  }
588
818
  for (const token of market.tokens) {
589
- const lendOpp = merklLendOpps.find(o => o.identifier.toLowerCase() === token.address.toLowerCase());
590
- if (lendOpp != undefined) {
591
- token.incentiveSupplyApy = new decimal_js_1.Decimal(lendOpp.apr / 100);
819
+ const tokenKey = token.address.toLowerCase();
820
+ const lendApy = lendOppApyByToken.get(tokenKey);
821
+ if (lendApy != undefined) {
822
+ token.incentiveSupplyApy = lendApy;
592
823
  }
593
- const borrowOpp = merklBorrowOpps.find(o => o.identifier.toLowerCase() === token.address.toLowerCase());
594
- if (borrowOpp != undefined) {
595
- token.incentiveBorrowApy = new decimal_js_1.Decimal(borrowOpp.apr / 100);
824
+ const borrowApy = borrowOppApyByToken.get(tokenKey);
825
+ if (borrowApy != undefined) {
826
+ token.incentiveBorrowApy = borrowApy;
596
827
  }
597
- const api_yield = yields.find(y => y.symbol.toUpperCase() == token.asset.symbol.toUpperCase());
828
+ const api_yield = yieldIndex.get(token.asset.symbol.toUpperCase());
598
829
  if (api_yield != undefined) {
599
830
  token.nativeApy = new decimal_js_1.Decimal(api_yield.apy / 100);
600
831
  }