@evaafi/sdk 0.9.5 → 0.9.6

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 (33) hide show
  1. package/dist/api/math.d.ts +16 -3
  2. package/dist/api/math.js +356 -33
  3. package/dist/api/parser.js +34 -12
  4. package/dist/constants/assets/assetId.d.ts +2 -0
  5. package/dist/constants/assets/assetId.js +3 -0
  6. package/dist/constants/assets/mainnet.d.ts +2 -0
  7. package/dist/constants/assets/mainnet.js +13 -1
  8. package/dist/constants/general/mainnet.d.ts +2 -0
  9. package/dist/constants/general/mainnet.js +4 -1
  10. package/dist/constants/pools/mainnet.d.ts +2 -0
  11. package/dist/constants/pools/mainnet.js +39 -1
  12. package/dist/constants/pools/testnet.d.ts +3 -2
  13. package/dist/constants/pools/testnet.js +16 -1
  14. package/dist/oracles/collectors/ClassicCollector.js +13 -18
  15. package/dist/oracles/collectors/FakeCollector.d.ts +30 -0
  16. package/dist/oracles/collectors/FakeCollector.js +128 -0
  17. package/dist/oracles/collectors/index.d.ts +1 -0
  18. package/dist/oracles/collectors/index.js +1 -0
  19. package/dist/types/Master.d.ts +8 -2
  20. package/dist/types/User.d.ts +7 -3
  21. package/package.json +1 -1
  22. package/src/api/math.ts +504 -40
  23. package/src/api/parser.ts +57 -12
  24. package/src/constants/assets/assetId.ts +4 -0
  25. package/src/constants/assets/mainnet.ts +24 -0
  26. package/src/constants/general/mainnet.ts +4 -0
  27. package/src/constants/pools/mainnet.ts +47 -1
  28. package/src/constants/pools/testnet.ts +30 -5
  29. package/src/oracles/collectors/ClassicCollector.ts +10 -14
  30. package/src/oracles/collectors/FakeCollector.ts +153 -0
  31. package/src/oracles/collectors/index.ts +1 -0
  32. package/src/types/Master.ts +9 -2
  33. package/src/types/User.ts +7 -3
package/src/api/parser.ts CHANGED
@@ -19,8 +19,11 @@ import {
19
19
  calculateLiquidationData,
20
20
  calculateMaximumWithdrawAmount,
21
21
  calculatePresentValue,
22
+ determineHeCategory,
23
+ exceedsStandardBorrowLimit,
22
24
  getAssetLiquidityMinusReserves,
23
25
  getAvailableToBorrow,
26
+ getAvailableToBorrowWithEMode,
24
27
  presentValue,
25
28
  } from './math';
26
29
  import { OracleParser } from './parsers/AbstractOracleParser';
@@ -130,8 +133,8 @@ export function createAssetConfig(): DictionaryValue<AssetConfig> {
130
133
  const baseTrackingBorrowSpeed = ref.loadUintBig(64);
131
134
  const borrowCap = ref.loadInt(64);
132
135
  const heCategory = ref.loadUint(8);
133
- const heCollateralFactor = ref.loadUint(16);
134
- const heLiquidationThreshold = ref.loadUint(16);
136
+ const heCollateralFactor = ref.loadUintBig(16);
137
+ const heLiquidationThreshold = ref.loadUintBig(16);
135
138
 
136
139
  return {
137
140
  jwAddress,
@@ -242,8 +245,8 @@ export function parseUserLiteData(
242
245
  const userSlice = Cell.fromBase64(userDataBOC).beginParse();
243
246
 
244
247
  const codeVersion = userSlice.loadCoins();
245
- const masterAddress = userSlice.loadAddress();
246
- const userAddress = userSlice.loadAddress();
248
+ const masterAddress = userSlice.loadAddressAny();
249
+ const userAddress = userSlice.loadAddressAny();
247
250
  const realPrincipals = userSlice.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.BigInt(64));
248
251
  const principalsDict = Dictionary.empty(Dictionary.Keys.BigUint(256), Dictionary.Values.BigInt(64));
249
252
  const userState = userSlice.loadInt(64);
@@ -256,18 +259,26 @@ export function parseUserLiteData(
256
259
  let backupCell1: Cell | null = null;
257
260
  let backupCell2: Cell | null = null;
258
261
  const bitsLeft = userSlice.remainingBits;
259
- if (bitsLeft > 32) {
262
+ const refsLeft = userSlice.remainingRefs;
263
+ if (bitsLeft === 0 && refsLeft === 0) {
264
+ // Init format: no extra data after state
265
+ } else if (bitsLeft >= 64 + 64 + 32 && refsLeft >= 1) {
266
+ // Old format with tracking indexes
260
267
  trackingSupplyIndex = userSlice.loadUintBig(64);
261
268
  trackingBorrowIndex = userSlice.loadUintBig(64);
262
269
  dutchAuctionStart = userSlice.loadUint(32);
263
270
  backupCell = loadMyRef(userSlice);
264
- } else {
271
+ } else if (bitsLeft >= 3 && refsLeft >= 1) {
272
+ // New format with rewards dict + maybe_refs
265
273
  rewards = userSlice.loadDict(Dictionary.Keys.BigUint(256), createUserRewards());
266
- backupCell1 = userSlice.loadMaybeRef();
267
- backupCell2 = userSlice.loadMaybeRef();
274
+ if (userSlice.remainingBits >= 2) {
275
+ backupCell1 = userSlice.loadMaybeRef();
276
+ backupCell2 = userSlice.loadMaybeRef();
277
+ }
268
278
  }
269
279
 
270
- userSlice.endParse();
280
+ // Skip remaining data if any (for forward compatibility)
281
+ // userSlice.endParse();
271
282
  const userBalances = Dictionary.empty<bigint, UserBalance>();
272
283
 
273
284
  for (const [_, asset] of Object.entries(poolAssetsConfig)) {
@@ -327,6 +338,7 @@ export function parseUserData(
327
338
 
328
339
  const withdrawalLimits = Dictionary.empty<bigint, bigint>();
329
340
  const borrowLimits = Dictionary.empty<bigint, bigint>();
341
+ const borrowLimitsWithEmode = Dictionary.empty<bigint, bigint>();
330
342
 
331
343
  let supplyBalance = 0n;
332
344
  let borrowBalance = 0n;
@@ -379,6 +391,19 @@ export function parseUserData(
379
391
  prices,
380
392
  masterConstants,
381
393
  );
394
+ const { availableToBorrow: availableToBorrowWithEmode, heCategory: predictedHeCategory } =
395
+ getAvailableToBorrowWithEMode(
396
+ assetsConfig,
397
+ assetsData,
398
+ userLiteData.realPrincipals,
399
+ prices,
400
+ masterConstants,
401
+ poolConfig,
402
+ );
403
+ let activeHeCategory = determineHeCategory(assetsConfig, userLiteData.realPrincipals, poolConfig);
404
+ if (activeHeCategory > 0 && !exceedsStandardBorrowLimit(userLiteData.realPrincipals, assetsConfig, assetsData, prices, masterConstants)) {
405
+ activeHeCategory = -1;
406
+ }
382
407
 
383
408
  for (const [_, asset] of Object.entries(poolAssetsConfig)) {
384
409
  const balance = userLiteData.balances.get(asset.assetId) as UserBalance;
@@ -396,7 +421,7 @@ export function parseUserData(
396
421
  assetsData,
397
422
  userLiteData.realPrincipals,
398
423
  prices,
399
- masterConstants,
424
+ poolConfig,
400
425
  asset.assetId,
401
426
  ),
402
427
  assetData.balance,
@@ -406,6 +431,7 @@ export function parseUserData(
406
431
 
407
432
  if (!prices.has(asset.assetId)) {
408
433
  borrowLimits.set(asset.assetId, 0n);
434
+ borrowLimitsWithEmode.set(asset.assetId, 0n);
409
435
  continue;
410
436
  }
411
437
 
@@ -419,13 +445,27 @@ export function parseUserData(
419
445
  ),
420
446
  ),
421
447
  );
448
+
449
+ borrowLimitsWithEmode.set(
450
+ asset.assetId,
451
+ bigIntMax(
452
+ 0n,
453
+ bigIntMin(
454
+ (availableToBorrowWithEmode * 10n ** assetConfig.decimals) / prices.get(asset.assetId)!,
455
+ assetLiquidityMinusReserves,
456
+ ),
457
+ ),
458
+ );
422
459
  }
423
460
 
424
- const limitUsed = borrowBalance + availableToBorrow;
461
+ const limitUsed = borrowBalance + availableToBorrowWithEmode;
425
462
  const limitUsedPercent =
426
463
  limitUsed === 0n
427
464
  ? 0
428
- : Number(BigInt(1e9) - (availableToBorrow * BigInt(1e9)) / (borrowBalance + availableToBorrow)) / 1e7;
465
+ : Number(
466
+ BigInt(1e9) -
467
+ (availableToBorrowWithEmode * BigInt(1e9)) / (borrowBalance + availableToBorrowWithEmode),
468
+ ) / 1e7;
429
469
 
430
470
  let healthFactor = 1;
431
471
  let liquidationData;
@@ -441,17 +481,22 @@ export function parseUserData(
441
481
  healthFactor = 1 - Number(liquidationData.totalDebt) / Number(liquidationData.totalLimit);
442
482
  }
443
483
  }
484
+
444
485
  return {
445
486
  ...userLiteData,
446
487
  withdrawalLimits: withdrawalLimits,
447
488
  borrowLimits: borrowLimits,
489
+ borrowLimitsWithEmode: borrowLimitsWithEmode,
448
490
  supplyBalance: supplyBalance,
449
491
  borrowBalance: borrowBalance,
450
492
  availableToBorrow: availableToBorrow,
493
+ availableToBorrowWithEmode: availableToBorrowWithEmode,
494
+ predictedHeCategory: predictedHeCategory,
451
495
  limitUsedPercent: limitUsedPercent,
452
496
  limitUsed: limitUsed,
453
497
  liquidationData: liquidationData,
454
498
  healthFactor: healthFactor,
455
499
  havePrincipalWithoutPrice: havePrincipalWithoutPrice,
500
+ activeHeCategory: activeHeCategory,
456
501
  };
457
502
  }
@@ -30,6 +30,10 @@ export const ASSET_ID = {
30
30
  PT_tsUSDe_01Sep2025: sha256Hash('PT_tsUSDe_01Sep2025'),
31
31
  PT_tsUSDe_18Dec2025: sha256Hash('PT_tsUSDe_18Dec2025'),
32
32
 
33
+ // Mainnet test assets
34
+ TUSDT: sha256Hash('TUSDT'),
35
+ TUSDe: sha256Hash('TUSDe'),
36
+
33
37
  // Testnet assets, faucet t.me/evaabuidl
34
38
  EUSDT: sha256Hash('EUSDT'),
35
39
  EUSDC: sha256Hash('EUSDC'),
@@ -216,3 +216,27 @@ export const EVAA_MAINNET: PoolAssetConfig = {
216
216
  ),
217
217
  )[0],
218
218
  };
219
+
220
+ export const TUSDT_MAINNET: PoolAssetConfig = {
221
+ name: 'TUSDT',
222
+ assetId: ASSET_ID.TUSDT,
223
+ jettonMasterAddress: Address.parse('EQCA1saJX4oYdG38m1__Of6ym2GnKSGVuccAy6UU-ESeXjvb'),
224
+ jettonWalletCode: Cell.fromBoc(
225
+ Buffer.from(
226
+ 'b5ee9c7201021101000323000114ff00f4a413f4bcf2c80b0102016202030202cc0405001ba0f605da89a1f401f481f481a8610201d40607020120080900c30831c02497c138007434c0c05c6c2544d7c0fc03383e903e900c7e800c5c75c87e800c7e800c1cea6d0000b4c7e08403e29fa954882ea54c4d167c0278208405e3514654882ea58c511100fc02b80d60841657c1ef2ea4d67c02f817c12103fcbc2000113e910c1c2ebcb853600201200a0b0083d40106b90f6a2687d007d207d206a1802698fc1080bc6a28ca9105d41083deecbef09dd0958f97162e99f98fd001809d02811e428027d012c678b00e78b6664f6aa401f1503d33ffa00fa4021f001ed44d0fa00fa40fa40d4305136a1522ac705f2e2c128c2fff2e2c254344270542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c920f9007074c8cb02ca07cbffc9d004fa40f40431fa0020d749c200f2e2c4778018c8cb055008cf1670fa0217cb6b13cc80c0201200d0e009e8210178d4519c8cb1f19cb3f5007fa0222cf165006cf1625fa025003cf16c95005cc2391729171e25008a813a08209c9c380a014bcf2e2c504c98040fb001023c85004fa0258cf1601cf16ccc9ed5402f73b51343e803e903e90350c0234cffe80145468017e903e9014d6f1c1551cdb5c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c0327e401c1d3232c0b281f2fff274140371c1472c7cb8b0c2be80146a2860822625a019ad822860822625a028062849e5c412440e0dd7c138c34975c2c0600f1000d73b51343e803e903e90350c01f4cffe803e900c145468549271c17cb8b049f0bffcb8b08160824c4b402805af3cb8b0e0841ef765f7b232c7c572cfd400fe8088b3c58073c5b25c60063232c14933c59c3e80b2dab33260103ec01004f214013e809633c58073c5b3327b552000705279a018a182107362d09cc8cb1f5230cb3f58fa025007cf165007cf16c9718010c8cb0524cf165006fa0215cb6a14ccc971fb0010241023007cc30023c200b08e218210d53276db708010c8cb055008cf165004fa0216cb6a12cb1f12cb3fc972fb0093356c21e203c85004fa0258cf1601cf16ccc9ed54',
227
+ 'hex',
228
+ ),
229
+ )[0],
230
+ };
231
+
232
+ export const TUSDE_MAINNET: PoolAssetConfig = {
233
+ name: 'TUSDe',
234
+ assetId: ASSET_ID.TUSDe,
235
+ jettonMasterAddress: Address.parse('EQC2cewpgyIzP9o1gTSGsQceuY1nt3bjpilek7ARKyhWtQKk'),
236
+ jettonWalletCode: Cell.fromBoc(
237
+ Buffer.from(
238
+ 'b5ee9c7201021101000323000114ff00f4a413f4bcf2c80b0102016202030202cc0405001ba0f605da89a1f401f481f481a8610201d40607020120080900c30831c02497c138007434c0c05c6c2544d7c0fc03383e903e900c7e800c5c75c87e800c7e800c1cea6d0000b4c7e08403e29fa954882ea54c4d167c0278208405e3514654882ea58c511100fc02b80d60841657c1ef2ea4d67c02f817c12103fcbc2000113e910c1c2ebcb853600201200a0b0083d40106b90f6a2687d007d207d206a1802698fc1080bc6a28ca9105d41083deecbef09dd0958f97162e99f98fd001809d02811e428027d012c678b00e78b6664f6aa401f1503d33ffa00fa4021f001ed44d0fa00fa40fa40d4305136a1522ac705f2e2c128c2fff2e2c254344270542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c920f9007074c8cb02ca07cbffc9d004fa40f40431fa0020d749c200f2e2c4778018c8cb055008cf1670fa0217cb6b13cc80c0201200d0e009e8210178d4519c8cb1f19cb3f5007fa0222cf165006cf1625fa025003cf16c95005cc2391729171e25008a813a08209c9c380a014bcf2e2c504c98040fb001023c85004fa0258cf1601cf16ccc9ed5402f73b51343e803e903e90350c0234cffe80145468017e903e9014d6f1c1551cdb5c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c0327e401c1d3232c0b281f2fff274140371c1472c7cb8b0c2be80146a2860822625a019ad822860822625a028062849e5c412440e0dd7c138c34975c2c0600f1000d73b51343e803e903e90350c01f4cffe803e900c145468549271c17cb8b049f0bffcb8b08160824c4b402805af3cb8b0e0841ef765f7b232c7c572cfd400fe8088b3c58073c5b25c60063232c14933c59c3e80b2dab33260103ec01004f214013e809633c58073c5b3327b552000705279a018a182107362d09cc8cb1f5230cb3f58fa025007cf165007cf16c9718010c8cb0524cf165006fa0215cb6a14ccc971fb0010241023007cc30023c200b08e218210d53276db708010c8cb055008cf165004fa0216cb6a12cb1f12cb3fc972fb0093356c21e203c85004fa0258cf1601cf16ccc9ed54',
239
+ 'hex',
240
+ ),
241
+ )[0],
242
+ };
@@ -9,6 +9,10 @@ export const MAINNET_VERSION = 10;
9
9
  export const EVAA_LP_MAINNET = Address.parse('EQBIlZX2URWkXCSg3QF2MJZU-wC5XkBoLww-hdWk2G37Jc6N');
10
10
  export const EVAA_LP_MAINNET_VERSION = 6;
11
11
 
12
+ /* LP POOL COPY */
13
+ export const EVAA_LP_COPY_MAINNET = Address.parse('EQA8H9nNHly7yJ32UmUDzH97Je2l8bAI6P74yKIIwPwKKXrz');
14
+ export const EVAA_LP_COPY_MAINNET_VERSION = 0;
15
+
12
16
  /* ALTS POOL */
13
17
  export const EVAA_ALTS_MAINNET = Address.parse('EQANURVS3fhBO9bivig34iyJQi97FhMbpivo1aUEAS2GYSu-');
14
18
  export const EVAA_ALTS_MAINNET_VERSION = 4;
@@ -1,7 +1,7 @@
1
1
  import { HexString } from '@pythnetwork/hermes-client';
2
2
  import { Address, Dictionary } from '@ton/core';
3
3
  import { FEED_ID, FeedMapItem } from '../../api/feeds';
4
- import { ClassicCollector, DefaultPythPriceSourcesConfig, PythCollector } from '../../oracles';
4
+ import { ClassicCollector, DefaultPythPriceSourcesConfig, FakeCollector, PythCollector } from '../../oracles';
5
5
  import { PoolConfig } from '../../types/Master';
6
6
  import { EvaaRewardsConfig } from '../../types/MasterRewards';
7
7
  import {
@@ -24,11 +24,15 @@ import {
24
24
  USDE_MAINNET,
25
25
  USDT_MAINNET,
26
26
  USDT_STORM_MAINNET,
27
+ TUSDE_MAINNET,
28
+ TUSDT_MAINNET,
27
29
  } from '../assets';
28
30
  import {
29
31
  EVAA_ALTS_MAINNET,
30
32
  EVAA_ALTS_MAINNET_VERSION,
31
33
  EVAA_EVAA_REWARDS_MASTER_MAINNET,
34
+ EVAA_LP_COPY_MAINNET,
35
+ EVAA_LP_COPY_MAINNET_VERSION,
32
36
  EVAA_LP_MAINNET,
33
37
  EVAA_LP_MAINNET_VERSION,
34
38
  EVAA_MASTER_MAINNET,
@@ -105,6 +109,7 @@ export const MAINNET_POOL_CONFIG: PoolConfig = {
105
109
  },
106
110
  }),
107
111
  poolAssetsConfig: MAINNET_POOL_ASSETS_CONFIG,
112
+ poolAssetsHEConfig: [],
108
113
  lendingCode: LENDING_CODE,
109
114
  };
110
115
 
@@ -118,6 +123,7 @@ export const MAINNET_STABLE_POOL_CONFIG: PoolConfig = {
118
123
  evaaOracles: ORACLES_MAINNET,
119
124
  }),
120
125
  poolAssetsConfig: MAINNET_STABLE_POOL_ASSETS_CONFIG,
126
+ poolAssetsHEConfig: [],
121
127
  lendingCode: LENDING_CODE,
122
128
  };
123
129
 
@@ -131,6 +137,24 @@ export const MAINNET_LP_POOL_CONFIG: PoolConfig = {
131
137
  evaaOracles: ORACLES_LP,
132
138
  }),
133
139
  poolAssetsConfig: MAINNET_LP_POOL_ASSETS_CONFIG,
140
+ poolAssetsHEConfig: [],
141
+ lendingCode: LENDING_CODE,
142
+ };
143
+
144
+ export const MAINNET_LP_POOL_COPY_CONFIG: PoolConfig = {
145
+ masterAddress: EVAA_LP_COPY_MAINNET,
146
+ masterVersion: EVAA_LP_COPY_MAINNET_VERSION,
147
+ masterConstants: MASTER_CONSTANTS,
148
+ collector: new ClassicCollector({
149
+ poolAssetsConfig: MAINNET_LP_POOL_ASSETS_CONFIG,
150
+ minimalOracles: 3,
151
+ evaaOracles: ORACLES_LP,
152
+ }),
153
+ poolAssetsConfig: MAINNET_LP_POOL_ASSETS_CONFIG,
154
+ poolAssetsHEConfig: [
155
+ { title: 'ton', assets: [TON_MAINNET, TON_STORM_MAINNET], heCategory: 1 },
156
+ { title: 'stable', assets: [USDT_MAINNET, USDT_STORM_MAINNET], heCategory: 2 },
157
+ ],
134
158
  lendingCode: LENDING_CODE,
135
159
  };
136
160
 
@@ -144,6 +168,7 @@ export const MAINNET_ALTS_POOL_CONFIG: PoolConfig = {
144
168
  evaaOracles: ORACLES_ALTS,
145
169
  }),
146
170
  poolAssetsConfig: MAINNET_ALTS_POOL_ASSETS_CONFIG,
171
+ poolAssetsHEConfig: [],
147
172
  lendingCode: LENDING_CODE,
148
173
  };
149
174
 
@@ -166,6 +191,7 @@ export const MAINNET_PYTH_V8_TOB_POOL_CONFIG: PoolConfig = {
166
191
  },
167
192
  }),
168
193
  poolAssetsConfig: MAINNET_PYTH_V8_TOB_POOL_ASSETS_CONFIG,
194
+ poolAssetsHEConfig: [],
169
195
  lendingCode: LENDING_CODE,
170
196
  };
171
197
 
@@ -181,6 +207,7 @@ export const MAINNET_V8_TOB_POOL_CONFIG: PoolConfig = {
181
207
  evaaOracles: ORACLES_MAINNET,
182
208
  }),
183
209
  poolAssetsConfig: MAINNET_V8_TOB_POOL_ASSETS_CONFIG,
210
+ poolAssetsHEConfig: [],
184
211
  lendingCode: LENDING_CODE,
185
212
  };
186
213
 
@@ -214,6 +241,25 @@ export const MAINNET_MASTER_EVAA_REWARD_CONFIG: EvaaRewardsConfig = {
214
241
  publicKey: Buffer.from('b8eb0e312a9aa6394edceef21573f2d45e7d7a616a924e33190bd52fa31c8bb1', 'hex'), // adminAddress publicKey
215
242
  };
216
243
 
244
+ export const MAINNET_CLASSIC_HE_POOL_CONFIG: PoolConfig = {
245
+ masterAddress: Address.parse('EQAI_O_VqUhqFQ-AW969xl1eXOi4euSWl1puxEroekJ5xpOt'),
246
+ masterVersion: 0,
247
+ masterConstants: MASTER_CONSTANTS,
248
+ collector: new FakeCollector(
249
+ Dictionary.empty<bigint, bigint>()
250
+ .set(ASSET_ID.TON, 1_300_000_000n)
251
+ .set(ASSET_ID.tsTON, 1_500_000_000n)
252
+ .set(ASSET_ID.TUSDT, 1_000_000_000n)
253
+ .set(ASSET_ID.TUSDe, 1_000_000_000n),
254
+ ),
255
+ poolAssetsConfig: [TON_MAINNET, TSTON_MAINNET, TUSDE_MAINNET, TUSDT_MAINNET],
256
+ poolAssetsHEConfig: [
257
+ { title: 'ton', assets: [TON_MAINNET, TSTON_MAINNET], heCategory: 1 },
258
+ { title: 'stable', assets: [TUSDT_MAINNET, TUSDE_MAINNET], heCategory: 2 },
259
+ ],
260
+ lendingCode: LENDING_CODE,
261
+ };
262
+
217
263
  export const ALL_MAINNET_POOLS: PoolConfig[] = [
218
264
  MAINNET_POOL_CONFIG,
219
265
  MAINNET_LP_POOL_CONFIG,
@@ -1,10 +1,19 @@
1
- import { HexString } from '@pythnetwork/hermes-client';
1
+ import type { HexString } from '@pythnetwork/hermes-client';
2
+ import type { PoolConfig } from '../../types/Master';
3
+ import type { EvaaRewardsConfig } from '../../types/MasterRewards';
2
4
  import { Address, Dictionary } from '@ton/core';
3
- import { FEED_ID, FeedMapItem } from '../../api/feeds';
5
+ import { FEED_ID, type FeedMapItem } from '../../api/feeds';
4
6
  import { ClassicCollector, DefaultPythPriceSourcesConfig, PythCollector } from '../../oracles';
5
- import { PoolConfig } from '../../types/Master';
6
- import { EvaaRewardsConfig } from '../../types/MasterRewards';
7
- import { ASSET_ID, EUSDT_TESTNET, JUSDC_TESTNET, TON_TESTNET } from '../assets';
7
+ import {
8
+ ASSET_ID,
9
+ EUSDT_TESTNET,
10
+ JUSDC_TESTNET,
11
+ TON_MAINNET,
12
+ TON_TESTNET,
13
+ TSTON_MAINNET,
14
+ USDE_MAINNET,
15
+ USDT_MAINNET,
16
+ } from '../assets';
8
17
  import {
9
18
  EVAA_MASTER_TESTNET_CLASSIC_TOB_AUDITED,
10
19
  EVAA_MASTER_TESTNET_PYTH_TOB_AUDITED,
@@ -45,6 +54,7 @@ export const TESTNET_PYTH_POOL_CONFIG_TOB_AUDITED: PoolConfig = {
45
54
  pythConfig: DefaultPythPriceSourcesConfig,
46
55
  }),
47
56
  poolAssetsConfig: TESTNET_POOL_ASSETS_CONFIG_TOB_AUDITED,
57
+ poolAssetsHEConfig: [],
48
58
  lendingCode: LENDING_CODE,
49
59
  };
50
60
 
@@ -59,6 +69,21 @@ export const TESTNET_CLASSIC_POOL_CONFIG_TOB_AUDITED: PoolConfig = {
59
69
  }),
60
70
  lendingCode: LENDING_CODE,
61
71
  poolAssetsConfig: TESTNET_POOL_ASSETS_CONFIG,
72
+ poolAssetsHEConfig: [],
73
+ };
74
+
75
+ export const TESTNET_CLASSIC_HE_POOL_CONFIG: PoolConfig = {
76
+ masterAddress: Address.parse('EQBykKj3k97Mx_EEGwzmnqFLteFRAt0BIg-ig96ifNkdV3Wn'),
77
+ masterVersion: 0,
78
+ masterConstants: MASTER_CONSTANTS,
79
+ collector: new ClassicCollector({
80
+ poolAssetsConfig: [TON_MAINNET, TSTON_MAINNET, USDT_MAINNET, USDE_MAINNET],
81
+ minimalOracles: 1,
82
+ evaaOracles: ORACLES_TESTNET,
83
+ }),
84
+ lendingCode: LENDING_CODE,
85
+ poolAssetsConfig: [TON_MAINNET, TSTON_MAINNET, USDT_MAINNET, USDE_MAINNET],
86
+ poolAssetsHEConfig: [],
62
87
  };
63
88
 
64
89
  export const TESTNET_MASTER_REWARD_CONFIG: EvaaRewardsConfig = {
@@ -1,7 +1,7 @@
1
1
  import { Dictionary } from '@ton/core';
2
2
  import { checkNotInDebtAtAll } from '../../api/math';
3
3
  import { ExtendedEvaaOracle, PoolAssetConfig } from '../../types/Master';
4
- import { FetchConfig, proxyFetchRetries } from '../../utils/utils';
4
+ import { FetchConfig } from '../../utils/utils';
5
5
  import { ClassicPrices, ClassicPricesMode, ClassicPricesOffset } from '../prices/ClassicPrices';
6
6
  import { PriceSource } from '../sources';
7
7
  import { DefaultPriceSourcesConfig, PriceSourcesConfig, RawPriceData } from '../Types';
@@ -181,20 +181,16 @@ export class ClassicCollector extends AbstractCollector {
181
181
  }
182
182
 
183
183
  async #collectPrices(fetchConfig?: FetchConfig): Promise<boolean> {
184
- for (const priceSource of this.#priceSources) {
185
- try {
186
- this.#prices = await proxyFetchRetries(
187
- () => collectAndFilterPrices(priceSource, this.#minimalOracles, fetchConfig),
188
- fetchConfig,
189
- );
190
- return true;
191
- } catch (error) {
192
- // Try next source
193
- continue;
194
- }
184
+ try {
185
+ this.#prices = await Promise.any(
186
+ this.#priceSources.map((source) =>
187
+ collectAndFilterPrices(source, this.#minimalOracles, fetchConfig),
188
+ ),
189
+ );
190
+ return true;
191
+ } catch {
192
+ return false;
195
193
  }
196
-
197
- return false;
198
194
  }
199
195
 
200
196
  async #collectPricesWithValidation(fetchConfig?: FetchConfig): Promise<void> {
@@ -0,0 +1,153 @@
1
+ import { beginCell, Cell, Dictionary } from '@ton/core';
2
+ import { ClassicPrices, ClassicPricesMode } from '../prices/ClassicPrices';
3
+ import { PoolAssetConfig } from '../../types/Master';
4
+ import { FetchConfig } from '../../utils/utils';
5
+ import { AbstractCollector } from './AbstractCollector';
6
+ import { packAssetsData, packPrices, packOraclesData } from '../utils';
7
+ import { keyPairFromSeed, sign } from '@ton/crypto';
8
+
9
+ function createFakeOracle(id: number, seed: Buffer) {
10
+ const keypair = keyPairFromSeed(seed);
11
+ return {
12
+ id,
13
+ pubkey: keypair.publicKey,
14
+ secret: keypair.secretKey,
15
+ };
16
+ }
17
+
18
+ const fakeOracles = [
19
+ createFakeOracle(0, Buffer.alloc(32, 0)),
20
+ createFakeOracle(1, Buffer.alloc(32, 1)),
21
+ createFakeOracle(2, Buffer.alloc(32, 2)),
22
+ createFakeOracle(3, Buffer.alloc(32, 3)),
23
+ ];
24
+
25
+ function packPricesData(timestamp: number, prices: Dictionary<bigint, bigint>): Cell {
26
+ return beginCell()
27
+ .storeUint(timestamp, 32)
28
+ .storeDict(prices, Dictionary.Keys.BigUint(256), Dictionary.Values.BigVarUint(4))
29
+ .endCell();
30
+ }
31
+
32
+ function signPricesData(
33
+ oracle: { secret: Buffer },
34
+ data: { timestamp: number; prices: Dictionary<bigint, bigint> },
35
+ ): Buffer {
36
+ const packedData = packPricesData(data.timestamp, data.prices);
37
+ return sign(packedData.hash(), oracle.secret);
38
+ }
39
+
40
+ function withSerializers(src: Dictionary<bigint, bigint>): Dictionary<bigint, bigint> {
41
+ const dict = Dictionary.empty(Dictionary.Keys.BigUint(256), Dictionary.Values.BigVarUint(4));
42
+ for (const key of src.keys()) {
43
+ dict.set(key, src.get(key)!);
44
+ }
45
+ return dict;
46
+ }
47
+
48
+ function buildPricesCell(
49
+ pricesDict: Dictionary<bigint, bigint>,
50
+ assetIds: bigint[],
51
+ timestamp: number,
52
+ ): { dict: Dictionary<bigint, bigint>; dataCell: Cell } {
53
+ const serializedPrices = withSerializers(pricesDict);
54
+ const oraclesData = fakeOracles.map((oracle) => {
55
+ const data = { timestamp, prices: serializedPrices };
56
+ const signature = signPricesData(oracle, data);
57
+ return {
58
+ oracle: { id: oracle.id, pubkey: oracle.pubkey },
59
+ data,
60
+ signature,
61
+ };
62
+ });
63
+
64
+ const medianData = assetIds.map((assetId) => ({
65
+ assetId,
66
+ medianPrice: pricesDict.get(assetId)!,
67
+ }));
68
+
69
+ const assetsDataCell = packAssetsData(medianData);
70
+ const oraclesDataCell = packOraclesData(oraclesData, assetIds);
71
+ const dataCell = packPrices(assetsDataCell, oraclesDataCell);
72
+
73
+ const dict = Dictionary.empty<bigint, bigint>();
74
+ for (const { assetId, medianPrice } of medianData) {
75
+ dict.set(assetId, medianPrice);
76
+ }
77
+
78
+ return { dict, dataCell };
79
+ }
80
+
81
+ /**
82
+ * FakeCollector for testing smart contracts.
83
+ *
84
+ * Accepts a Dictionary<bigint, bigint> mapping assetId to price
85
+ * (in ASSET_PRICE_SCALE, i.e. USD price * 1e9) and produces valid
86
+ * ClassicPrices without any network calls.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const prices = Dictionary.empty<bigint, bigint>();
91
+ * prices.set(TON_ASSET_ID, 5_000_000_000n); // $5.00
92
+ * prices.set(USDT_ASSET_ID, 1_000_000_000n); // $1.00
93
+ *
94
+ * const collector = new FakeCollector(prices);
95
+ * const classicPrices = await collector.getPrices();
96
+ * ```
97
+ */
98
+ export class FakeCollector extends AbstractCollector {
99
+ readonly #pricesDict: Dictionary<bigint, bigint>;
100
+
101
+ constructor(prices: Dictionary<bigint, bigint>) {
102
+ super();
103
+ this.#pricesDict = prices;
104
+ }
105
+
106
+ async getPricesForLiquidate(
107
+ _realPrincipals: Dictionary<bigint, bigint>,
108
+ _fetchConfig?: FetchConfig,
109
+ ): Promise<ClassicPrices> {
110
+ return this.#buildClassicPrices(ClassicPricesMode.SPOT);
111
+ }
112
+
113
+ async getPricesForWithdraw(
114
+ realPrincipals: Dictionary<bigint, bigint>,
115
+ withdrawAsset: PoolAssetConfig,
116
+ collateralToDebt = false,
117
+ fetchConfig?: FetchConfig,
118
+ ): Promise<ClassicPrices> {
119
+ return await this.getPrices();
120
+ }
121
+
122
+ async getPricesForSupplyWithdraw(
123
+ _realPrincipals: Dictionary<bigint, bigint>,
124
+ _supplyAsset: PoolAssetConfig | undefined,
125
+ _withdrawAsset: PoolAssetConfig | undefined,
126
+ _collateralToDebt: boolean,
127
+ _fetchConfig?: FetchConfig,
128
+ ): Promise<ClassicPrices> {
129
+ return this.#buildClassicPrices(ClassicPricesMode.TWAP);
130
+ }
131
+
132
+ async getPrices(_assets?: PoolAssetConfig[], _fetchConfig?: FetchConfig): Promise<ClassicPrices> {
133
+ return this.#buildClassicPrices();
134
+ }
135
+
136
+ #buildClassicPrices(mode?: ClassicPricesMode): ClassicPrices {
137
+ const keys = this.#pricesDict.keys();
138
+ if (keys.length === 0) {
139
+ return ClassicPrices.createEmptyPrices();
140
+ }
141
+
142
+ const timestamp = Math.floor(Date.now() / 1000);
143
+ const { dict, dataCell } = buildPricesCell(this.#pricesDict, keys, timestamp);
144
+
145
+ return new ClassicPrices({
146
+ mode,
147
+ dict,
148
+ dataCell,
149
+ minPublishTime: undefined,
150
+ maxPublishTime: undefined,
151
+ });
152
+ }
153
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './AbstractCollector';
2
2
  export * from './ClassicCollector';
3
+ export * from './FakeCollector';
3
4
  export * from './PythCollector';
@@ -23,11 +23,18 @@ export type PoolAssetConfig = {
23
23
  jettonWalletCode: Cell;
24
24
  };
25
25
 
26
+ export type PoolAssetHEConfig = {
27
+ title: string;
28
+ assets: PoolAssetConfig[];
29
+ heCategory: number;
30
+ };
31
+
26
32
  export type PoolConfig = {
27
33
  masterAddress: Address;
28
34
  masterVersion: number;
29
35
  masterConstants: MasterConstants;
30
36
  poolAssetsConfig: PoolAssetConfig[];
37
+ poolAssetsHEConfig: PoolAssetHEConfig[];
31
38
  lendingCode: Cell;
32
39
  collector: AbstractCollector;
33
40
  };
@@ -66,8 +73,8 @@ export type AssetConfig = {
66
73
  baseTrackingBorrowSpeed: bigint;
67
74
  borrowCap: number | bigint;
68
75
  heCategory: number;
69
- heCollateralFactor: number;
70
- heLiquidationThreshold: number;
76
+ heCollateralFactor: bigint;
77
+ heLiquidationThreshold: bigint;
71
78
  };
72
79
 
73
80
  export type AssetData = {
package/src/types/User.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Address, Cell, Dictionary } from '@ton/core';
1
+ import { Address, Cell, Dictionary, ExternalAddress } from '@ton/core';
2
2
  import {
3
3
  AssetConfig,
4
4
  AssetData,
@@ -43,8 +43,8 @@ export type LiquidationData = LiquidableData | NonLiquidableData;
43
43
  export type UserLiteData = {
44
44
  type: 'active';
45
45
  codeVersion: number;
46
- masterAddress: Address;
47
- ownerAddress: Address;
46
+ masterAddress: Address | ExternalAddress | null;
47
+ ownerAddress: Address | ExternalAddress | null;
48
48
  principals: Dictionary<bigint, bigint>;
49
49
  realPrincipals: Dictionary<bigint, bigint>; // principals before applying dusts
50
50
  state: number;
@@ -62,15 +62,19 @@ export type UserLiteData = {
62
62
  export type UserDataActive = UserLiteData & {
63
63
  withdrawalLimits: Dictionary<bigint, bigint>;
64
64
  borrowLimits: Dictionary<bigint, bigint>;
65
+ borrowLimitsWithEmode: Dictionary<bigint, bigint>;
65
66
  repayLimits?: Dictionary<bigint, bigint>;
66
67
  supplyBalance: bigint;
67
68
  borrowBalance: bigint;
68
69
  availableToBorrow: bigint;
70
+ availableToBorrowWithEmode: bigint;
69
71
  limitUsedPercent: number;
70
72
  limitUsed: bigint;
71
73
  healthFactor: number;
72
74
  liquidationData: LiquidationData;
73
75
  havePrincipalWithoutPrice: boolean;
76
+ predictedHeCategory: number;
77
+ activeHeCategory: number;
74
78
  };
75
79
 
76
80
  export type UserDataInactive = {