@gearbox-protocol/sdk 14.1.1 → 14.2.1

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 (40) hide show
  1. package/dist/cjs/dev/claimDSToken.js +208 -0
  2. package/dist/cjs/plugins/accounts/AccountsPlugin.js +1 -2
  3. package/dist/cjs/sdk/OnchainSDK.js +12 -0
  4. package/dist/cjs/sdk/accounts/CreditAccountsServiceV310.js +1448 -23
  5. package/dist/cjs/sdk/accounts/index.js +0 -4
  6. package/dist/cjs/sdk/market/credit/CreditFacadeV310Contract.js +6 -0
  7. package/dist/cjs/sdk/market/kyc/securitize/SecuritizeKYCFactory.js +19 -2
  8. package/dist/cjs/sdk/market/kyc/securitize/types.js +20 -0
  9. package/dist/cjs/sdk/market/oracle/PriceOracleV310Contract.js +26 -0
  10. package/dist/cjs/sdk/market/pricefeeds/PriceFeedsRegister.js +3 -0
  11. package/dist/esm/dev/claimDSToken.js +187 -0
  12. package/dist/esm/plugins/accounts/AccountsPlugin.js +2 -7
  13. package/dist/esm/sdk/OnchainSDK.js +14 -0
  14. package/dist/esm/sdk/accounts/CreditAccountsServiceV310.js +1466 -23
  15. package/dist/esm/sdk/accounts/index.js +0 -2
  16. package/dist/esm/sdk/accounts/multicall-utils.js +1 -5
  17. package/dist/esm/sdk/market/credit/CreditFacadeV310Contract.js +6 -0
  18. package/dist/esm/sdk/market/kyc/securitize/SecuritizeKYCFactory.js +21 -2
  19. package/dist/esm/sdk/market/kyc/securitize/types.js +12 -0
  20. package/dist/esm/sdk/market/oracle/PriceOracleV310Contract.js +26 -0
  21. package/dist/esm/sdk/market/pricefeeds/PriceFeedsRegister.js +3 -0
  22. package/dist/types/dev/claimDSToken.d.ts +34 -0
  23. package/dist/types/sdk/OnchainSDK.d.ts +10 -0
  24. package/dist/types/sdk/accounts/CreditAccountsServiceV310.d.ts +181 -3
  25. package/dist/types/sdk/accounts/index.d.ts +0 -2
  26. package/dist/types/sdk/accounts/types.d.ts +56 -0
  27. package/dist/types/sdk/market/credit/CreditFacadeV310Contract.d.ts +2 -0
  28. package/dist/types/sdk/market/kyc/securitize/SecuritizeKYCFactory.d.ts +1 -1
  29. package/dist/types/sdk/market/kyc/securitize/types.d.ts +37 -22
  30. package/dist/types/sdk/market/oracle/PriceOracleBaseContract.d.ts +5 -0
  31. package/dist/types/sdk/market/oracle/PriceOracleV310Contract.d.ts +4 -0
  32. package/dist/types/sdk/market/oracle/types.d.ts +9 -0
  33. package/dist/types/sdk/pools/types.d.ts +19 -0
  34. package/package.json +1 -1
  35. package/dist/cjs/sdk/accounts/AbstractCreditAccountsService.js +0 -1411
  36. package/dist/cjs/sdk/accounts/createCreditAccountService.js +0 -35
  37. package/dist/esm/sdk/accounts/AbstractCreditAccountsService.js +0 -1405
  38. package/dist/esm/sdk/accounts/createCreditAccountService.js +0 -11
  39. package/dist/types/sdk/accounts/AbstractCreditAccountsService.d.ts +0 -226
  40. package/dist/types/sdk/accounts/createCreditAccountService.d.ts +0 -9
@@ -1,8 +1,1119 @@
1
+ import { ierc4626AdapterAbi } from "@gearbox-protocol/integrations-v3";
1
2
  import { encodeFunctionData, getContract } from "viem";
2
- import { iCreditFacadeMulticallV310Abi } from "../../abi/310/generated.js";
3
- import { MAX_UINT256 } from "../constants/math.js";
4
- import { AbstractCreditAccountService } from "./AbstractCreditAccountsService.js";
5
- class CreditAccountServiceV310 extends AbstractCreditAccountService {
3
+ import {
4
+ iBotListV310Abi,
5
+ iCreditFacadeMulticallV310Abi
6
+ } from "../../abi/310/generated.js";
7
+ import { creditAccountCompressorAbi } from "../../abi/compressors/creditAccountCompressor.js";
8
+ import { peripheryCompressorAbi } from "../../abi/compressors/peripheryCompressor.js";
9
+ import { rewardsCompressorAbi } from "../../abi/compressors/rewardsCompressor.js";
10
+ import { iWithdrawalCompressorV310Abi } from "../../abi/IWithdrawalCompressorV310.js";
11
+ import { iBaseRewardPoolAbi } from "../../abi/iBaseRewardPool.js";
12
+ import { iKYCFactoryAbi } from "../../abi/kyc/iKYCFactory.js";
13
+ import { SDKConstruct } from "../base/index.js";
14
+ import { chains } from "../chain/chains.js";
15
+ import {
16
+ ADDRESS_0X0,
17
+ AP_CREDIT_ACCOUNT_COMPRESSOR,
18
+ AP_PERIPHERY_COMPRESSOR,
19
+ AP_REWARDS_COMPRESSOR,
20
+ MAX_UINT256,
21
+ MIN_INT96,
22
+ PERCENTAGE_FACTOR,
23
+ RAY,
24
+ VERSION_RANGE_310
25
+ } from "../constants/index.js";
26
+ import {
27
+ getRawPriceUpdates
28
+ } from "../market/index.js";
29
+ import { assetsMap } from "../router/index.js";
30
+ import { AddressMap, AddressSet, hexEq } from "../utils/index.js";
31
+ import { simulateWithPriceUpdates } from "../utils/viem/index.js";
32
+ import {
33
+ extractPriceUpdates,
34
+ extractQuotaTokens,
35
+ mergePriceUpdates
36
+ } from "./multicall-utils.js";
37
+ const COMPRESSORS = {
38
+ [chains.Mainnet.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023",
39
+ [chains.Monad.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023"
40
+ };
41
+ function getWithdrawalCompressorAddress(chainId) {
42
+ return COMPRESSORS[chainId];
43
+ }
44
+ class CreditAccountsServiceV310 extends SDKConstruct {
45
+ #compressor;
46
+ #batchSize;
47
+ constructor(sdk, options) {
48
+ super(sdk);
49
+ this.#batchSize = options?.batchSize;
50
+ }
51
+ setBatchSize(batchSize) {
52
+ this.#batchSize = batchSize;
53
+ }
54
+ /**
55
+ * {@inheritDoc ICreditAccountsService.getCreditAccountData}
56
+ **/
57
+ async getCreditAccountData(account, blockNumber) {
58
+ let raw;
59
+ try {
60
+ raw = await this.client.readContract({
61
+ abi: creditAccountCompressorAbi,
62
+ address: this.compressor,
63
+ functionName: "getCreditAccountData",
64
+ args: [account],
65
+ blockNumber,
66
+ // @ts-expect-error
67
+ gas: this.sdk.gasLimit
68
+ });
69
+ } catch (_e) {
70
+ return void 0;
71
+ }
72
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
73
+ raw.creditManager
74
+ );
75
+ const factory = marketSuite.kycFactory;
76
+ let ca;
77
+ let investor;
78
+ if (raw.success) {
79
+ ca = raw;
80
+ investor = await factory?.getInvestor(raw.creditAccount, false);
81
+ } else {
82
+ const { txs: priceUpdateTxs } = await this.#getUpdateForAccount(raw);
83
+ [ca, investor] = await simulateWithPriceUpdates(this.client, {
84
+ priceUpdates: priceUpdateTxs,
85
+ contracts: [
86
+ {
87
+ abi: creditAccountCompressorAbi,
88
+ address: this.compressor,
89
+ functionName: "getCreditAccountData",
90
+ args: [account]
91
+ },
92
+ ...factory ? [
93
+ {
94
+ abi: iKYCFactoryAbi,
95
+ address: factory.address,
96
+ functionName: "getInvestor",
97
+ args: [raw.creditAccount]
98
+ }
99
+ ] : []
100
+ ],
101
+ blockNumber,
102
+ gas: this.sdk.gasLimit
103
+ });
104
+ }
105
+ return { ...ca, investor };
106
+ }
107
+ /**
108
+ * {@inheritDoc ICreditAccountsService.getCreditAccounts}
109
+ **/
110
+ async getCreditAccounts(options, blockNumber) {
111
+ const {
112
+ creditManager,
113
+ includeZeroDebt = false,
114
+ maxHealthFactor = MAX_UINT256,
115
+ minHealthFactor = 0n,
116
+ owner = ADDRESS_0X0,
117
+ ignoreReservePrices = false
118
+ } = options ?? {};
119
+ const arg0 = creditManager ?? {
120
+ configurators: this.marketConfigurators,
121
+ creditManagers: [],
122
+ pools: [],
123
+ underlying: ADDRESS_0X0
124
+ };
125
+ const caFilter = {
126
+ owner,
127
+ includeZeroDebt,
128
+ minHealthFactor,
129
+ maxHealthFactor,
130
+ reverting: false
131
+ };
132
+ const { txs: priceUpdateTxs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
133
+ ignoreReservePrices ? { main: true } : void 0
134
+ );
135
+ const allCAs = [];
136
+ let revertingOffset = 0;
137
+ for (const reverting of [false, true]) {
138
+ let offset = 0n;
139
+ revertingOffset = allCAs.length;
140
+ do {
141
+ const [accounts, newOffset] = await this.#getCreditAccounts(
142
+ this.#batchSize ? [
143
+ arg0,
144
+ { ...caFilter, reverting },
145
+ offset,
146
+ BigInt(this.#batchSize)
147
+ // limit
148
+ ] : [arg0, { ...caFilter, reverting }, offset],
149
+ priceUpdateTxs,
150
+ blockNumber
151
+ );
152
+ allCAs.push(...accounts);
153
+ offset = newOffset;
154
+ } while (offset !== 0n);
155
+ }
156
+ this.logger?.debug(
157
+ `loaded ${allCAs.length} credit accounts (${allCAs.length - revertingOffset} reverting)`
158
+ );
159
+ return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
160
+ }
161
+ /**
162
+ * {@inheritDoc ICreditAccountsService.getBorrowerCreditAccounts}
163
+ **/
164
+ async getBorrowerCreditAccounts(borrower, options, blockNumber) {
165
+ const {
166
+ creditManager,
167
+ includeZeroDebt = false,
168
+ maxHealthFactor = MAX_UINT256,
169
+ minHealthFactor = 0n,
170
+ ignoreReservePrices = false
171
+ } = options ?? {};
172
+ const { txs: priceUpdateTxs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
173
+ ignoreReservePrices ? { main: true } : void 0
174
+ );
175
+ const investorDataList = await this.sdk.kyc.getInvestorData(borrower);
176
+ const kycAccountAddresses = investorDataList.flatMap(
177
+ (d) => d.creditAccounts.map((ca) => ca.creditAccount)
178
+ );
179
+ const cmFilter = creditManager ? {
180
+ configurators: [],
181
+ creditManagers: [creditManager],
182
+ pools: [],
183
+ underlying: ADDRESS_0X0
184
+ } : {
185
+ configurators: this.marketConfigurators,
186
+ creditManagers: [],
187
+ pools: [],
188
+ underlying: ADDRESS_0X0
189
+ };
190
+ const permissiveFilter = {
191
+ owner: borrower,
192
+ includeZeroDebt: true,
193
+ minHealthFactor: 0n,
194
+ maxHealthFactor: MAX_UINT256,
195
+ reverting: false
196
+ };
197
+ const kycContracts = kycAccountAddresses.map(
198
+ (account) => ({
199
+ abi: creditAccountCompressorAbi,
200
+ address: this.compressor,
201
+ functionName: "getCreditAccountData",
202
+ args: [account]
203
+ })
204
+ );
205
+ const getCreditAccountsContracts = [false, true].map(
206
+ (reverting) => ({
207
+ abi: creditAccountCompressorAbi,
208
+ address: this.compressor,
209
+ functionName: "getCreditAccounts",
210
+ args: [cmFilter, { ...permissiveFilter, reverting }, 0n]
211
+ })
212
+ );
213
+ const allContracts = [...kycContracts, ...getCreditAccountsContracts];
214
+ const results = await simulateWithPriceUpdates(this.client, {
215
+ priceUpdates: priceUpdateTxs,
216
+ contracts: allContracts,
217
+ blockNumber,
218
+ gas: this.sdk.gasLimit
219
+ });
220
+ const kycResults = results.slice(
221
+ 0,
222
+ kycAccountAddresses.length
223
+ );
224
+ const normalResults = results.slice(kycAccountAddresses.length);
225
+ const seen = new AddressSet();
226
+ const allCAs = [];
227
+ for (const ca of kycResults) {
228
+ if (!seen.has(ca.creditAccount)) {
229
+ seen.add(ca.creditAccount);
230
+ allCAs.push({ ...ca, investor: borrower });
231
+ }
232
+ }
233
+ for (const [accounts] of normalResults) {
234
+ for (const ca of accounts) {
235
+ if (!seen.has(ca.creditAccount)) {
236
+ seen.add(ca.creditAccount);
237
+ allCAs.push({ ...ca, investor: void 0 });
238
+ }
239
+ }
240
+ }
241
+ const filtered = allCAs.filter((ca) => {
242
+ if (!includeZeroDebt && ca.debt === 0n) return false;
243
+ if (ca.healthFactor < minHealthFactor) return false;
244
+ if (ca.healthFactor > maxHealthFactor) return false;
245
+ if (creditManager && !hexEq(ca.creditManager, creditManager))
246
+ return false;
247
+ return true;
248
+ });
249
+ this.logger?.debug(
250
+ `loaded ${allCAs.length} borrower credit accounts (${kycResults.length} KYC, ${filtered.length} after filter)`
251
+ );
252
+ return filtered.sort((a, b) => Number(a.healthFactor - b.healthFactor));
253
+ }
254
+ /**
255
+ * {@inheritDoc ICreditAccountsService.getRewards}
256
+ **/
257
+ async getRewards(creditAccount) {
258
+ const rewards = await this.client.readContract({
259
+ abi: rewardsCompressorAbi,
260
+ address: this.rewardCompressor,
261
+ functionName: "getRewards",
262
+ args: [creditAccount]
263
+ });
264
+ const callData = encodeFunctionData({
265
+ abi: iBaseRewardPoolAbi,
266
+ functionName: "getReward",
267
+ args: []
268
+ });
269
+ const r = rewards.reduce((acc, r2) => {
270
+ const adapter = r2.adapter.toLowerCase();
271
+ const stakedPhantomToken = r2.stakedPhantomToken.toLowerCase();
272
+ const rewardToken = r2.rewardToken.toLowerCase();
273
+ const key = [adapter, stakedPhantomToken].join("-");
274
+ if (!acc[key]) {
275
+ acc[key] = {
276
+ adapter,
277
+ stakedPhantomToken,
278
+ calls: [
279
+ {
280
+ target: adapter,
281
+ callData
282
+ }
283
+ ],
284
+ rewards: []
285
+ };
286
+ }
287
+ acc[key].rewards.push({
288
+ token: rewardToken,
289
+ balance: r2.amount
290
+ });
291
+ return acc;
292
+ }, {});
293
+ return Object.values(r);
294
+ }
295
+ /**
296
+ * {@inheritDoc ICreditAccountsService.getConnectedBots}
297
+ **/
298
+ async getConnectedBots(accountsToCheck, legacyMigrationBot, additionalBots) {
299
+ const allResp = await this.client.multicall({
300
+ contracts: [
301
+ ...accountsToCheck.map((o) => {
302
+ const pool = this.sdk.marketRegister.findByCreditManager(
303
+ o.creditManager
304
+ );
305
+ return {
306
+ abi: peripheryCompressorAbi,
307
+ address: this.peripheryCompressor,
308
+ functionName: "getConnectedBots",
309
+ args: [pool.configurator.address, o.creditAccount]
310
+ };
311
+ }),
312
+ ...legacyMigrationBot ? accountsToCheck.map((ca) => {
313
+ const cm = this.sdk.marketRegister.findCreditManager(
314
+ ca.creditManager
315
+ );
316
+ return {
317
+ abi: iBotListV310Abi,
318
+ address: cm.creditFacade.botList,
319
+ functionName: "getBotStatus",
320
+ args: [legacyMigrationBot, ca.creditAccount]
321
+ };
322
+ }) : [],
323
+ ...accountsToCheck.flatMap((ca) => {
324
+ const cm = this.sdk.marketRegister.findCreditManager(
325
+ ca.creditManager
326
+ );
327
+ return additionalBots.map((bot) => {
328
+ return {
329
+ abi: iBotListV310Abi,
330
+ address: cm.creditFacade.botList,
331
+ functionName: "getBotStatus",
332
+ args: [bot, ca.creditAccount]
333
+ };
334
+ });
335
+ })
336
+ ],
337
+ allowFailure: true,
338
+ batchSize: 0
339
+ });
340
+ const legacyStart = 0;
341
+ const legacyEnd = accountsToCheck.length;
342
+ const legacy = allResp.slice(
343
+ legacyStart,
344
+ legacyEnd
345
+ );
346
+ const migrationStart = legacyEnd;
347
+ const migrationEnd = legacyMigrationBot ? migrationStart + accountsToCheck.length : migrationStart;
348
+ const migrationResp = allResp.slice(
349
+ migrationStart,
350
+ migrationEnd
351
+ );
352
+ const additionalStart = migrationEnd;
353
+ const additionalResp = allResp.slice(
354
+ additionalStart
355
+ );
356
+ return {
357
+ legacy,
358
+ additionalBots: this.#getActiveBots(
359
+ accountsToCheck,
360
+ additionalBots,
361
+ additionalResp
362
+ ),
363
+ legacyMigration: this.#getActiveMigrationBots(
364
+ accountsToCheck,
365
+ legacyMigrationBot,
366
+ migrationResp
367
+ )
368
+ };
369
+ }
370
+ #getActiveBots(accountsToCheck, bots, result) {
371
+ if (result.length !== bots.length * accountsToCheck.length) {
372
+ console.error(
373
+ "result length mismatch",
374
+ result.length,
375
+ bots.length * accountsToCheck.length
376
+ );
377
+ }
378
+ const botsByCAIndex = accountsToCheck.reduce((acc, _, index) => {
379
+ const r = result.slice(index * bots.length, (index + 1) * bots.length);
380
+ acc.push({
381
+ result: r
382
+ });
383
+ return acc;
384
+ }, []);
385
+ return botsByCAIndex;
386
+ }
387
+ #getActiveMigrationBots(accountsToCheck, bot, result) {
388
+ if (bot) {
389
+ if (result.length !== accountsToCheck.length) {
390
+ console.error(
391
+ "result length mismatch for migration bots",
392
+ result.length,
393
+ accountsToCheck.length
394
+ );
395
+ }
396
+ return { result, botAddress: bot };
397
+ }
398
+ return void 0;
399
+ }
400
+ /**
401
+ * {@inheritDoc ICreditAccountsService.fullyLiquidate}
402
+ **/
403
+ async fullyLiquidate(props) {
404
+ const {
405
+ account,
406
+ to,
407
+ slippage = 50n,
408
+ keepAssets,
409
+ ignoreReservePrices,
410
+ applyLossPolicy,
411
+ debtOnly
412
+ } = props;
413
+ const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
414
+ const routerCloseResult = await this.sdk.routerFor(account).findBestClosePath({
415
+ creditAccount: account,
416
+ creditManager: cm.creditManager,
417
+ slippage,
418
+ keepAssets,
419
+ debtOnly
420
+ });
421
+ const calls = await this.#prependPriceUpdates(
422
+ account.creditManager,
423
+ routerCloseResult.calls,
424
+ account,
425
+ { ignoreReservePrices }
426
+ );
427
+ let lossPolicyData;
428
+ if (applyLossPolicy) {
429
+ const market = this.sdk.marketRegister.findByCreditManager(
430
+ account.creditManager
431
+ );
432
+ lossPolicyData = await market.lossPolicy.getLiquidationData(
433
+ account.creditAccount
434
+ );
435
+ this.logger?.debug({ lossPolicyData }, "loss policy data");
436
+ }
437
+ const tx = cm.creditFacade.liquidateCreditAccount(
438
+ account.creditAccount,
439
+ to,
440
+ calls,
441
+ lossPolicyData
442
+ );
443
+ return {
444
+ tx,
445
+ calls,
446
+ routerCloseResult,
447
+ lossPolicyData,
448
+ creditFacade: cm.creditFacade
449
+ };
450
+ }
451
+ /**
452
+ * {@inheritDoc ICreditAccountsService.calcMinSeizedAmount}
453
+ */
454
+ calcMinSeizedAmount(props) {
455
+ const { account, token, repaidAmount } = props;
456
+ const market = this.sdk.marketRegister.findByCreditManager(
457
+ account.creditManager
458
+ );
459
+ const suite = this.sdk.marketRegister.findCreditManager(
460
+ account.creditManager
461
+ );
462
+ const fee = suite.isExpired ? suite.creditManager.liquidationDiscountExpired : suite.creditManager.liquidationDiscount;
463
+ const tokenAmount = market.priceOracle.convert(
464
+ market.underlying,
465
+ token,
466
+ repaidAmount
467
+ );
468
+ return tokenAmount * 9990n / BigInt(fee);
469
+ }
470
+ /**
471
+ * {@inheritDoc ICreditAccountsService.partiallyLiquidate}
472
+ */
473
+ async partiallyLiquidate(props) {
474
+ const { account, token, repaidAmount, minSeizedAmount, to } = props;
475
+ const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
476
+ const updates = await this.getOnDemandPriceUpdates(account, true);
477
+ const tx = cm.creditFacade.partiallyLiquidateCreditAccount(
478
+ account.creditAccount,
479
+ token,
480
+ repaidAmount,
481
+ minSeizedAmount,
482
+ to,
483
+ updates
484
+ );
485
+ return tx;
486
+ }
487
+ /**
488
+ * {@inheritDoc ICreditAccountsService.closeCreditAccount}
489
+ **/
490
+ async closeCreditAccount({
491
+ operation,
492
+ assetsToWithdraw,
493
+ creditAccount: ca,
494
+ to,
495
+ slippage = 50n,
496
+ closePath
497
+ }) {
498
+ const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
499
+ await this.sdk.tokensMeta.loadTokenData(cm.underlying);
500
+ const underlying = this.sdk.tokensMeta.mustGet(cm.underlying);
501
+ if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
502
+ throw new Error(
503
+ "closeCreditAccount is not supported for KYC underlying credit accounts"
504
+ );
505
+ }
506
+ const routerCloseResult = closePath || await this.sdk.routerFor(ca).findBestClosePath({
507
+ creditAccount: ca,
508
+ creditManager: cm.creditManager,
509
+ slippage
510
+ });
511
+ const operationCalls = [
512
+ ...routerCloseResult.calls,
513
+ ...this.#prepareDisableQuotas(ca),
514
+ ...this.#prepareDecreaseDebt(ca),
515
+ ...assetsToWithdraw.map(
516
+ (t) => this.#prepareWithdrawToken(ca.creditFacade, t, MAX_UINT256, to)
517
+ )
518
+ ];
519
+ const calls = operation === "close" ? operationCalls : await this.#prependPriceUpdates(ca.creditManager, operationCalls, ca);
520
+ const tx = await this.#closeCreditAccountTx(
521
+ cm,
522
+ ca.creditAccount,
523
+ calls,
524
+ operation
525
+ );
526
+ return { tx, calls, routerCloseResult, creditFacade: cm.creditFacade };
527
+ }
528
+ /**
529
+ * {@inheritDoc ICreditAccountsService.updateQuotas}
530
+ **/
531
+ async updateQuotas({
532
+ minQuota,
533
+ averageQuota,
534
+ creditAccount
535
+ }) {
536
+ const cm = this.sdk.marketRegister.findCreditManager(
537
+ creditAccount.creditManager
538
+ );
539
+ const operationCalls = this.#prepareUpdateQuotas(
540
+ creditAccount.creditFacade,
541
+ { minQuota, averageQuota }
542
+ );
543
+ const calls = await this.#prependPriceUpdates(
544
+ creditAccount.creditManager,
545
+ operationCalls,
546
+ creditAccount
547
+ );
548
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
549
+ return { tx, calls, creditFacade: cm.creditFacade };
550
+ }
551
+ /**
552
+ * {@inheritDoc ICreditAccountsService.addCollateral}
553
+ **/
554
+ async addCollateral({
555
+ creditAccount,
556
+ asset,
557
+ permit,
558
+ ethAmount,
559
+ minQuota,
560
+ averageQuota
561
+ }) {
562
+ const cm = this.sdk.marketRegister.findCreditManager(
563
+ creditAccount.creditManager
564
+ );
565
+ const operationCalls = [
566
+ ...this.#prepareAddCollateral(
567
+ creditAccount.creditFacade,
568
+ [asset],
569
+ permit ? { [asset.token]: permit } : {}
570
+ ),
571
+ ...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
572
+ minQuota,
573
+ averageQuota
574
+ })
575
+ ];
576
+ const calls = await this.#prependPriceUpdates(
577
+ creditAccount.creditManager,
578
+ operationCalls,
579
+ creditAccount
580
+ );
581
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
582
+ tx.value = ethAmount.toString(10);
583
+ return { tx, calls, creditFacade: cm.creditFacade };
584
+ }
585
+ /**
586
+ * {@inheritDoc ICreditAccountsService.changeDebt}
587
+ **/
588
+ async changeDebt({
589
+ creditAccount,
590
+ amount,
591
+ collateral
592
+ }) {
593
+ if (amount === 0n) {
594
+ throw new Error("debt increase or decrease must be non-zero");
595
+ }
596
+ const isDecrease = amount < 0n;
597
+ const change = amount > 0n ? amount : -amount;
598
+ const cm = this.sdk.marketRegister.findCreditManager(
599
+ creditAccount.creditManager
600
+ );
601
+ const addCollateralCalls = collateral && isDecrease ? this.#prepareAddCollateral(
602
+ creditAccount.creditFacade,
603
+ [
604
+ {
605
+ token: collateral[0].token,
606
+ balance: collateral[0].balance
607
+ }
608
+ ],
609
+ {}
610
+ ) : [];
611
+ const unwrapCalls = collateral && isDecrease ? await this.getKYCUnwrapCalls(
612
+ collateral[0].balance,
613
+ creditAccount.creditManager
614
+ ) || [] : [];
615
+ if (addCollateralCalls.length > 0 && unwrapCalls.length === 0 && collateral && collateral?.[0].token !== creditAccount.underlying) {
616
+ throw new Error(
617
+ "Can't use collateral other than underlying for non KYC market"
618
+ );
619
+ }
620
+ const operationCalls = [
621
+ ...addCollateralCalls,
622
+ ...unwrapCalls,
623
+ this.#prepareChangeDebt(creditAccount.creditFacade, change, isDecrease)
624
+ ];
625
+ const calls = await this.#prependPriceUpdates(
626
+ creditAccount.creditManager,
627
+ operationCalls,
628
+ creditAccount
629
+ );
630
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
631
+ return { tx, calls, creditFacade: cm.creditFacade };
632
+ }
633
+ /**
634
+ * {@inheritDoc ICreditAccountsService.executeSwap}
635
+ **/
636
+ async executeSwap({
637
+ creditAccount,
638
+ calls: swapCalls,
639
+ minQuota,
640
+ averageQuota
641
+ }) {
642
+ if (swapCalls.length === 0) throw new Error("No path to execute");
643
+ const cm = this.sdk.marketRegister.findCreditManager(
644
+ creditAccount.creditManager
645
+ );
646
+ const operationCalls = [
647
+ ...swapCalls,
648
+ ...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
649
+ minQuota,
650
+ averageQuota
651
+ })
652
+ ];
653
+ const calls = await this.#prependPriceUpdates(
654
+ creditAccount.creditManager,
655
+ operationCalls,
656
+ creditAccount
657
+ );
658
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
659
+ return { tx, calls, creditFacade: cm.creditFacade };
660
+ }
661
+ /**
662
+ * {@inheritDoc ICreditAccountsService.previewDelayedWithdrawal}
663
+ **/
664
+ async previewDelayedWithdrawal({
665
+ creditAccount,
666
+ amount,
667
+ token
668
+ }) {
669
+ const compressor = getWithdrawalCompressorAddress(this.sdk.chainId);
670
+ if (!compressor)
671
+ throw new Error(
672
+ `No compressor for current chain ${this.sdk.networkType}`
673
+ );
674
+ const contract = getContract({
675
+ address: compressor,
676
+ abi: iWithdrawalCompressorV310Abi,
677
+ client: this.client
678
+ });
679
+ const resp = await contract.read.getWithdrawalRequestResult([
680
+ creditAccount,
681
+ token,
682
+ amount
683
+ ]);
684
+ return resp;
685
+ }
686
+ /**
687
+ * {@inheritDoc ICreditAccountsService.getPendingWithdrawals}
688
+ **/
689
+ async getPendingWithdrawals({
690
+ creditAccount
691
+ }) {
692
+ const compressor = getWithdrawalCompressorAddress(this.sdk.chainId);
693
+ if (!compressor)
694
+ throw new Error(
695
+ `No compressor for current chain ${this.sdk.networkType}`
696
+ );
697
+ const contract = getContract({
698
+ address: compressor,
699
+ abi: iWithdrawalCompressorV310Abi,
700
+ client: this.client
701
+ });
702
+ const resp = await contract.read.getCurrentWithdrawals([creditAccount]);
703
+ const claimableNow = resp?.[0] || [];
704
+ const pendingResult = [...resp?.[1] || []].sort(
705
+ (a, b) => a.claimableAt < b.claimableAt ? -1 : 1
706
+ );
707
+ const respResult = {
708
+ claimableNow: [...claimableNow],
709
+ pending: pendingResult
710
+ };
711
+ return respResult;
712
+ }
713
+ /**
714
+ * {@inheritDoc ICreditAccountsService.startDelayedWithdrawal}
715
+ **/
716
+ async startDelayedWithdrawal({
717
+ creditAccount,
718
+ minQuota,
719
+ averageQuota,
720
+ preview
721
+ }) {
722
+ const cm = this.sdk.marketRegister.findCreditManager(
723
+ creditAccount.creditManager
724
+ );
725
+ const record = preview.outputs.reduce((acc, o) => {
726
+ const token = o.token.toLowerCase();
727
+ acc[token] = (acc[token] || 0n) + o.amount;
728
+ return acc;
729
+ }, {});
730
+ const balances = Object.entries(record).filter(([, a]) => a > 10n);
731
+ const storeExpectedBalances = {
732
+ target: cm.creditFacade.address,
733
+ callData: encodeFunctionData({
734
+ abi: iCreditFacadeMulticallV310Abi,
735
+ functionName: "storeExpectedBalances",
736
+ args: [
737
+ balances.map(([token, amount]) => ({
738
+ token,
739
+ amount: amount > 10n ? amount - 10n : 0n
740
+ }))
741
+ ]
742
+ })
743
+ };
744
+ const compareBalances = {
745
+ target: cm.creditFacade.address,
746
+ callData: encodeFunctionData({
747
+ abi: iCreditFacadeMulticallV310Abi,
748
+ functionName: "compareBalances",
749
+ args: []
750
+ })
751
+ };
752
+ const operationCalls = [
753
+ storeExpectedBalances,
754
+ ...preview.requestCalls,
755
+ compareBalances,
756
+ ...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
757
+ minQuota,
758
+ averageQuota
759
+ })
760
+ ];
761
+ const calls = await this.#prependPriceUpdates(
762
+ creditAccount.creditManager,
763
+ operationCalls,
764
+ creditAccount
765
+ );
766
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
767
+ return { tx, calls, creditFacade: cm.creditFacade };
768
+ }
769
+ /**
770
+ * {@inheritDoc ICreditAccountsService.claimDelayed}
771
+ **/
772
+ async claimDelayed({
773
+ creditAccount,
774
+ minQuota,
775
+ averageQuota,
776
+ claimableNow
777
+ }) {
778
+ const zeroDebt = creditAccount.debt === 0n;
779
+ const cm = this.sdk.marketRegister.findCreditManager(
780
+ creditAccount.creditManager
781
+ );
782
+ const record = claimableNow.outputs.reduce(
783
+ (acc, o) => {
784
+ const token = o.token.toLowerCase();
785
+ acc[token] = (acc[token] || 0n) + o.amount;
786
+ return acc;
787
+ },
788
+ {}
789
+ );
790
+ const balances = Object.entries(record).filter(([, a]) => a > 10n);
791
+ const storeExpectedBalances = {
792
+ target: cm.creditFacade.address,
793
+ callData: encodeFunctionData({
794
+ abi: iCreditFacadeMulticallV310Abi,
795
+ functionName: "storeExpectedBalances",
796
+ args: [
797
+ balances.map(([token, amount]) => ({
798
+ token,
799
+ amount: amount > 10n ? amount - 10n : 0n
800
+ }))
801
+ ]
802
+ })
803
+ };
804
+ const compareBalances = {
805
+ target: cm.creditFacade.address,
806
+ callData: encodeFunctionData({
807
+ abi: iCreditFacadeMulticallV310Abi,
808
+ functionName: "compareBalances",
809
+ args: []
810
+ })
811
+ };
812
+ const quotaCalls = zeroDebt ? [] : this.#prepareUpdateQuotas(creditAccount.creditFacade, {
813
+ minQuota,
814
+ averageQuota
815
+ });
816
+ const operationCalls = [
817
+ storeExpectedBalances,
818
+ ...claimableNow.claimCalls,
819
+ compareBalances,
820
+ ...quotaCalls
821
+ ];
822
+ const calls = zeroDebt ? operationCalls : await this.#prependPriceUpdates(
823
+ creditAccount.creditManager,
824
+ operationCalls,
825
+ creditAccount
826
+ );
827
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
828
+ return { tx, calls, creditFacade: cm.creditFacade };
829
+ }
830
+ /**
831
+ * {@inheritDoc ICreditAccountsService.getApprovalAddress}
832
+ **/
833
+ async getApprovalAddress(options) {
834
+ const { creditManager } = options;
835
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
836
+ const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
837
+ const factory = marketSuite.kycFactory;
838
+ if (factory) {
839
+ return factory.getApprovalAddress(options);
840
+ }
841
+ return suite.creditManager.address;
842
+ }
843
+ /**
844
+ * {@inheritDoc ICreditAccountsService.getOpenAccountRequirements}
845
+ */
846
+ async getOpenAccountRequirements(borrower, creditManager, props) {
847
+ const { kycFactory } = this.sdk.marketRegister.findByCreditManager(creditManager);
848
+ if (!kycFactory) {
849
+ return void 0;
850
+ }
851
+ return kycFactory.getOpenAccountRequirements(borrower, props);
852
+ }
853
+ /**
854
+ * {@inheritDoc ICreditAccountsService.openCA}
855
+ **/
856
+ async openCA(props) {
857
+ const {
858
+ ethAmount,
859
+ creditManager,
860
+ reopenCreditAccount,
861
+ collateral,
862
+ permits,
863
+ debt,
864
+ withdrawToken,
865
+ referralCode,
866
+ to,
867
+ calls: openPathCalls,
868
+ callsAfter,
869
+ minQuota,
870
+ averageQuota,
871
+ kycOptions
872
+ } = props;
873
+ const cmSuite = this.sdk.marketRegister.findCreditManager(creditManager);
874
+ const cm = cmSuite.creditManager;
875
+ let tokenToWithdraw;
876
+ if (withdrawToken === true) {
877
+ tokenToWithdraw = cm.underlying;
878
+ } else if (typeof withdrawToken === "string") {
879
+ tokenToWithdraw = withdrawToken;
880
+ }
881
+ const operationCalls = [
882
+ this.#prepareIncreaseDebt(cm.creditFacade, debt),
883
+ ...this.#prepareAddCollateral(cm.creditFacade, collateral, permits),
884
+ ...openPathCalls,
885
+ // path from underlying to withdrawal token
886
+ ...tokenToWithdraw ? [
887
+ this.#prepareWithdrawToken(
888
+ cm.creditFacade,
889
+ tokenToWithdraw,
890
+ MAX_UINT256,
891
+ to
892
+ )
893
+ ] : [],
894
+ ...this.#prepareUpdateQuotas(cm.creditFacade, {
895
+ minQuota,
896
+ averageQuota
897
+ }),
898
+ ...callsAfter ?? []
899
+ ];
900
+ const calls = await this.#prependPriceUpdates(cm.address, operationCalls);
901
+ let tx;
902
+ if (reopenCreditAccount) {
903
+ tx = await this.#multicallTx(cmSuite, reopenCreditAccount, calls);
904
+ } else {
905
+ tx = await this.#openCreditAccountTx(
906
+ cmSuite,
907
+ to,
908
+ calls,
909
+ referralCode,
910
+ kycOptions
911
+ );
912
+ }
913
+ tx.value = ethAmount.toString(10);
914
+ return { calls, tx, creditFacade: cmSuite.creditFacade };
915
+ }
916
+ /**
917
+ * {@inheritDoc ICreditAccountsService.getBorrowRate}
918
+ **/
919
+ getBorrowRate(ca) {
920
+ const { creditManager } = this.sdk.marketRegister.findCreditManager(
921
+ ca.creditManager
922
+ );
923
+ const { pool } = this.sdk.marketRegister.findByCreditManager(
924
+ ca.creditManager
925
+ );
926
+ const { feeInterest } = creditManager;
927
+ const { baseInterestRate } = pool.pool;
928
+ const baseRateWithFee = baseInterestRate * (BigInt(feeInterest) + PERCENTAGE_FACTOR);
929
+ const totalDebt = ca.debt + ca.accruedInterest + ca.accruedFees;
930
+ const r = ca.debt * baseRateWithFee / (totalDebt * RAY);
931
+ const caTokens = new AddressMap(ca.tokens.map((t) => [t.token, t]));
932
+ let qr = 0n;
933
+ for (const t of creditManager.collateralTokens) {
934
+ const b = caTokens.get(t);
935
+ if (b) {
936
+ qr += b.quota * BigInt(pool.pqk.quotas.get(t)?.rate ?? 0);
937
+ }
938
+ }
939
+ qr = qr * (BigInt(feeInterest) + PERCENTAGE_FACTOR) / PERCENTAGE_FACTOR;
940
+ qr /= totalDebt;
941
+ return r + qr;
942
+ }
943
+ /**
944
+ * {@inheritDoc ICreditAccountsService.getOptimalHFForPartialLiquidation}
945
+ **/
946
+ getOptimalHFForPartialLiquidation(ca) {
947
+ const borrowRate = this.getBorrowRate(ca);
948
+ return PERCENTAGE_FACTOR + (borrowRate < 100n ? borrowRate : 100n);
949
+ }
950
+ /**
951
+ * Internal wrapper for CreditAccountCompressor.getCreditAccounts + price updates wrapped into multicall
952
+ * @param args
953
+ * @param priceUpdateTxs
954
+ * @param blockNumber
955
+ * @returns
956
+ */
957
+ async #getCreditAccounts(args, priceUpdateTxs, blockNumber) {
958
+ let resp;
959
+ if (priceUpdateTxs?.length) {
960
+ [resp] = await simulateWithPriceUpdates(this.client, {
961
+ priceUpdates: priceUpdateTxs,
962
+ contracts: [
963
+ {
964
+ abi: creditAccountCompressorAbi,
965
+ address: this.compressor,
966
+ functionName: "getCreditAccounts",
967
+ args
968
+ }
969
+ ],
970
+ blockNumber,
971
+ gas: this.sdk.gasLimit
972
+ });
973
+ } else {
974
+ resp = await this.client.readContract({
975
+ abi: creditAccountCompressorAbi,
976
+ address: this.compressor,
977
+ functionName: "getCreditAccounts",
978
+ args,
979
+ blockNumber,
980
+ // @ts-expect-error
981
+ gas: this.sdk.gasLimit
982
+ });
983
+ }
984
+ this.logger?.debug(
985
+ {
986
+ accounts: resp[0]?.length ?? 0,
987
+ nextOffset: Number(resp[1])
988
+ },
989
+ "got credit accounts"
990
+ );
991
+ return resp;
992
+ }
993
+ /**
994
+ * Returns multicall entries to redeem (unwrap) KYC ERC-4626 vault shares into underlying for the given credit manager.
995
+ * Used when withdrawing debt from a KYC market: redeems adapter vault shares so the underlying can be withdrawn.
996
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
997
+ * @param amount - Number of vault shares (adapter tokens) to redeem
998
+ * @param creditManager - Credit manager address
999
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1000
+ */
1001
+ async getKYCUnwrapCalls(amount, creditManager) {
1002
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1003
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1004
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1005
+ return void 0;
1006
+ }
1007
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1008
+ const adapterAddress = adapter?.address;
1009
+ if (!adapterAddress) {
1010
+ return void 0;
1011
+ }
1012
+ const mc = [
1013
+ {
1014
+ target: adapterAddress,
1015
+ callData: encodeFunctionData({
1016
+ abi: ierc4626AdapterAbi,
1017
+ functionName: "redeem",
1018
+ args: [amount, ADDRESS_0X0, ADDRESS_0X0]
1019
+ })
1020
+ }
1021
+ ];
1022
+ return mc;
1023
+ }
1024
+ /**
1025
+ * Returns multicall entries to deposit (wrap) underlying into KYC ERC-4626 vault shares for the given credit manager.
1026
+ * Used when adding debt on a KYC market: deposits underlying into the adapter vault so shares are minted on the account.
1027
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1028
+ * @param amount - Amount of underlying assets to deposit into the vault (in underlying decimals)
1029
+ * @param creditManager - Credit manager address
1030
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1031
+ */
1032
+ async getKYCWrapCalls(amount, creditManager) {
1033
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1034
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1035
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1036
+ return void 0;
1037
+ }
1038
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1039
+ const adapterAddress = adapter?.address;
1040
+ if (!adapterAddress) {
1041
+ return void 0;
1042
+ }
1043
+ const mc = [
1044
+ {
1045
+ target: adapterAddress,
1046
+ callData: encodeFunctionData({
1047
+ abi: ierc4626AdapterAbi,
1048
+ functionName: "deposit",
1049
+ args: [amount, ADDRESS_0X0]
1050
+ })
1051
+ }
1052
+ ];
1053
+ return mc;
1054
+ }
1055
+ /**
1056
+ * Returns multicall entries to call redeemDiff on the KYC ERC-4626 adapter for the given credit manager.
1057
+ * Redeems the leftover vault shares (e.g. after repaying debt) so the account does not hold excess KYC vault tokens.
1058
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1059
+ * @param amount - Leftover vault share amount to redeem (in adapter/vault decimals)
1060
+ * @param creditManager - Credit manager address
1061
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1062
+ */
1063
+ async getRedeemDiffCalls(amount, creditManager) {
1064
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1065
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1066
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1067
+ return void 0;
1068
+ }
1069
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1070
+ const adapterAddress = adapter?.address;
1071
+ if (!adapterAddress) {
1072
+ return void 0;
1073
+ }
1074
+ const mc = [
1075
+ {
1076
+ target: adapterAddress,
1077
+ callData: encodeFunctionData({
1078
+ abi: ierc4626AdapterAbi,
1079
+ functionName: "redeemDiff",
1080
+ args: [amount]
1081
+ })
1082
+ }
1083
+ ];
1084
+ return mc;
1085
+ }
1086
+ /**
1087
+ * Returns multicall entries to call depositDiff on the KYC ERC-4626 adapter for the given credit manager.
1088
+ * Deposits the leftover underlying (e.g. after decreasing debt) into the vault so the account does not hold excess underlying.
1089
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1090
+ * @param amount - Leftover underlying amount to deposit into the vault (in underlying decimals)
1091
+ * @param creditManager - Credit manager address
1092
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1093
+ */
1094
+ async getDepositDiffCalls(amount, creditManager) {
1095
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1096
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1097
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1098
+ return void 0;
1099
+ }
1100
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1101
+ const adapterAddress = adapter?.address;
1102
+ if (!adapterAddress) {
1103
+ return void 0;
1104
+ }
1105
+ const mc = [
1106
+ {
1107
+ target: adapterAddress,
1108
+ callData: encodeFunctionData({
1109
+ abi: ierc4626AdapterAbi,
1110
+ functionName: "depositDiff",
1111
+ args: [amount]
1112
+ })
1113
+ }
1114
+ ];
1115
+ return mc;
1116
+ }
6
1117
  /**
7
1118
  * {@inheritDoc ICreditAccountsService.setBot}
8
1119
  */
@@ -37,12 +1148,12 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
37
1148
  args: [botAddress, permissions]
38
1149
  })
39
1150
  };
40
- const calls = targetContract.type === "creditAccount" ? await this.prependPriceUpdates(
1151
+ const calls = targetContract.type === "creditAccount" ? await this.#prependPriceUpdates(
41
1152
  targetContract.creditManager,
42
1153
  [addBotCall],
43
1154
  targetContract
44
1155
  ) : [addBotCall];
45
- const tx = targetContract.type === "creditAccount" ? await this.multicallTx(cm, targetContract.creditAccount, calls) : void 0;
1156
+ const tx = targetContract.type === "creditAccount" ? await this.#multicallTx(cm, targetContract.creditAccount, calls) : void 0;
46
1157
  return { tx, calls, creditFacade: cm.creditFacade };
47
1158
  }
48
1159
  /**
@@ -60,24 +1171,24 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
60
1171
  );
61
1172
  const operationCalls = [
62
1173
  ...assetsToWithdraw.map(
63
- (a) => this.prepareWithdrawToken(
1174
+ (a) => this.#prepareWithdrawToken(
64
1175
  creditAccount.creditFacade,
65
1176
  a.token,
66
1177
  a.balance,
67
1178
  to
68
1179
  )
69
1180
  ),
70
- ...this.prepareUpdateQuotas(creditAccount.creditFacade, {
1181
+ ...this.#prepareUpdateQuotas(creditAccount.creditFacade, {
71
1182
  minQuota,
72
1183
  averageQuota
73
1184
  })
74
1185
  ];
75
- const calls = await this.prependPriceUpdates(
1186
+ const calls = await this.#prependPriceUpdates(
76
1187
  creditAccount.creditManager,
77
1188
  operationCalls,
78
1189
  creditAccount
79
1190
  );
80
- const tx = await this.multicallTx(cm, creditAccount.creditAccount, calls);
1191
+ const tx = await this.#multicallTx(cm, creditAccount.creditAccount, calls);
81
1192
  return { tx, calls, creditFacade: cm.creditFacade };
82
1193
  }
83
1194
  /**
@@ -102,18 +1213,18 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
102
1213
  creditAccount: ca
103
1214
  });
104
1215
  const operationCalls = [
105
- ...this.prepareAddCollateral(ca.creditFacade, addCollateral, permits),
1216
+ ...this.#prepareAddCollateral(ca.creditFacade, addCollateral, permits),
106
1217
  ...wrapCalls,
107
- ...this.prepareDisableQuotas(ca),
108
- ...this.prepareDecreaseDebt(ca),
1218
+ ...this.#prepareDisableQuotas(ca),
1219
+ ...this.#prepareDecreaseDebt(ca),
109
1220
  ...unwrapCalls,
110
1221
  ...claimPath.calls,
111
1222
  ...assetsToWithdraw.map(
112
- (t) => this.prepareWithdrawToken(ca.creditFacade, t.token, MAX_UINT256, to)
1223
+ (t) => this.#prepareWithdrawToken(ca.creditFacade, t.token, MAX_UINT256, to)
113
1224
  )
114
1225
  ];
115
- const calls = operation === "close" ? operationCalls : await this.prependPriceUpdates(ca.creditManager, operationCalls, ca);
116
- const tx = await this.closeCreditAccountTx(
1226
+ const calls = operation === "close" ? operationCalls : await this.#prependPriceUpdates(ca.creditManager, operationCalls, ca);
1227
+ const tx = await this.#closeCreditAccountTx(
117
1228
  cm,
118
1229
  ca.creditAccount,
119
1230
  calls,
@@ -141,14 +1252,14 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
141
1252
  const wrapCalls = await this.getDepositDiffCalls(1n, ca.creditManager) ?? [];
142
1253
  const addCollateral = collateralAssets.filter((a) => a.balance > 0);
143
1254
  const operationCalls = [
144
- ...this.prepareAddCollateral(ca.creditFacade, addCollateral, permits),
1255
+ ...this.#prepareAddCollateral(ca.creditFacade, addCollateral, permits),
145
1256
  ...claimPath.calls,
146
1257
  ...wrapCalls,
147
1258
  ...assetsToWithdraw.map(
148
- (t) => this.prepareWithdrawToken(ca.creditFacade, t.token, MAX_UINT256, to)
1259
+ (t) => this.#prepareWithdrawToken(ca.creditFacade, t.token, MAX_UINT256, to)
149
1260
  )
150
1261
  ];
151
- const calls = await this.prependPriceUpdates(
1262
+ const calls = await this.#prependPriceUpdates(
152
1263
  ca.creditManager,
153
1264
  operationCalls,
154
1265
  ca
@@ -184,17 +1295,349 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
184
1295
  if (claimPath.calls.length === 0) throw new Error("No path to execute");
185
1296
  const operationCalls = [
186
1297
  ...claimPath.calls,
187
- ...this.prepareUpdateQuotas(ca.creditFacade, { minQuota, averageQuota })
1298
+ ...this.#prepareUpdateQuotas(ca.creditFacade, { minQuota, averageQuota })
188
1299
  ];
189
- const calls = await this.prependPriceUpdates(
1300
+ const calls = await this.#prependPriceUpdates(
190
1301
  ca.creditManager,
191
1302
  operationCalls,
192
1303
  ca
193
1304
  );
194
- const tx = await this.multicallTx(cm, ca.creditAccount, calls);
1305
+ const tx = await this.#multicallTx(cm, ca.creditAccount, calls);
195
1306
  return { tx, calls, creditFacade: cm.creditFacade };
196
1307
  }
1308
+ /**
1309
+ * Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
1310
+ * {@inheritDoc ICreditAccountsService.getOnDemandPriceUpdates}
1311
+ **/
1312
+ async getOnDemandPriceUpdates(account, ignoreReservePrices) {
1313
+ const { creditManager, creditAccount } = account;
1314
+ const cm = this.sdk.marketRegister.findCreditManager(creditManager);
1315
+ const update = await this.#getUpdateForAccount(
1316
+ account,
1317
+ ignoreReservePrices
1318
+ );
1319
+ this.logger?.debug(
1320
+ { account: creditAccount, manager: cm.name },
1321
+ `getting on demand price updates from ${update.txs.length} txs`
1322
+ );
1323
+ return getRawPriceUpdates(update);
1324
+ }
1325
+ /**
1326
+ * Analyzes a multicall array and prepends necessary on-demand price feed updates.
1327
+ *
1328
+ * Deduplicates existing `onDemandPriceUpdates` calls
1329
+ *
1330
+ * @param creditManager - Address of the credit manager
1331
+ * @param calls - The multicall array to prepend price updates to
1332
+ * @param ca - Credit account slice, undefined when opening a new account
1333
+ * @param options - Optional settings for price update generation
1334
+ * @returns A new array with a single consolidated price update call prepended,
1335
+ * followed by the non-price-update calls in their original order
1336
+ */
1337
+ async #prependPriceUpdates(creditManager, calls, ca, options) {
1338
+ const market = this.sdk.marketRegister.findByCreditManager(creditManager);
1339
+ const cm = this.sdk.marketRegister.findCreditManager(creditManager).creditManager;
1340
+ const { priceUpdates: existingUpdates, remainingCalls } = extractPriceUpdates(calls);
1341
+ const tokens = new AddressSet([
1342
+ cm.underlying,
1343
+ // underlying - always included
1344
+ ...extractQuotaTokens(calls)
1345
+ // tokens from `updateQuota` calls
1346
+ ]);
1347
+ if (ca) {
1348
+ for (const t of ca.tokens) {
1349
+ const isEnabled = (t.mask & ca.enabledTokensMask) !== 0n;
1350
+ if (t.balance > 10n && isEnabled) {
1351
+ tokens.add(t.token);
1352
+ }
1353
+ }
1354
+ }
1355
+ const ignoreReservePrices = options?.ignoreReservePrices;
1356
+ const priceFeeds = market.priceOracle.priceFeedsForTokens(Array.from(tokens), {
1357
+ main: true,
1358
+ reserve: !ignoreReservePrices
1359
+ });
1360
+ const tStr = tokens.map((t) => this.labelAddress(t)).join(", ");
1361
+ const remark = ignoreReservePrices ? " main" : "";
1362
+ this.logger?.debug(
1363
+ { account: ca?.creditAccount, manager: cm.name },
1364
+ `prependPriceUpdates for ${tStr} from ${priceFeeds.length}${remark} price feeds`
1365
+ );
1366
+ const generatedUpdates = await this.sdk.priceFeeds.generatePriceFeedsUpdates(priceFeeds);
1367
+ const merged = mergePriceUpdates(existingUpdates, generatedUpdates);
1368
+ if (merged.length === 0) {
1369
+ return remainingCalls;
1370
+ }
1371
+ return [
1372
+ {
1373
+ target: cm.creditFacade,
1374
+ callData: encodeFunctionData({
1375
+ abi: iCreditFacadeMulticallV310Abi,
1376
+ functionName: "onDemandPriceUpdates",
1377
+ args: [merged]
1378
+ })
1379
+ },
1380
+ ...remainingCalls
1381
+ ];
1382
+ }
1383
+ async #getUpdateForAccount(account, ignoreReservePrices) {
1384
+ const { creditManager, creditAccount, enabledTokensMask } = account;
1385
+ const market = this.sdk.marketRegister.findByCreditManager(creditManager);
1386
+ const cm = this.sdk.marketRegister.findCreditManager(creditManager).creditManager;
1387
+ const tokens = new AddressSet([cm.underlying]);
1388
+ for (const t of account.tokens) {
1389
+ const isEnabled = (t.mask & enabledTokensMask) !== 0n;
1390
+ if (t.balance > 10n && isEnabled) {
1391
+ tokens.add(t.token);
1392
+ }
1393
+ }
1394
+ const priceFeeds = market.priceOracle.priceFeedsForTokens(Array.from(tokens), {
1395
+ main: true,
1396
+ reserve: !ignoreReservePrices
1397
+ });
1398
+ const tStr = tokens.map((t) => this.labelAddress(t)).join(", ");
1399
+ const remark = ignoreReservePrices ? " main" : "";
1400
+ this.logger?.debug(
1401
+ { account: creditAccount, manager: cm.name },
1402
+ `generating price feed updates for ${tStr} from ${priceFeeds.length}${remark} price feeds`
1403
+ );
1404
+ return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(priceFeeds);
1405
+ }
1406
+ /**
1407
+ * {@inheritDoc ICreditAccountsService.multicall}
1408
+ */
1409
+ async multicall(creditAccount, calls, options) {
1410
+ const cm = this.sdk.marketRegister.findCreditManager(
1411
+ creditAccount.creditManager
1412
+ );
1413
+ const callsWithPrices = await this.#prependPriceUpdates(
1414
+ creditAccount.creditManager,
1415
+ calls,
1416
+ creditAccount,
1417
+ options
1418
+ );
1419
+ return cm.creditFacade.multicall(
1420
+ creditAccount.creditAccount,
1421
+ callsWithPrices
1422
+ );
1423
+ }
1424
+ /**
1425
+ * {@inheritDoc ICreditAccountsService.botMulticall}
1426
+ */
1427
+ async botMulticall(creditAccount, calls, options) {
1428
+ const cm = this.sdk.marketRegister.findCreditManager(
1429
+ creditAccount.creditManager
1430
+ );
1431
+ const callsWithPrices = await this.#prependPriceUpdates(
1432
+ creditAccount.creditManager,
1433
+ calls,
1434
+ creditAccount,
1435
+ options
1436
+ );
1437
+ return cm.creditFacade.botMulticall(
1438
+ creditAccount.creditAccount,
1439
+ callsWithPrices
1440
+ );
1441
+ }
1442
+ #prepareDisableQuotas(ca) {
1443
+ const calls = [];
1444
+ for (const { token, quota } of ca.tokens) {
1445
+ if (quota > 0n) {
1446
+ calls.push({
1447
+ target: ca.creditFacade,
1448
+ callData: encodeFunctionData({
1449
+ abi: iCreditFacadeMulticallV310Abi,
1450
+ functionName: "updateQuota",
1451
+ args: [token, MIN_INT96, 0n]
1452
+ })
1453
+ });
1454
+ }
1455
+ }
1456
+ return calls;
1457
+ }
1458
+ #prepareUpdateQuotas(creditFacade, { averageQuota, minQuota }) {
1459
+ const minRecord = assetsMap(minQuota);
1460
+ const calls = averageQuota.map((q) => {
1461
+ const minAsset = minRecord.get(q.token);
1462
+ const min = minAsset && minAsset?.balance > 0 ? minAsset.balance : 0n;
1463
+ return {
1464
+ target: creditFacade,
1465
+ callData: encodeFunctionData({
1466
+ abi: iCreditFacadeMulticallV310Abi,
1467
+ functionName: "updateQuota",
1468
+ args: [q.token, q.balance, min]
1469
+ })
1470
+ };
1471
+ });
1472
+ return calls;
1473
+ }
1474
+ #prepareDecreaseDebt(ca) {
1475
+ if (ca.debt > 0n) {
1476
+ return [
1477
+ {
1478
+ target: ca.creditFacade,
1479
+ callData: encodeFunctionData({
1480
+ abi: iCreditFacadeMulticallV310Abi,
1481
+ functionName: "decreaseDebt",
1482
+ args: [MAX_UINT256]
1483
+ })
1484
+ }
1485
+ ];
1486
+ }
1487
+ return [];
1488
+ }
1489
+ #prepareWithdrawToken(creditFacade, token, amount, to) {
1490
+ return {
1491
+ target: creditFacade,
1492
+ callData: encodeFunctionData({
1493
+ abi: iCreditFacadeMulticallV310Abi,
1494
+ functionName: "withdrawCollateral",
1495
+ args: [token, amount, to]
1496
+ })
1497
+ };
1498
+ }
1499
+ #prepareIncreaseDebt(creditFacade, debt) {
1500
+ return {
1501
+ target: creditFacade,
1502
+ callData: encodeFunctionData({
1503
+ abi: iCreditFacadeMulticallV310Abi,
1504
+ functionName: "increaseDebt",
1505
+ args: [debt]
1506
+ })
1507
+ };
1508
+ }
1509
+ #prepareChangeDebt(creditFacade, change, isDecrease) {
1510
+ return {
1511
+ target: creditFacade,
1512
+ callData: encodeFunctionData({
1513
+ abi: iCreditFacadeMulticallV310Abi,
1514
+ functionName: isDecrease ? "decreaseDebt" : "increaseDebt",
1515
+ args: [change]
1516
+ })
1517
+ };
1518
+ }
1519
+ #prepareAddCollateral(creditFacade, assets, permits) {
1520
+ const calls = assets.map(({ token, balance }) => {
1521
+ const p = permits[token];
1522
+ if (p) {
1523
+ return {
1524
+ target: creditFacade,
1525
+ callData: encodeFunctionData({
1526
+ abi: iCreditFacadeMulticallV310Abi,
1527
+ functionName: "addCollateralWithPermit",
1528
+ args: [token, balance, p.deadline, p.v, p.r, p.s]
1529
+ })
1530
+ };
1531
+ }
1532
+ return {
1533
+ target: creditFacade,
1534
+ callData: encodeFunctionData({
1535
+ abi: iCreditFacadeMulticallV310Abi,
1536
+ functionName: "addCollateral",
1537
+ args: [token, balance]
1538
+ })
1539
+ };
1540
+ });
1541
+ return calls;
1542
+ }
1543
+ /**
1544
+ * Returns addresses of market configurators
1545
+ */
1546
+ get marketConfigurators() {
1547
+ return this.sdk.marketRegister.marketConfigurators.map((mc) => mc.address);
1548
+ }
1549
+ get rewardCompressor() {
1550
+ return this.sdk.addressProvider.mustGetLatest(
1551
+ AP_REWARDS_COMPRESSOR,
1552
+ VERSION_RANGE_310
1553
+ )[0];
1554
+ }
1555
+ get peripheryCompressor() {
1556
+ return this.sdk.addressProvider.mustGetLatest(
1557
+ AP_PERIPHERY_COMPRESSOR,
1558
+ VERSION_RANGE_310
1559
+ )[0];
1560
+ }
1561
+ get compressor() {
1562
+ if (!this.#compressor) {
1563
+ [this.#compressor] = this.sdk.addressProvider.mustGetLatest(
1564
+ AP_CREDIT_ACCOUNT_COMPRESSOR,
1565
+ VERSION_RANGE_310
1566
+ );
1567
+ this.logger?.debug(
1568
+ `credit account compressor address: ${this.#compressor}`
1569
+ );
1570
+ }
1571
+ return this.#compressor;
1572
+ }
1573
+ /**
1574
+ * Wrapper that selects between credit facade and KYC factory
1575
+ * @param suite
1576
+ * @param to
1577
+ * @param calls
1578
+ * @param referralCode
1579
+ * @param kycOptions
1580
+ * @returns
1581
+ */
1582
+ async #openCreditAccountTx(suite, to, calls, referralCode, kycOptions) {
1583
+ const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
1584
+ const factory = marketSuite.kycFactory;
1585
+ if (factory) {
1586
+ return factory.openCreditAccount(
1587
+ suite.creditManager.address,
1588
+ calls,
1589
+ kycOptions
1590
+ );
1591
+ }
1592
+ return suite.creditFacade.openCreditAccount(to, calls, referralCode ?? 0n);
1593
+ }
1594
+ /**
1595
+ * Wrapper that selects between credit facade and KYC factory
1596
+ * @param suite
1597
+ * @param creditAccount
1598
+ * @param calls
1599
+ * @param kycOptions
1600
+ * @returns
1601
+ */
1602
+ async #multicallTx(suite, creditAccount, calls, kycOptions) {
1603
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
1604
+ suite.creditManager.address
1605
+ );
1606
+ const factory = marketSuite.kycFactory;
1607
+ if (factory) {
1608
+ return factory.multicall(creditAccount, calls, kycOptions);
1609
+ }
1610
+ return suite.creditFacade.multicall(creditAccount, calls);
1611
+ }
1612
+ /**
1613
+ * Wrapper that selects between credit facade and KYC factory
1614
+ * @param suite
1615
+ * @param creditAccount
1616
+ * @param calls
1617
+ * @param operation
1618
+ * @param kycOptions
1619
+ * @returns
1620
+ */
1621
+ async #closeCreditAccountTx(suite, creditAccount, calls, operation, kycOptions) {
1622
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
1623
+ suite.creditManager.address
1624
+ );
1625
+ const factory = marketSuite.kycFactory;
1626
+ if (operation === "close") {
1627
+ if (factory) {
1628
+ throw new Error(
1629
+ "CloseOptions=close is not supported for KYC underlying credit accounts"
1630
+ );
1631
+ }
1632
+ return suite.creditFacade.closeCreditAccount(creditAccount, calls);
1633
+ }
1634
+ if (factory) {
1635
+ return factory.multicall(creditAccount, calls, kycOptions);
1636
+ }
1637
+ return suite.creditFacade.multicall(creditAccount, calls);
1638
+ }
197
1639
  }
198
1640
  export {
199
- CreditAccountServiceV310
1641
+ CreditAccountsServiceV310,
1642
+ getWithdrawalCompressorAddress
200
1643
  };