curvance 5.0.0 → 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 +22 -7
  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 +75 -18
  15. package/dist/classes/CToken.d.ts.map +1 -1
  16. package/dist/classes/CToken.js +638 -269
  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 +40 -6
  52. package/dist/classes/Market.d.ts.map +1 -1
  53. package/dist/classes/Market.js +333 -106
  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 -12
  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 */
@@ -162,6 +220,7 @@ class Market {
162
220
  * @returns What tokens can and cannot be borrowed from
163
221
  */
164
222
  getBorrowableCTokens() {
223
+ this.requireFullUserTokenData("reading borrowable token eligibility");
165
224
  const result = {
166
225
  eligible: [],
167
226
  ineligible: []
@@ -185,6 +244,7 @@ class Market {
185
244
  * @returns The total user deposits change (ex: 50, which would be $50/day)
186
245
  */
187
246
  getUserDepositsChange(rate) {
247
+ this.requireFullUserTokenData(`reading user deposit change for rate ${rate}`);
188
248
  let total_change = (0, decimal_js_1.Decimal)(0);
189
249
  for (const token of this.tokens) {
190
250
  const amount = token.getUserAssetBalance(true);
@@ -198,6 +258,7 @@ class Market {
198
258
  * @returns The total user debt change (ex: 50, which would be $50/day)
199
259
  */
200
260
  getUserDebtChange(rate) {
261
+ this.requireFullUserTokenData(`reading user debt change for rate ${rate}`);
201
262
  let total_change = (0, decimal_js_1.Decimal)(0);
202
263
  for (const token of this.tokens) {
203
264
  if (!token.isBorrowable) {
@@ -214,6 +275,7 @@ class Market {
214
275
  * @returns The total user net change (ex: 50, which would be $50/day)
215
276
  */
216
277
  getUserNetChange(rate) {
278
+ this.requireFullUserTokenData(`reading user net change for rate ${rate}`);
217
279
  const earn = this.getUserDepositsChange(rate);
218
280
  const debt = this.getUserDebtChange(rate);
219
281
  return earn.sub(debt);
@@ -257,24 +319,82 @@ class Market {
257
319
  return Promise.all(this.tokens.map((token) => token.getSnapshot(account)));
258
320
  }
259
321
  hasUserActivity() {
322
+ this.requireFullUserTokenData("determining user activity");
260
323
  return this.tokens.some((token) => token.cache.userAssetBalance > 0n ||
261
324
  token.cache.userShareBalance > 0n ||
262
325
  token.cache.userCollateral > 0n ||
263
326
  token.cache.userDebt > 0n);
264
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
+ }
265
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;
266
372
  this.cache.dynamic = dynamicData;
267
373
  if (userData != undefined) {
268
374
  this.cache.user = userData;
375
+ this._userDataScope = "full";
269
376
  }
270
377
  for (const token of this.tokens) {
271
- const nextDynamic = dynamicData.tokens.find((t) => t.address == token.address);
272
- 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());
273
380
  token.cache = {
274
381
  ...token.cache,
275
- ...(nextDynamic ?? {}),
382
+ ...nextDynamic,
276
383
  ...(nextUser ?? {}),
277
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?.();
278
398
  }
279
399
  }
280
400
  async reloadMarketData() {
@@ -285,48 +405,149 @@ class Market {
285
405
  }
286
406
  this.applyState(dynamic);
287
407
  }
288
- async reloadUserData(account) {
408
+ async reloadUserData(account, options = {}) {
409
+ const allowSignerMismatch = options.allowSignerMismatch ?? false;
410
+ this.assertRefreshAccountCompatible(account, allowSignerMismatch);
289
411
  const { dynamicMarkets, userMarkets } = await this.reader.getMarketStates([this.address], account);
290
- const dynamic = dynamicMarkets[0];
291
- const user = userMarkets[0];
292
- if (dynamic == undefined || user == undefined) {
293
- throw new Error(`Could not reload market state for ${this.address}.`);
294
- }
412
+ const dynamic = Market.requireMarketRow(dynamicMarkets, this, "dynamic");
413
+ const user = Market.requireMarketRow(userMarkets, this, "user");
295
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);
296
423
  }
297
424
  static getActiveUserMarkets(markets) {
298
425
  return markets.filter((market) => market.hasUserActivity());
299
426
  }
300
- static async reloadUserMarkets(markets, account) {
301
- if (markets.length === 0) {
302
- return [];
303
- }
427
+ static groupByReaderDeployment(markets) {
304
428
  const groups = new Map();
305
429
  for (const market of markets) {
306
- const existing = groups.get(market.reader);
430
+ const groupKey = market.reader.batchKey ?? market.reader;
431
+ const existing = groups.get(groupKey);
307
432
  if (existing) {
308
- existing.push(market);
433
+ existing.markets.push(market);
309
434
  }
310
435
  else {
311
- groups.set(market.reader, [market]);
436
+ groups.set(groupKey, {
437
+ reader: market.reader,
438
+ markets: [market],
439
+ });
312
440
  }
313
441
  }
314
- 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)) {
315
453
  const addresses = groupedMarkets.map((market) => market.address);
316
454
  const { dynamicMarkets, userMarkets } = await reader.getMarketStates(addresses, account);
317
- const dynamicByAddress = new Map(dynamicMarkets.map((market) => [market.address, market]));
318
- 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]));
319
457
  for (const market of groupedMarkets) {
320
- const dynamic = dynamicByAddress.get(market.address);
321
- const user = userByAddress.get(market.address);
458
+ const dynamic = dynamicByAddress.get(market.address.toLowerCase());
459
+ const user = userByAddress.get(market.address.toLowerCase());
322
460
  if (dynamic == undefined || user == undefined) {
323
461
  throw new Error(`Could not reload market state for ${market.address}.`);
324
462
  }
325
- 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 });
326
492
  }
327
493
  }
494
+ for (const { market, user } of plans) {
495
+ market.applyUserSummary(user);
496
+ market.bindRefreshedAccount(account);
497
+ }
328
498
  return markets;
329
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
+ }
330
551
  /**
331
552
  * Preview the impact of the user descision for their deposit/borrow/leverage
332
553
  * @param user - Wallet address
@@ -339,7 +560,14 @@ class Market {
339
560
  async previewAssetImpact(user, collateral_ctoken, debt_ctoken, deposit_amount, borrow_amount, rate_change) {
340
561
  const amount_in = (0, helpers_1.toBigInt)(deposit_amount, collateral_ctoken.asset.decimals);
341
562
  const amount_out = (0, helpers_1.toBigInt)(borrow_amount, debt_ctoken.asset.decimals);
342
- 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
+ }
343
571
  const supply_apy = (0, decimal_js_1.Decimal)(supply * (0, helpers_1.getRateSeconds)('year')).div(helpers_1.WAD);
344
572
  const borrow_apy = (0, decimal_js_1.Decimal)(borrow * (0, helpers_1.getRateSeconds)('year')).div(helpers_1.WAD);
345
573
  const supply_percent = (0, decimal_js_1.Decimal)(supply * (0, helpers_1.getRateSeconds)(rate_change)).div(helpers_1.WAD);
@@ -375,27 +603,26 @@ class Market {
375
603
  if (newLeverage.equals(1)) {
376
604
  return null;
377
605
  }
378
- const { collateralAssetReduction } = deposit_ctoken.previewLeverageDown(newLeverage, currentLeverage);
379
- 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);
380
613
  const repayTokens = borrow_ctoken.convertUsdToTokens(repayUsd, true);
381
614
  return this.previewPositionHealth(deposit_ctoken, borrow_ctoken, false, FormatConverter_1.default.bigIntToDecimal(collateralAssetReduction, deposit_ctoken.asset.decimals), true, repayTokens);
382
615
  }
383
- async previewPositionHealthLeverageUp(deposit_ctoken, borrow_ctoken, newLeverage, depositAssets) {
384
- const { borrowAmount } = deposit_ctoken.previewLeverageUp(newLeverage, borrow_ctoken, depositAssets);
385
- // borrowAmount is the reduced amount sent to the contract — this is both
386
- // what enters the vault/swap (becomes collateral) and what the user owes (debt).
387
- // Use price-based conversion for collateral increase — this matches how the
388
- // on-chain health reader values positions (via oracle prices, not vault rates).
389
- const borrowUsd = borrowAmount.mul(borrow_ctoken.getPrice(true));
390
- const collateralFromBorrow = borrowUsd.div(deposit_ctoken.getPrice(true));
391
- // Total collateral increase = initial deposit + borrowed amount swapped to collateral.
392
- // The on-chain reader starts from the user's current position, so the deposit
393
- // must be included or the preview will undercount collateral (showing ~0% health).
394
- const depositInTokens = depositAssets
395
- ? FormatConverter_1.default.bigIntToDecimal(depositAssets, deposit_ctoken.asset.decimals)
396
- : (0, decimal_js_1.Decimal)(0);
397
- const collateralIncrease = collateralFromBorrow.add(depositInTokens);
398
- 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);
399
626
  }
400
627
  /**
401
628
  * A dynamic position health previewer for any action
@@ -438,12 +665,13 @@ class Market {
438
665
  */
439
666
  async previewPositionHealthRedeem(ctoken, amount) {
440
667
  const user = this.getAccountOrThrow();
441
- const redeem_amount = ctoken.convertTokenInputToShares(amount);
442
- const existing_collateral = ctoken.cache.userCollateral;
443
- if (redeem_amount > existing_collateral) {
444
- 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})`);
445
673
  }
446
- 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);
447
675
  if (data.errorCodeHit) {
448
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.`);
449
677
  }
@@ -480,7 +708,10 @@ class Market {
480
708
  */
481
709
  async previewPositionHealthRepay(token, amount) {
482
710
  const user = this.getAccountOrThrow();
483
- 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);
484
715
  if (data.errorCodeHit) {
485
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.`);
486
717
  }
@@ -494,8 +725,8 @@ class Market {
494
725
  * @param borrowAssets - Amount of assets being borrowed
495
726
  * @returns An object containing the hypothetical liquidity values
496
727
  */
497
- async hypotheticalLiquidityOf(account, cTokenModified = helpers_1.EMPTY_ADDRESS, redemptionShares = 0n, borrowAssets = 0n) {
498
- 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);
499
730
  }
500
731
  /**
501
732
  * Fetch the expiration date of a user's cooldown period
@@ -507,7 +738,7 @@ class Market {
507
738
  const cooldownTimestamp = await this.contract.accountAssets(account);
508
739
  const cooldownLength = fetch || this.cooldownLength == 0n ? await this.contract.MIN_HOLD_PERIOD() : this.cooldownLength;
509
740
  const unlockTime = cooldownTimestamp + cooldownLength;
510
- return unlockTime == cooldownLength ? null : new Date(Number(unlockTime * 1000n));
741
+ return this.cooldownDateFromUnlockTime(unlockTime, cooldownLength);
511
742
  }
512
743
  /**
513
744
  * Fetch multiple market cooldown expirations
@@ -526,7 +757,7 @@ class Market {
526
757
  const market = markets[i];
527
758
  const cooldownTimestamp = cooldownTimestamps[i];
528
759
  const cooldownLength = market.cooldownLength;
529
- cooldowns[market.address] = cooldownTimestamp == cooldownLength ? null : new Date(Number(cooldownTimestamp * 1000n));
760
+ cooldowns[market.address] = market.cooldownDateFromUnlockTime(cooldownTimestamp, cooldownLength);
530
761
  }
531
762
  return cooldowns;
532
763
  }
@@ -537,68 +768,64 @@ class Market {
537
768
  * @param provider - The RPC provider
538
769
  * @returns An array of Market instances setup with protocol reader data
539
770
  */
540
- 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) {
541
- const all_data = await reader.getAllMarketData(account);
542
- 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);
543
778
  // Filter out USDC — DeFiLlama incorrectly returns YZM vault yield labeled as USDC
544
779
  const [yields, merklLendOpps, merklBorrowOpps] = await Promise.all([
545
- Api_1.Api.fetchNativeYields(setup).then(y => y.filter(y => y.symbol.toUpperCase() !== 'USDC')),
546
- (0, merkl_1.fetchMerklOpportunities)({ action: 'LEND' }).catch(() => []),
547
- (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(() => []),
548
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);
549
792
  let markets = [];
550
- for (let i = 0; i < all_data.staticMarket.length; i++) {
551
- const staticData = all_data.staticMarket[i];
552
- const dynamicData = all_data.dynamicMarket[i];
553
- const userData = all_data.userData.markets[i];
793
+ for (const staticData of all_data.staticMarket) {
554
794
  const market_address = staticData.address;
555
- let deploy_data;
556
- for (const obj_key of deploy_keys) {
557
- const data = setup.contracts.markets[obj_key];
558
- if (typeof data != 'object') {
559
- continue;
560
- }
561
- if (market_address == data.address) {
562
- deploy_data = {
563
- name: obj_key,
564
- plugins: 'plugins' in data ? data.plugins : {}
565
- };
566
- break;
567
- }
568
- }
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());
569
798
  if (deploy_data == undefined) {
570
799
  console.warn(`Could not find deploy data for market: ${market_address}, skipping...`);
571
800
  continue;
572
801
  }
573
- if (staticData == undefined) {
574
- console.warn(`Could not find static market data for index: ${i}`);
575
- continue;
576
- }
577
802
  if (dynamicData == undefined) {
578
- console.warn(`Could not find dynamic market data for index: ${i}`);
579
- continue;
803
+ throw new Error(`Missing dynamic market data for ${market_address} during Market.getAll boot.`);
580
804
  }
581
805
  if (userData == undefined) {
582
- console.warn(`Could not find user market data for index: ${i}`);
583
- continue;
806
+ throw new Error(`Missing user market data for ${market_address} during Market.getAll boot.`);
584
807
  }
585
- const market = new Market(provider, signer, account, staticData, dynamicData, userData, deploy_data, oracle_manager, reader, setup);
586
- if (milestones[market.address] != undefined) {
587
- 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;
588
813
  }
589
- if (incentives[market.address] != undefined) {
590
- market.incentives = incentives[market.address];
814
+ const marketIncentives = incentivesByAddress.get(rewardKey);
815
+ if (marketIncentives != undefined) {
816
+ market.incentives = marketIncentives;
591
817
  }
592
818
  for (const token of market.tokens) {
593
- const lendOpp = merklLendOpps.find(o => o.identifier.toLowerCase() === token.address.toLowerCase());
594
- if (lendOpp != undefined) {
595
- 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;
596
823
  }
597
- const borrowOpp = merklBorrowOpps.find(o => o.identifier.toLowerCase() === token.address.toLowerCase());
598
- if (borrowOpp != undefined) {
599
- token.incentiveBorrowApy = new decimal_js_1.Decimal(borrowOpp.apr / 100);
824
+ const borrowApy = borrowOppApyByToken.get(tokenKey);
825
+ if (borrowApy != undefined) {
826
+ token.incentiveBorrowApy = borrowApy;
600
827
  }
601
- const api_yield = yields.find(y => y.symbol.toUpperCase() == token.asset.symbol.toUpperCase());
828
+ const api_yield = yieldIndex.get(token.asset.symbol.toUpperCase());
602
829
  if (api_yield != undefined) {
603
830
  token.nativeApy = new decimal_js_1.Decimal(api_yield.apy / 100);
604
831
  }